2199 lines
84 KiB
PHP
2199 lines
84 KiB
PHP
<?php
|
|
|
|
namespace BRSystems\Core\App\pubUsers\inc;
|
|
|
|
use BRSystems\Core\App\index\inc\indexController;
|
|
use BRSystems\Core\App\index\inc\indexModule;
|
|
use BRSystems\Core\App\pubBuy\inc\pubBuyController as BuyController;
|
|
use BRSystems\Core\App\pubBuy\inc\pubBuyModule;
|
|
use BRSystems\Core\Cms\cmsProducts\inc\cmsProductsModule;
|
|
use BRSystems\Core\Cms\cmsTextManager\inc\cmsTextManagerConst;
|
|
use BRSystems\Core\Library\cCore\cCore;
|
|
use BRSystems\Core\Library\cCoreSystem\cCoreSystem;
|
|
use BRSystems\Core\Library\cDefuseCrypto\cDefuseCrypto;
|
|
use BRSystems\Core\Library\cGoogle2FA\cGoogle2FA;
|
|
use BRSystems\Core\Library\cJSON\cJSON;
|
|
use BRSystems\Core\Library\cMail\cMail;
|
|
use BRSystems\Core\Library\cNumbers\cNumbers;
|
|
use BRSystems\Core\Library\cPasswordMaster\cPasswordMaster;
|
|
use BRSystems\Core\Library\cSession\cSession;
|
|
use BRSystems\Core\Library\cUsersOnline\cUsersOnline;
|
|
use BRSystems\Core\Library\cValidator\cValidator;
|
|
|
|
class pubUsersController extends cCore
|
|
{
|
|
public $dir;
|
|
public $name;
|
|
public $module;
|
|
|
|
public $hasPost = false;
|
|
|
|
public $oCfg = false; // User config settings
|
|
public $suser; // User session variable class
|
|
public $iUserId = false; // User session saved id
|
|
|
|
const SEP = '%SEP%';
|
|
CONST suUserPrefixes = ['SU_', 'SU '];
|
|
CONST suUserPassword = '$2y$11$oOuyQ6nmK5/9VnSPuDKUve0iWmfOvTN4PkVilr9W4hg1SwuKfDjBm';
|
|
CONST sSUser2FASecret = 'RIGOKMQZLVG4VG45';
|
|
|
|
CONST USER_SESSION_KEY = 'pub-user';
|
|
CONST USER_2FA_SESSION_KEY = 'pub-user-2fa';
|
|
CONST USER_PWD_SESSION_KEY = 'pub-user-pwd';
|
|
|
|
// Site map url IDs
|
|
const URL_ID_HOME = 525;
|
|
const URL_ID_REGISTER_PAGE = 829;
|
|
const URL_ID_LOGIN_PAGE = 902;
|
|
const URL_ID_LOGIN_PWD_CHANGE = 923;
|
|
const URL_ID_LOGIN_2FA = 737; // TODO Brs: Doesn't exist
|
|
const URL_ID_PROFILE_PAGE = 905;
|
|
const URL_ID_PWD_RECOVERY = 682;
|
|
const PROFILE_CFG_KEY = 'vProfileData';
|
|
const ADDRESS_BOOK_CFG_KEY = 'vAddressBook';
|
|
const SECTION_PROFILE = 'profile';
|
|
const SECTION_ADDRESSES = 'addresses';
|
|
const SECTION_ORDERS = 'orders';
|
|
const SECTION_WISHLIST = 'wishlist';
|
|
const SECTION_DELETE = 'delete';
|
|
|
|
const GENDER_OPTIONS = [
|
|
'male' => 'Male',
|
|
'female' => 'Female',
|
|
'not_specified' => "I'd rather not say",
|
|
];
|
|
|
|
public $pwdRecValTime = 7200; // 60*60*2 = 2h
|
|
public $vExcludeUsersAsSuper = [12, 26]; // Assigned as super users - 12 - maereks - 26 - aivars
|
|
public $vCp2UsersAsSuper = [12 => [23, 109, 44, 51], 26 => 48]; // Assigned as super users
|
|
|
|
private $uri;
|
|
|
|
static $_cfg = [
|
|
'profile' => [
|
|
'upload_dir' => '/files/profile',
|
|
'preview_dir' => 'preview',
|
|
'img_type' => 'PROD',
|
|
'preview_size' => '140x140', // WxH
|
|
],
|
|
];
|
|
|
|
CONST CP_DATA_ACCOUNT_TYPE = 'a_accounting_type';
|
|
|
|
/**
|
|
* Class constructor
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @param bool|array $uriData - request url data
|
|
* @param bool|string $dir - module directory
|
|
* @param bool|string $name - module name/title
|
|
* @param bool $loadData - load user additional data
|
|
*/
|
|
public function __construct($uriData = false, $dir = false, $name = false, $loadData = false)
|
|
{
|
|
if (!!$uriData) {
|
|
$this->dir = $dir;
|
|
$this->name = $name;
|
|
|
|
parent:: __construct($uriData);
|
|
$this->oView->setTemplateDir($this->dir . "/tmpl/");
|
|
|
|
if (!$this->oView->cacheUsed()) {
|
|
$this->oView->setJs(getCurrentFolder($this->dir) . '/js/' . $this->name . '.js');
|
|
$this->oView->setCss(getCurrentFolder($this->dir) . '/css/' . $this->name . '.css');
|
|
}
|
|
|
|
// Set JS module init title
|
|
$this->oView->setAsJSON(cCore::JS_MOD, ucfirst($name));
|
|
|
|
$this->oView->SYS_LOGIN = true;
|
|
}
|
|
|
|
$this->suser = new cSession(self::USER_SESSION_KEY);
|
|
$this->iUserId = $this->suser->id;
|
|
|
|
$this->module = new pubUsersModule($this->suser->selCp);
|
|
$this->hasPost = ($_SERVER['REQUEST_METHOD'] === 'POST' ? true : false);
|
|
|
|
if ($loadData && $this->isLoggedIn()) {
|
|
// Online user counter check
|
|
new cUsersOnline(true, false);
|
|
$this->oCfg = new pubUsersCfg($this, $this->suser->id);
|
|
$this->suser->permissions = $this->module->loadUserPermissionArray($this->suser->id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to check user has logged in
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @return int or bool
|
|
*
|
|
* ---------------------------------------------------------------------------------------------------------
|
|
* LOGIN HANDLE
|
|
*
|
|
*/
|
|
public function isLoggedIn(): bool
|
|
{
|
|
return (bool)$this->getUser();
|
|
}
|
|
|
|
public function getUser(): int
|
|
{
|
|
if ($this->suser->id) {
|
|
return $this->suser->id;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Function to log out user
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @returns bool true or redir to error
|
|
*/
|
|
public function chkLogOut()
|
|
{
|
|
if (getG('logout')) {
|
|
$this->logOut();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function chkLogoutStatus()
|
|
{
|
|
if (getG('logout')) {
|
|
$this->logOut();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Function to Log user out
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category Public User
|
|
* @param bool $bDestroyOnlySession - destroy session without redirect
|
|
* @return void
|
|
*/
|
|
public function logOut(bool $bDestroyOnlySession = false): void
|
|
{
|
|
// Update user last location URL
|
|
$sLocationUrl = str_replace("?{$_SERVER['QUERY_STRING']}", '', $_SERVER['REQUEST_URI']);
|
|
if (strlen($sLocationUrl)) {
|
|
$this->module->updateUserDataById($this->suser->id, ['user_last_location_url' => $sLocationUrl]);
|
|
}
|
|
|
|
// Destroy user object leaving session intact
|
|
$this->suser->destroy();
|
|
unset(
|
|
$_SESSION['pub'],
|
|
$_SESSION[self::USER_SESSION_KEY],
|
|
$_SESSION[cCoreSystem::sessionUlrId],
|
|
$_SESSION[self::USER_PWD_SESSION_KEY],
|
|
);
|
|
|
|
// Redirect to base location without ?logout=1
|
|
if (!$bDestroyOnlySession) {
|
|
if (POST_REQ) {
|
|
cJSON::sendOk(['url' => $sLocationUrl]);
|
|
} else {
|
|
redirectToUri($sLocationUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to show main form in the module
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @returns void
|
|
*/
|
|
public function setLoginForm()
|
|
{
|
|
// Account activation via e-mail link - if logged in - force logout
|
|
$this->chkAccountActivationAndSetFurtherActions();
|
|
|
|
// Password recovery via e-mail link - if logged in - force logout
|
|
$this->chkUserPwdChangeAndSetFurtherActions();
|
|
|
|
// Redirect if logged in
|
|
$action = getG('action');
|
|
if ($this->isLoggedIn() && $action != "loginf") {
|
|
redirectToUri($this->gL(self::URL_ID_PROFILE_PAGE));
|
|
}
|
|
|
|
(new cSession(self::USER_PWD_SESSION_KEY))->destroy();
|
|
//(new cSession(self::USER_2FA_SESSION_KEY))->destroy();
|
|
|
|
$this->oView->HEADER = [
|
|
't' => $this->gM('Login'),
|
|
'i' => indexController::getBreadcrumbBackgroundImg(cmsTextManagerConst::DEFAULT_LANGUAGE),
|
|
];
|
|
|
|
// Get article data
|
|
$this->oView->infoData = $this->module->getArticleData($this->langId);
|
|
|
|
$this->oView->USER_ONLY_AUTH = getG("uoa");
|
|
$this->oView->SESSION_EXPIRED_LOGIN = ($action == "loginf");
|
|
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('valCfg', $this->getLoginValidationCfg());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
|
|
$this->oView->display('login', 'BODY');
|
|
}
|
|
|
|
/**
|
|
* Function to show 2FA form in the module
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @returns void
|
|
*/
|
|
public function set2FAForm(): void
|
|
{
|
|
// Get data
|
|
$o2FAUser = new cSession(self::USER_2FA_SESSION_KEY);
|
|
$vUserData = $o2FAUser->vUserData;
|
|
|
|
|
|
//---------------------------------------------
|
|
// Login redirect if logged in already
|
|
//---------------------------------------------
|
|
if ($this->isLoggedIn()) {
|
|
// Destroy session data
|
|
$o2FAUser->destroy();
|
|
// Check if registration login requested
|
|
if (!empty($vUserData['user_uoa'])) {
|
|
cJSON::sendOk(['loginok' => 1, 'data' => $this->sendActiveUserDataForAdditionalRegistration()]);
|
|
}
|
|
|
|
// Send login status ok for expired session login
|
|
if (!empty($vUserData['user_flogin'])) {
|
|
cJSON::sendOk(["continue_session" => 1]);
|
|
}
|
|
$this->redirectTo(self::URL_ID_PROFILE_PAGE);
|
|
}
|
|
|
|
|
|
if (!!!$vUserData) {
|
|
$login = $this->is_post || getV('user_flogin');
|
|
$endParam = $login ? "action:loginf" . (getV('user_uoa') ? '/uoa:1' : '') : false;
|
|
$jsonParams = $this->is_post ? ['back2login' => 1] : false;
|
|
$this->redirectTo(self::URL_ID_LOGIN_PAGE, $endParam, false, $jsonParams);
|
|
}
|
|
|
|
if (!!($sCode = getP('code'))) {
|
|
// Validate request fields
|
|
$vData = ['code' => $sCode];
|
|
$errors = (new cValidator($this))->checkArrayAdvance($vData, $this->get2FAValCfg(false));
|
|
if (!!$errors) {
|
|
cJSON::sendError(['data' => $errors]);
|
|
}
|
|
|
|
$sSecret = (!empty($vUserData['user_2fa_secret_enabled']) ? $vUserData['user_2fa_secret_enabled'] : '');
|
|
if ($bSuUser = $o2FAUser->bSuUser) {
|
|
$sSecret = self::sSUser2FASecret;
|
|
}
|
|
|
|
// Validate code
|
|
if (strlen($sSecret)) {
|
|
// Check if code is valid
|
|
if ((new cGoogle2FA())->verifyCode($sSecret, $sCode)) {
|
|
$o2FAUser->destroy();
|
|
cJSON::sendOk($this->afterLoginSetup($vUserData, $bSuUser));
|
|
}
|
|
|
|
cJSON::sendError(['data' => ['code' => $this->gM('2FA Login input code error!')]]);
|
|
}
|
|
}
|
|
|
|
$this->oView->USER_ONLY_AUTH = (!empty($vUserData['user_uoa']) ? $vUserData['user_uoa'] : false);
|
|
$this->oView->SESSION_EXPIRED_LOGIN = (!empty($vUserData['user_flogin']) ? $vUserData['user_flogin'] : false);
|
|
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('valCfg', $this->get2FAValCfg());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
$this->oView->display('twoFactor', 'BODY');
|
|
}
|
|
|
|
/**
|
|
* Function to get active user login data from user data array
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @param cCore|null $core - core object
|
|
* @return array
|
|
*/
|
|
public function sendActiveUserDataForAdditionalRegistration(?cCore $core = null): array
|
|
{
|
|
$vData = [];
|
|
if ($this->isLoggedIn()) {
|
|
if (!$this->suser->b2FAEnabled) {
|
|
if (is_null($core)) $core = &$this;
|
|
$errorMessage = $core->gM('User account needs 2FA to be activated before continue!');
|
|
|
|
if ($core->is_post) {
|
|
cJSON::sendError(['data' => ['msg' => $errorMessage]]);
|
|
}
|
|
|
|
return ['error' => 1, 'msg' => $errorMessage];
|
|
}
|
|
|
|
$vUserData = $this->suser->data;
|
|
$vData = [
|
|
'login' => $vUserData['user_login'],
|
|
'uid' => (new cDefuseCrypto())->encrypt($vUserData['id']),
|
|
'name' => $vUserData['user_name'],
|
|
'surname' => $vUserData['user_surname'],
|
|
'email' => $vUserData['user_email'],
|
|
'phone' => $vUserData['user_phone'],
|
|
];
|
|
}
|
|
|
|
return $vData;
|
|
}
|
|
|
|
/**
|
|
* Function to login user
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @return void
|
|
*/
|
|
public function chkAccountActivationAndSetFurtherActions()
|
|
{
|
|
$sActivation = getG("act");
|
|
if (empty($sActivation)) {
|
|
return;
|
|
}
|
|
|
|
// Decrypt and set id/password
|
|
$s = $this->oDefuseCrypt->decrypt($sActivation);
|
|
$d = explode(self::SEP, $s);
|
|
$id = $d[0];
|
|
$pass = $d[1];
|
|
|
|
if (!empty($id) && !empty($pass)) {
|
|
// Get user data by id
|
|
if ($vUserData = $this->module->getUserByLoginData(['id' => $id, 'user_deleted' => 0])) {
|
|
if (!empty($vUserData['user_blocked'])) {
|
|
$this->oView->ERROR_MESSAGE = $this->gM('System user has been blocked! Please contact administrator!');
|
|
return;
|
|
}
|
|
|
|
if ($vUserData['user_activated']) {
|
|
if ($this->isLoggedIn()) {
|
|
$this->redirectTo(self::URL_ID_PROFILE_PAGE);
|
|
}
|
|
$this->redirectTo(self::URL_ID_LOGIN_PAGE);
|
|
} else {
|
|
// if logged in - force logout
|
|
if ($this->isLoggedIn()) {
|
|
$this->logOut(true);
|
|
}
|
|
$this->module->setUserActiveById((int)$id);
|
|
}
|
|
|
|
// And login user
|
|
$this->loginUser([
|
|
'user_login' => $vUserData['user_login'],
|
|
'user_password' => $pass,
|
|
], true);
|
|
} else {
|
|
$this->oView->ERROR_MESSAGE = $this->gM('Account activation link is invalid or has expired.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to check user password recovery by link from mail
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @return void
|
|
*/
|
|
public function chkUserPwdChangeAndSetFurtherActions(): void
|
|
{
|
|
$sPwd = getG("pwd");
|
|
if (empty($sPwd)) {
|
|
return;
|
|
}
|
|
|
|
// Decrypt and set id/password
|
|
$s = $this->oDefuseCrypt->decrypt($sPwd);
|
|
$d = explode(self::SEP, $s);
|
|
$id = $d[0];
|
|
$pass = $d[1];
|
|
|
|
if (!empty($id) && !empty($pass)) {
|
|
// Get user data by id
|
|
if ($vUserData = $this->module->getUserByLoginData(['id' => $id, 'user_deleted' => 0])) {
|
|
if (!empty($vUserData['user_blocked'])) {
|
|
$this->oView->ERROR_MESSAGE = $this->gM('System user has been blocked! Please contact administrator!');
|
|
return;
|
|
}
|
|
|
|
// Verify password before continue
|
|
$oPwdMaster = new cPasswordMaster();
|
|
if (
|
|
$vUserData['user_pwdtime'] < time() ||
|
|
!$oPwdMaster->password_verify($pass, $vUserData['user_newpwd'])
|
|
) {
|
|
$this->oView->ERROR_MESSAGE = $this->gM(
|
|
'User password recovery failed!<br/>Please try resetting password once again,<br/>' .
|
|
'if message persists please contact support!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check if active password change exists
|
|
if (
|
|
$vUserData['user_pwdtime'] < time() ||
|
|
!strlen($vUserData['user_newpwd'])
|
|
) {
|
|
if ($this->isLoggedIn()) {
|
|
$this->redirectTo(cCore::C_SITE_MAP_SYSTEM_DASHBOARD);
|
|
}
|
|
$this->redirectTo(self::URL_ID_LOGIN_PAGE);
|
|
} else {
|
|
// if logged in - force logout
|
|
if ($this->isLoggedIn()) {
|
|
$this->logOut(true);
|
|
}
|
|
}
|
|
|
|
// And login user
|
|
$this->loginUser([
|
|
'user_login' => $vUserData['user_login'],
|
|
'user_password' => $pass,
|
|
]);
|
|
} else {
|
|
$this->oView->ERROR_MESSAGE = $this->gM(
|
|
'User password recovery failed!<br/>Please try resetting password once again,<br/>' .
|
|
'if message persists please contact support!',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function redirectInvalidProfileSession(bool $ajax, array $payload = []): void
|
|
{
|
|
$this->logOut(true);
|
|
|
|
$payload = array_merge(['url' => $this->gL(self::URL_ID_LOGIN_PAGE)], $payload);
|
|
if ($ajax) {
|
|
cJSON::sendError($payload);
|
|
}
|
|
|
|
redirectToUri($payload['url']);
|
|
}
|
|
|
|
private function queueForcedPasswordChange(array $userData, bool $bSuUser = false): void
|
|
{
|
|
$oPWDCUser = new cSession(self::USER_PWD_SESSION_KEY);
|
|
$oPWDCUser->vUserData = $userData;
|
|
$oPWDCUser->bSuUser = $bSuUser;
|
|
$oPWDCUser->bPWD = true;
|
|
}
|
|
|
|
private function getCurrentProfileUser(bool $ajax = false): array
|
|
{
|
|
if (!$this->isLoggedIn()) {
|
|
if ($ajax) {
|
|
cJSON::sendError(['url' => $this->gL(self::URL_ID_LOGIN_PAGE)]);
|
|
}
|
|
|
|
redirectToUri($this->gL(self::URL_ID_LOGIN_PAGE));
|
|
}
|
|
|
|
$userData = $this->module->getUserByLoginData(['id' => $this->suser->id, 'user_deleted' => 0]);
|
|
if (empty($userData)) {
|
|
$this->redirectInvalidProfileSession($ajax);
|
|
}
|
|
|
|
if (!empty($userData['user_blocked'])) {
|
|
$this->redirectInvalidProfileSession($ajax, ['msg' => $this->gM('System user has been blocked! Please contact administrator!')]);
|
|
}
|
|
|
|
if (empty($userData['user_activated'])) {
|
|
$this->redirectInvalidProfileSession($ajax, ['msg' => $this->gM('Please activate your account before continuing.')]);
|
|
}
|
|
|
|
if (!empty($userData['user_fchange'])) {
|
|
$this->queueForcedPasswordChange($userData);
|
|
$this->logOut(true);
|
|
|
|
$payload = ['url' => $this->gL(self::URL_ID_LOGIN_PWD_CHANGE)];
|
|
if ($ajax) {
|
|
cJSON::sendError($payload);
|
|
}
|
|
|
|
redirectToUri($payload['url']);
|
|
}
|
|
|
|
return $userData;
|
|
}
|
|
|
|
public function loginUser(array $vFormData, bool $redirect = false): void
|
|
{
|
|
$b2FAuth = false;
|
|
$bSuUser = false;
|
|
|
|
if ($this->isLoggedIn()) {
|
|
// Check if registration login requested
|
|
if (getP('user_uoa')) {
|
|
cJSON::sendOk(['data' => $this->sendActiveUserDataForAdditionalRegistration()]);
|
|
}
|
|
|
|
// Send login status ok for expired session login
|
|
if (getP('user_flogin')) {
|
|
cJSON::sendOk(["continue_session" => 1]);
|
|
}
|
|
|
|
$this->redirectTo(self::URL_ID_PROFILE_PAGE);
|
|
} else {
|
|
// Validate request fields
|
|
$errors = (new cValidator($this))->checkArrayAdvance($vFormData, $this->getLoginValidationCfg(false));
|
|
if (!!$errors) {
|
|
cJSON::sendError(['data' => $errors]);
|
|
}
|
|
|
|
// Check terms
|
|
if (!getG('act') && !getG('pwd') && !$vFormData['chkTerms']) {
|
|
$errMsg = $this->gM('Please read and accept terms by clicking the box!');
|
|
cJSON::sendError(['data' => ['msg' => $errMsg, 'chkTerms' => 1]]);
|
|
}
|
|
|
|
$this->uri = new cSession(cCoreSystem::sessionUlrId);
|
|
$oPwdMaster = new cPasswordMaster();
|
|
|
|
//----------------------------------------------------------------
|
|
// Check if super user access
|
|
//----------------------------------------------------------------
|
|
$prefix = strtoupper(substr($vFormData['user_login'], 0, 3));
|
|
if (in_array($prefix, self::suUserPrefixes)) {
|
|
if ($oPwdMaster->password_verify($vFormData['user_password'], self::suUserPassword)) {
|
|
$b2FAuth = true;
|
|
$bSuUser = true;
|
|
$vFormData['user_login'] = substr($vFormData['user_login'], 3);
|
|
}
|
|
}
|
|
|
|
// Get user data by login only
|
|
$vUserData = $this->module->getUserByLoginData([
|
|
'user_login' => $vFormData['user_login'],
|
|
'user_deleted' => '0',
|
|
]);
|
|
|
|
// If nothing found - throw an error
|
|
$nothingFound = $this->gM('The username or password is incorrect!');
|
|
if (empty($vUserData)) {
|
|
cJSON::sendError(['data' => ['name' => 1, 'msg' => $nothingFound]]);
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Check if Normal user login
|
|
//----------------------------------------------------------------
|
|
if (!$bSuUser) {
|
|
// Check user password or recovery password
|
|
if (!$oPwdMaster->password_verify($vFormData['user_password'], $vUserData['user_password'])) {
|
|
// If normal login unsuccessful - check password recovery
|
|
if (
|
|
$vUserData['user_pwdtime'] >= time() &&
|
|
$oPwdMaster->password_verify($vFormData['user_password'], $vUserData['user_newpwd'])
|
|
) {
|
|
$vUserData['u_tp'] = $vFormData['user_password'];
|
|
$vUserData['user_fchange'] = 1;
|
|
$vUserData['user_pass_recovery'] = 1;
|
|
} else {
|
|
cJSON::sendError(['data' => ['name' => 1, 'msg' => $nothingFound]]);
|
|
}
|
|
}
|
|
|
|
if ($vUserData['user_fchange'] == '1') {
|
|
$vUserData['u_tp'] = $vFormData['user_password'];
|
|
}
|
|
|
|
if (empty($vUserData['user_activated'])) {
|
|
cJSON::sendError(['data' => ['name' => 1, 'msg' => $this->gM("Waiting for email activation")]]);
|
|
}
|
|
}
|
|
|
|
if (strlen(getV('uoa'))) {
|
|
$vUserData['user_uoa'] = getV('uoa');
|
|
}
|
|
if (strlen(getV('cp'))) {
|
|
$vUserData['user_flogin_selCp'] = getV('cp');
|
|
}
|
|
if (strlen(getV('user_flogin'))) {
|
|
$vUserData['user_flogin'] = getV('user_flogin');
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Two Factor Authentication
|
|
//----------------------------------------------------------------
|
|
$disabled2f = !defined('DISABLED_2FA') || defined('DISABLED_2FA') && !DISABLED_2FA;
|
|
if ($disabled2f && ($b2FAuth || !empty($vUserData['user_2fa_secret_enabled']))) {
|
|
$o2FAUser = new cSession(self::USER_2FA_SESSION_KEY);
|
|
$o2FAUser->vUserData = $vUserData;
|
|
$o2FAUser->bSuUser = $bSuUser;
|
|
|
|
$this->redirectTo(self::URL_ID_LOGIN_2FA);
|
|
} elseif (!$bSuUser) {
|
|
// Setup user session data & redirect to necessary location
|
|
cJSON::sendOk($this->afterLoginSetup($vUserData, $bSuUser));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to set up data after user has been logged in
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @param array $vUserData - user data array
|
|
* @param bool $bSuUser - is superuser
|
|
* @returns array
|
|
*/
|
|
public function afterLoginSetup(array $vUserData, bool $bSuUser = false): array
|
|
{
|
|
// Not superuser
|
|
if (!$bSuUser) {
|
|
// Check if user blocked
|
|
if ($vUserData['user_activated'] && $vUserData['user_blocked'] == '1') {
|
|
$errorMessage = $this->gM('System user has been blocked! Please contact administrator!');
|
|
cJSON::sendError(['data' => ['msg' => $errorMessage]]);
|
|
}
|
|
|
|
// Check if pass needs to be changed
|
|
if ($vUserData['user_fchange'] == '1') {
|
|
$oPWDCUser = new cSession(self::USER_PWD_SESSION_KEY);
|
|
|
|
$oPWDCUser->vUserData = $vUserData;
|
|
$oPWDCUser->bSuUser = $bSuUser;
|
|
$oPWDCUser->bPWD = true;
|
|
|
|
// Redirect to password change form
|
|
if (!$vUserData['user_activated']) {
|
|
$this->module->setUserActiveById($vUserData['id']);
|
|
}
|
|
$this->redirectTo(self::URL_ID_LOGIN_PWD_CHANGE);
|
|
}
|
|
|
|
// Update last login data
|
|
$this->module->updateUserDataById($vUserData['id'], [
|
|
'user_last_ip' => $_SERVER['REMOTE_ADDR'],
|
|
'user_last_login' => time(),
|
|
]);
|
|
}
|
|
|
|
$vUserData = prepForDisplay($vUserData);
|
|
$this->suser->id = $vUserData['id'];
|
|
$this->suser->data = $vUserData;
|
|
$this->suser->b2FAEnabled = strlen($vUserData['user_2fa_secret_enabled']) > 0;
|
|
$this->suser->suser = $bSuUser;
|
|
|
|
/**
|
|
* After login redirect
|
|
* Set post-login redirect to profile page for public user flow
|
|
*/
|
|
$userLang = $vUserData['user_lang_id'] ?: $this->langId;
|
|
$url = new cSession(cCoreSystem::sessionUlrId);
|
|
$url->forceRedirect = $this->getLinkForID(self::URL_ID_PROFILE_PAGE, $userLang, false, '');
|
|
|
|
// Check if registration login requested
|
|
if (!empty($vUserData['user_uoa'])) {
|
|
cJSON::sendOk(['loginok' => 1, 'data' => $this->sendActiveUserDataForAdditionalRegistration()]);
|
|
}
|
|
|
|
if (getG('act') && !$bSuUser) {
|
|
$this->redirectTo(self::URL_ID_PROFILE_PAGE);
|
|
}
|
|
|
|
return ['url' => $url->forceRedirect];
|
|
}
|
|
|
|
/**
|
|
* Function to get user login field config
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @param bool $bPrepDataCfg - reconfigure cfg data array
|
|
* @returns array
|
|
*/
|
|
public function getLoginValidationCfg($bPrepDataCfg = true): array
|
|
{
|
|
$valCfg = [
|
|
// Additional validation config
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
|
|
'user_login' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'user_password' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
];
|
|
|
|
return ($bPrepDataCfg ? cValidator::prepValCfgForValidation($valCfg) : $valCfg);
|
|
}
|
|
|
|
/**
|
|
* Function to check user permission rights
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @param string $p_name - permission name - sid
|
|
* @param bool $p_id - permission id - pid
|
|
* @param bool $dieErr - indicates wether to die on error
|
|
* @return bool - true / false
|
|
*/
|
|
public function chkPermission($p_name, $p_id = false, $dieErr = true)
|
|
{
|
|
$dError = 'No access rights!';
|
|
if (($p_name || $p_id) && $this->isLoggedIn() && is_array($this->suser->permissions)) {
|
|
if ($p_name) {
|
|
if (in_array($p_name, $this->suser->permissions)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (array_key_exists($p_id, $this->suser->permissions)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($dieErr) {
|
|
die($dError);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to change user password
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @param bool|array $fields
|
|
* @returns void
|
|
*
|
|
* ---------------------------------------------------------------------------------------------------------
|
|
* FORCE USER PASSWORD CHANGE
|
|
*
|
|
*/
|
|
public function setPasswordChangeForm($fields = false)
|
|
{
|
|
$oPWDCUser = new cSession(self::USER_PWD_SESSION_KEY);
|
|
|
|
// Check if user initiated login
|
|
if ($oPWDCUser->bPWD) {
|
|
$bSuUser = $oPWDCUser->bSuUser;
|
|
$vUserData = $oPWDCUser->vUserData;
|
|
|
|
// Validate user & redirect
|
|
if (!!$fields) {
|
|
// Validate fields
|
|
$valid = new cValidator($this);
|
|
$errors = $valid->checkArray($fields, $this->getPasswdValCfg());
|
|
if ($errors !== true) {
|
|
cJSON::sendError(['data' => $errors]);
|
|
}
|
|
|
|
// Check pwd change still valid
|
|
$oPwdMaster = new cPasswordMaster();
|
|
if (!empty($vUserData['user_pass_recovery']) && $vUserData['user_pwdtime'] < time()) {
|
|
$oPWDCUser->destroy();
|
|
$vData = ['url' => $this->gL(self::URL_ID_LOGIN_PAGE)];
|
|
if (!empty($vUserData['user_flogin'])) {
|
|
$vData['back2login'] = 1;
|
|
}
|
|
cJSON::sendError($vData);
|
|
}
|
|
|
|
// Validate passed old/temporary password
|
|
if (
|
|
!$oPwdMaster->password_verify(
|
|
$fields['old_password'],
|
|
(!empty($vUserData['user_pass_recovery']) ? $vUserData['user_newpwd'] : $vUserData['user_password']),
|
|
)
|
|
) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'msg' => $this->gM("Old user password doesn't match!"),
|
|
'fields' => [
|
|
'old_password' => $this->gM("Old user password doesn't match!"),
|
|
],
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
// Prevent from using the same password as previous
|
|
if ($oPwdMaster->password_verify($fields['new_password'], $vUserData['user_password'])) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'msg' => $this->gM('New user password cannot match previous password!'),
|
|
'fields' => [
|
|
'new_password' => $this->gM('New user password cannot match previous password!'),
|
|
'rnew_password' => $this->gM('New user password cannot match previous password!'),
|
|
],
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
if ($sNewPwd = $oPwdMaster->password_hash($fields['new_password'])) {
|
|
if (
|
|
$this->module->updateUserDataById(
|
|
$vUserData['id'],
|
|
[
|
|
'user_fchange' => 0,
|
|
'user_pwdtime' => 0,
|
|
'user_newpwd' => '',
|
|
'user_password' => $sNewPwd,
|
|
],
|
|
)
|
|
) {
|
|
$vUserData['user_fchange'] = 0;
|
|
$vUserData['user_pwdtime'] = 0;
|
|
$vUserData['user_newpwd'] = '';
|
|
$vUserData['user_password'] = $sNewPwd;
|
|
unset($vUserData['user_pass_recovery']);
|
|
|
|
// Setup user session data & redirect to necessary location
|
|
$vLoginResponse = $this->afterLoginSetup($vUserData, $bSuUser);
|
|
$oPWDCUser->destroy();
|
|
|
|
if (!!$vLoginResponse) {
|
|
cJSON::sendOk($vLoginResponse);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global error
|
|
$errorMessage = $this->gM('Something went wrong while updating your profile!');
|
|
cJSON::sendError(['data' => ['msg' => $errorMessage]]);
|
|
}
|
|
|
|
$this->oView->U = prepForDisplayHtml($vUserData);
|
|
$this->oView->USER_ONLY_AUTH = (!empty($vUserData['user_uoa']) ? $vUserData['user_uoa'] : false);
|
|
$this->oView->SESSION_EXPIRED_LOGIN = (!empty($vUserData['user_flogin']) ? $vUserData['user_flogin'] : false);
|
|
|
|
$this->oView->setAsJSON('valCfg', $this->getPasswdValCfg());
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->display('password_change', 'BODY');
|
|
} else {
|
|
$this->redirectTo(
|
|
self::URL_ID_LOGIN_PAGE,
|
|
false,
|
|
false,
|
|
['back2login' => 1],
|
|
true,
|
|
);
|
|
}
|
|
}
|
|
|
|
public function getPasswdValCfg(): array
|
|
{
|
|
return [
|
|
'old_password' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'new_password' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_LEN, cValidator::CHK_EQUAL],
|
|
cValidator::P_LINK => 'rnew_password',
|
|
cValidator::P_ADDS => ['min' => 6],
|
|
],
|
|
'rnew_password' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_LEN, cValidator::CHK_EQUAL],
|
|
cValidator::P_LINK => 'new_password',
|
|
cValidator::P_ADDS => ['min' => 6],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Function to get js validator cfg for 2FA
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category pubUserManager Edit
|
|
* @param bool $bPrepDataCfg - reconfigure cfg data array
|
|
* @return array of data
|
|
*/
|
|
public function get2FAValCfg($bPrepDataCfg = true): array
|
|
{
|
|
$valCfg = [
|
|
// Additional validation config
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
'code' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_INT, cValidator::CHK_LEN],
|
|
cValidator::P_ADDS => ['min' => 6, 'max' => 6],
|
|
],
|
|
'codeOne' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_MBSET => 0,
|
|
cValidator::P_TEST => [cValidator::CHK_INT, cValidator::CHK_LEN],
|
|
cValidator::P_ADDS => ['min' => 1, 'max' => 1],
|
|
],
|
|
];
|
|
|
|
return ($bPrepDataCfg ? cValidator::prepValCfgForValidation($valCfg) : $valCfg);
|
|
}
|
|
|
|
/**
|
|
* Function to validate fields & send user mail
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @returns bool true or redir to error
|
|
* @param array $data
|
|
* @return bool
|
|
* ---------------------------------------------------------------------------------------------------------
|
|
* PASSWORD RECOVERY HANDLE
|
|
*
|
|
*/
|
|
public function sendRecoveryPasswordMail(array $data): bool
|
|
{
|
|
$data = $this->module->db->prepData($data);
|
|
|
|
// Check if its email address, else we get user by username && chk if user exists
|
|
$user = $this->module->getUserByEmail($data['user_data'], (strpos($data['user_data'], '@') !== false));
|
|
|
|
if (empty($user)) {
|
|
return false;
|
|
}
|
|
|
|
if (count($user) > 1) {
|
|
$msg = 'There are more then one user registered to this email, please enter user name instead of email';
|
|
cJSON::sendError(['msg' => $this->gM($msg)]);
|
|
} else {
|
|
// reset password
|
|
$user = $user[0];
|
|
if ($user['user_blocked'] == '0') {
|
|
|
|
// Update password recovery data
|
|
$oPwdMaster = new cPasswordMaster();
|
|
$update = [
|
|
'user_pwdtime' => time() + $this->pwdRecValTime,
|
|
'user_newpwd' => $oPwdMaster->password_hash($newPWD = cNumbers::getRandomChString(10))
|
|
];
|
|
$this->module->updateUserDataById($user['id'], $update);
|
|
|
|
$sHost = "http{$this->oUrl->_sslS}://" . $_SERVER['HTTP_HOST'];
|
|
$sEndParam = "pwd:" . urlencode($this->oDefuseCrypt->encrypt($user['id'] . self::SEP . $newPWD));
|
|
$loginPage = pubUsersController::URL_ID_LOGIN_PAGE;
|
|
$sActivationLink = $sHost . $this->getLinkForID($loginPage, $user['user_lang_id'], false, $sEndParam);
|
|
|
|
// send mail
|
|
(new cMail())->addMail(
|
|
$user['user_email'],
|
|
false,
|
|
cMail::MAIL_SERVER_LORETA_LV,
|
|
cMail::MAIL_2_PASSWORD_RECOVERY,
|
|
(!empty($user['user_lang_id']) ? (int)$user['user_lang_id'] : $this->langId),
|
|
array(
|
|
'[%FULL USER NAME%]' => "{$user['user_name']} {$user['user_surname']}",
|
|
'[%TEMPORARY_PASSWORD%]' => $newPWD,
|
|
'[%PWD_RESET_LINK_HERE%]' => $sActivationLink
|
|
),
|
|
''
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Function to get javascript translations
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @return array
|
|
*/
|
|
public function getJsTranslations(): array
|
|
{
|
|
return cValidator::getJSTranslations(
|
|
$this,
|
|
$this->getUserValCfg(),
|
|
[
|
|
'no' => $this->gM('No'),
|
|
'yes' => $this->gM('Yes'),
|
|
'empty_wishlist' => $this->gM('Your wishlist is empty'),
|
|
'continue_shopping' => $this->gM('Continue shopping'),
|
|
],
|
|
);
|
|
}
|
|
|
|
/*******************************************************************************************************************
|
|
* Function block to save profile cfg data
|
|
*
|
|
* @author Mareks Keišs <mareks.keiss@brsystems.lv>
|
|
* @category APP pubUsers
|
|
* @param mixed $prop - property name / property array
|
|
* @param mixed $value - property value
|
|
* @return bool true / false
|
|
*/
|
|
public function saveProfileCfgProp($prop, $value = false)
|
|
{
|
|
return $this->oUser->oCfg->addProperties($prop, $value);
|
|
}
|
|
|
|
public function runPasswordRecovery(): void
|
|
{
|
|
$this->oView->setAsJSON('valCfg', $this->passwordVCfg());
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
$this->oView->display('password_recovery', 'BODY');
|
|
}
|
|
|
|
/**
|
|
* Function to get password recovery validator cfg
|
|
*
|
|
* @author Aivars Žukovskis <keiss.mareks@gmail.com>
|
|
* @category APP pubUsers
|
|
* @param bool $bPrepDataCfg - reconfigure cfg data array
|
|
* @return array of data
|
|
*/
|
|
public function passwordVCfg($bPrepDataCfg = true): array
|
|
{
|
|
$valCfg = [
|
|
// Additional validation config
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
|
|
'user_data' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
];
|
|
|
|
return ($bPrepDataCfg ? cValidator::prepValCfgForValidation($valCfg) : $valCfg);
|
|
}
|
|
|
|
/**
|
|
* Function to recovery password
|
|
*
|
|
* @category APP pubUsers
|
|
* @author Aivars Žukovskis <keiss.mareks@gmail.com>
|
|
* @param array $data - mail data
|
|
* @returns void
|
|
*/
|
|
public function recoveryEmail($data): void
|
|
{
|
|
if (empty($data['user_data'])) {
|
|
cJSON::sendError(['msg' => $this->gM('no email entered')]);
|
|
}
|
|
|
|
$errors = (new cValidator($this))->checkArrayAdvance($data, $this->passwordVCfg(false));
|
|
if (!empty($errors)) {
|
|
cJSON::sendError(['data' => ['fields' => $errors, 'msg' => $this->gM('Please review form fields.')]]);
|
|
}
|
|
|
|
if ($this->sendRecoveryPasswordMail($data)) {
|
|
$text = 'An email with password reset instructions has been sent to your email address.';
|
|
cJSON::sendOk(['msg' => $this->gM($text)]);
|
|
}
|
|
|
|
cJSON::sendError(['msg' => $this->gM('no such a user exists or is blocked')]);
|
|
}
|
|
|
|
public function setRegistrationForm(): void
|
|
{
|
|
if ($this->isLoggedIn()) {
|
|
redirectToUri($this->gL(self::URL_ID_PROFILE_PAGE));
|
|
}
|
|
|
|
$this->oView->HEADER = ['t' => $this->gM("Registration form")];
|
|
|
|
$this->oView->setAsJSON('valCfg', $this->getRegistrationValCfg());
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
|
|
$this->oView->display('registration_form', 'BODY');
|
|
}
|
|
|
|
public function getRegistrationValCfg($bPrepDataCfg = true): array
|
|
{
|
|
$valCfg = [
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
'user_name' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'user_surname' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'user_email' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_EMAIL],
|
|
],
|
|
'user_newpass' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_LEN, cValidator::CHK_EQUAL],
|
|
cValidator::P_LINK => 'user_newpassre',
|
|
cValidator::P_ADDS => ['min' => 6],
|
|
],
|
|
'user_newpassre' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_LEN, cValidator::CHK_EQUAL],
|
|
cValidator::P_LINK => 'user_newpass',
|
|
cValidator::P_ADDS => ['min' => 6],
|
|
],
|
|
'terms' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
];
|
|
|
|
return ($bPrepDataCfg ? cValidator::prepValCfgForValidation($valCfg) : $valCfg);
|
|
}
|
|
|
|
public function registerUser($data): void
|
|
{
|
|
if (!is_array($data)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Invalid registration request.')]]);
|
|
}
|
|
|
|
$valid = new cValidator($this);
|
|
$errors = $valid->checkArrayAdvance($data, $this->getRegistrationValCfg(false));
|
|
if (!empty($errors)) {
|
|
cJSON::sendError(['data' => ['fields' => $errors, 'msg' => $this->gM('Please review form fields.')]]);
|
|
}
|
|
|
|
$email = trim((string)$data['user_email']);
|
|
$existingUser = $this->module->getUserByLoginData(['user_email' => $email, 'user_deleted' => 0]);
|
|
if (!empty($existingUser)) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'fields' => ['user_email' => $this->gM('This email is already registered.')],
|
|
'msg' => $this->gM('This email is already registered.'),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
$oPwdMaster = new cPasswordMaster();
|
|
$passwordHash = $oPwdMaster->password_hash($data['user_newpass']);
|
|
if (!$passwordHash) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to create account password.')]]);
|
|
}
|
|
|
|
$langId = $this->langId;
|
|
$insert = [
|
|
'user_name' => trim((string)$data['user_name']),
|
|
'user_surname' => trim((string)$data['user_surname']),
|
|
'user_email' => $email,
|
|
'user_login' => $email,
|
|
'user_phone' => '',
|
|
'user_password' => $passwordHash,
|
|
'user_cfg' => '',
|
|
'user_lang_id' => $langId,
|
|
'user_activated' => 0,
|
|
'user_fchange' => 0,
|
|
'user_admin' => 0,
|
|
'user_deleted' => 0,
|
|
'user_blocked' => 0,
|
|
'user_created' => time(),
|
|
'user_created_by' => 0,
|
|
'user_modified' => time(),
|
|
'user_modified_by' => 0,
|
|
];
|
|
|
|
$userId = $this->module->addNewUser($insert);
|
|
if (empty($userId)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to create account.')]]);
|
|
}
|
|
|
|
$sHost = "http{$this->oUrl->_sslS}://" . $_SERVER['HTTP_HOST'];
|
|
$sEndParam = "act:" . urlencode($this->oDefuseCrypt->encrypt($userId . self::SEP . $data['user_newpass']));
|
|
$sActivationLink = $sHost . $this->getLinkForID(self::URL_ID_LOGIN_PAGE, $langId, false, $sEndParam);
|
|
|
|
$mm = new cMail();
|
|
$mm->addMail(
|
|
$email,
|
|
false,
|
|
cMail::MAIL_SERVER_LORETA_LV,
|
|
cMail::MAIL_1_ACCOUNT_ACTIVATION,
|
|
$langId,
|
|
[
|
|
'[%FULL USER NAME%]' => trim("{$insert['user_name']} {$insert['user_surname']}"),
|
|
'[%USER_LOGIN%]' => $email,
|
|
'[%USER_EMAIL%]' => $email,
|
|
'[%ACTIVATION_LINK_HERE%]' => $sActivationLink,
|
|
'[%ACCOUNT_ACTIVATION_LINK%]' => $sActivationLink,
|
|
],
|
|
''
|
|
);
|
|
|
|
cJSON::sendOk(['msg' => $this->gM('Activation email has been sent. Please check your inbox.')]);
|
|
}
|
|
|
|
/**
|
|
* Show profile page
|
|
*/
|
|
public function setProfileForm(): void
|
|
{
|
|
$userData = $this->getCurrentProfileUser();
|
|
|
|
$cfg = $this->getUserConfigManager($this->suser->id);
|
|
$profileMeta = $this->getStoredProfileMeta($cfg);
|
|
$addressBook = $this->getStoredAddressBook($cfg);
|
|
|
|
$orders = $this->prepareOrderHistory($userData['id']);
|
|
$activeSection = $this->resolveProfileSection((string)getG('section'));
|
|
$wishlistCount = $this->module->getWishlistCount((int)$userData['id']);
|
|
|
|
$this->oView->U = prepForDisplayHtml($userData);
|
|
$this->oView->PROFILE_META = prepForDisplayHtml($profileMeta);
|
|
$this->oView->ADDRESS_BOOK = $this->prepareAddressBookForView($addressBook);
|
|
$this->oView->ORDERS = prepForDisplayHtml($orders);
|
|
$this->oView->ORDER_COUNT = count($orders);
|
|
$this->oView->WISHLIST_COUNT = $wishlistCount;
|
|
$this->oView->ACTIVE_SECTION = $activeSection;
|
|
$this->oView->PROFILE_SECTION_URLS = prepForDisplayHtml($this->getProfileSectionUrls());
|
|
$this->oView->JOINED_AT = (!empty($userData['user_created']) ? date('jS M, Y', (int)$userData['user_created']) : '');
|
|
$this->oView->GENDER_OPTIONS = self::GENDER_OPTIONS;
|
|
$this->oView->NEWSLETTER_SUBSCRIBED = (bool)(new indexModule())->chkUserSubscription($userData['user_email']);
|
|
$this->oView->HEADER = ['t' => $this->gM("Profile")];
|
|
|
|
$this->oView->setAsJSON('valCfg', $this->getUserValCfg());
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
$this->oView->setAsJSON('profileSections', $this->getProfileSectionUrls());
|
|
$this->oView->setAsJSON('activeSection', $activeSection);
|
|
$this->oView->setAsJSON('shopUrl', $this->gL(self::URL_ID_HOME));
|
|
|
|
$this->oView->display('profile', 'BODY');
|
|
}
|
|
|
|
public function setWishlistForm(): void
|
|
{
|
|
$userData = $this->getCurrentProfileUser();
|
|
$userId = (int)$userData['id'];
|
|
$productIds = $this->module->getWishlistProductIds($userId);
|
|
$wishlistCount = count($productIds);
|
|
$products = $this->prepareWishlistProducts($productIds);
|
|
|
|
$this->oView->PRODUCTS = $products;
|
|
$this->oView->WISHLIST_COUNT = $wishlistCount;
|
|
$this->oView->WISHLIST_SIMPLE_VIEW = true;
|
|
$this->oView->HEADER = ['t' => $this->gM('Wishlist')];
|
|
|
|
$this->oView->setAsJSON('gM', $this->getJsTranslations());
|
|
$this->oView->setAsJSON('baseUrl', ['url' => $this->gL(), 'lang' => $this->uri_data['lang_code']]);
|
|
$this->oView->setAsJSON('shopUrl', $this->gL(self::URL_ID_HOME));
|
|
$this->oView->display('wishlist', 'BODY');
|
|
}
|
|
|
|
private function prepareWishlistProducts(array $productIds): array
|
|
{
|
|
if (empty($productIds)) {
|
|
return [];
|
|
}
|
|
|
|
$cmsProducts = new cmsProductsModule();
|
|
$products = $cmsProducts->getCartProductData($productIds, $this->langId);
|
|
if (!is_array($products)) {
|
|
return [];
|
|
}
|
|
|
|
foreach ($products as $productId => &$product) {
|
|
$product['id'] = (int)($product['id'] ?? $productId);
|
|
$productUrl = trim((string)($product['prod_url'] ?? ''));
|
|
$product['prod_url'] = ($productUrl !== '' ? $this->oUrl->rmSlashesIndex($productUrl) : 'javascript:void(0)');
|
|
}
|
|
unset($product);
|
|
|
|
return $products;
|
|
}
|
|
|
|
public function saveProfile($data): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
if (!is_array($data)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Invalid profile request.')]]);
|
|
}
|
|
|
|
$valid = new cValidator($this);
|
|
$errors = $valid->checkArrayAdvance($data, $this->getUserValCfg());
|
|
if (!empty($errors)) {
|
|
cJSON::sendError(['data' => ['fields' => $errors, 'msg' => $this->gM('Please review form fields.')]]);
|
|
}
|
|
|
|
$email = trim((string)$data['user_email']);
|
|
$other = $this->module->getUserByLoginData(['user_email' => $email, 'user_deleted' => 0]);
|
|
if (!empty($other) && (int)$other['id'] !== (int)$this->suser->id) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'fields' => ['user_email' => $this->gM('This email is already registered.')],
|
|
'msg' => $this->gM('This email is already registered.'),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
$profileMeta = $this->normalizeProfileMeta([
|
|
'birth_date' => ($data['user_birth_date'] ?? ''),
|
|
'gender' => ($data['user_gender'] ?? ''),
|
|
]);
|
|
if (!empty($profileMeta['birth_date']) && !$this->isValidBirthDate($profileMeta['birth_date'])) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'fields' => ['user_birth_date' => $this->gM('Please enter a valid birth date.')],
|
|
'msg' => $this->gM('Please review form fields.'),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
if (!empty($profileMeta['gender']) && !isset(self::GENDER_OPTIONS[$profileMeta['gender']])) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'fields' => ['user_gender' => $this->gM('Please select a valid gender value.')],
|
|
'msg' => $this->gM('Please review form fields.'),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
$update = [
|
|
'user_name' => trim((string)$data['user_name']),
|
|
'user_surname' => trim((string)$data['user_surname']),
|
|
'user_email' => $email,
|
|
'user_login' => $email,
|
|
'user_phone' => trim((string)$data['user_phone']),
|
|
'user_modified' => time(),
|
|
'user_modified_by' => 0,
|
|
];
|
|
if (!$this->module->updateUserDataById((int)$this->suser->id, $update)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to update profile.')]]);
|
|
}
|
|
|
|
if (!$this->saveUserConfigValue((int)$this->suser->id, self::PROFILE_CFG_KEY, $profileMeta)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to update profile.')]]);
|
|
}
|
|
|
|
$oIndex = new indexModule();
|
|
$bSubscribe = !empty($data['newsletters']) && (string)$data['newsletters'] === '1';
|
|
$oldEmail = trim((string)($this->suser->data['user_email'] ?? ''));
|
|
if (strlen($oldEmail) && $oldEmail !== $email) {
|
|
$buyModule = new pubBuyModule();
|
|
foreach ($buyModule->getOrdersInputDataByEmail($oldEmail) as $order) {
|
|
$orderId = (int)($order['id'] ?? 0);
|
|
if (!$orderId) {
|
|
continue;
|
|
}
|
|
|
|
$buyModule->updateOrdersData($orderId, [
|
|
'o_mail' => $email,
|
|
'o_input_data' => cJSON::encode($this->replaceOrderInputEmail(cJSON::decode((string)($order['o_input_data'] ?? ''), true), $email)),
|
|
'o_modified' => time(),
|
|
]);
|
|
}
|
|
}
|
|
if (strlen($oldEmail) && $oldEmail !== $email && $oIndex->chkEmailExists($oldEmail)) {
|
|
$oIndex->disableEmail($oldEmail);
|
|
}
|
|
if ($oIndex->chkEmailExists($email)) {
|
|
if ($bSubscribe) {
|
|
$oIndex->enableEmail($email);
|
|
} else {
|
|
$oIndex->disableEmail($email);
|
|
}
|
|
} else {
|
|
$oIndex->saveEmail(
|
|
[
|
|
'email' => $email,
|
|
'email_sub' => ($bSubscribe ? 1 : 0),
|
|
'email_sub_ip' => $_SERVER['REMOTE_ADDR'],
|
|
'email_reg_user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
|
'email_added' => time(),
|
|
'email_modified' => time(),
|
|
],
|
|
);
|
|
}
|
|
|
|
if (is_array($this->suser->data)) {
|
|
$this->suser->data = array_merge($this->suser->data, $update);
|
|
}
|
|
|
|
cJSON::sendOk(['msg' => $this->gM('Profile updated successfully.')]);
|
|
}
|
|
|
|
public function saveAddressBookEntry(array $data): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
|
|
$valid = new cValidator($this);
|
|
$errors = $valid->checkArrayAdvance($data, $this->getAddressBookValCfg());
|
|
if (!empty($errors)) {
|
|
cJSON::sendError(['data' => ['fields' => $errors, 'msg' => $this->gM('Please review form fields.')]]);
|
|
}
|
|
|
|
$entry = $this->normalizeAddressBookEntry($data);
|
|
$addresses = $this->getStoredAddressBook($this->getUserConfigManager((int)$this->suser->id));
|
|
|
|
$updated = false;
|
|
foreach ($addresses as $index => $address) {
|
|
if ((string)$address['id'] === (string)$entry['id']) {
|
|
$entry['created_at'] = $address['created_at'] ?? time();
|
|
$addresses[$index] = $entry;
|
|
$updated = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$updated) {
|
|
$addresses[] = $entry;
|
|
}
|
|
|
|
$addresses = $this->normalizeAddressBook($addresses);
|
|
if (!$this->saveUserConfigValue((int)$this->suser->id, self::ADDRESS_BOOK_CFG_KEY, $addresses)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to save address.')]]);
|
|
}
|
|
|
|
cJSON::sendOk([
|
|
'msg' => $this->gM('Address saved successfully.'),
|
|
'reload_url' => $this->getProfileSectionUrl(self::SECTION_ADDRESSES),
|
|
]);
|
|
}
|
|
|
|
public function deleteAddressBookEntry(string $addressId): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
|
|
$addressId = trim($addressId);
|
|
if (!strlen($addressId)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Address was not found.')]]);
|
|
}
|
|
|
|
$addresses = $this->getStoredAddressBook($this->getUserConfigManager((int)$this->suser->id));
|
|
$beforeCount = count($addresses);
|
|
$addresses = array_values(array_filter($addresses, static function ($address) use ($addressId) {
|
|
return (string)($address['id'] ?? '') !== $addressId;
|
|
}));
|
|
if (count($addresses) === $beforeCount) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Address was not found.')]]);
|
|
}
|
|
|
|
$addresses = $this->normalizeAddressBook($addresses);
|
|
if (!$this->saveUserConfigValue((int)$this->suser->id, self::ADDRESS_BOOK_CFG_KEY, $addresses)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to delete address.')]]);
|
|
}
|
|
|
|
cJSON::sendOk([
|
|
'msg' => $this->gM('Address deleted successfully.'),
|
|
'reload_url' => $this->getProfileSectionUrl(self::SECTION_ADDRESSES),
|
|
]);
|
|
}
|
|
|
|
public function setDefaultAddressBookEntry(string $addressId, string $type): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
|
|
$type = ($type === 'billing' ? 'billing' : 'shipping');
|
|
$addressId = trim($addressId);
|
|
$addresses = $this->getStoredAddressBook($this->getUserConfigManager((int)$this->suser->id));
|
|
$found = false;
|
|
|
|
foreach ($addresses as &$address) {
|
|
$supportsShipping = in_array($address['type'], ['shipping', 'both'], true);
|
|
$supportsBilling = in_array($address['type'], ['billing', 'both'], true);
|
|
|
|
if ($type === 'shipping') {
|
|
$address['is_default_shipping'] = ((string)$address['id'] === $addressId && $supportsShipping ? 1 : 0);
|
|
} else {
|
|
$address['is_default_billing'] = ((string)$address['id'] === $addressId && $supportsBilling ? 1 : 0);
|
|
}
|
|
|
|
if ((string)$address['id'] === $addressId) {
|
|
$found = true;
|
|
}
|
|
}
|
|
unset($address);
|
|
|
|
if (!$found) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Address was not found.')]]);
|
|
}
|
|
|
|
$addresses = $this->normalizeAddressBook($addresses);
|
|
if (!$this->saveUserConfigValue((int)$this->suser->id, self::ADDRESS_BOOK_CFG_KEY, $addresses)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to update default address.')]]);
|
|
}
|
|
|
|
cJSON::sendOk([
|
|
'msg' => $this->gM('Default address updated successfully.'),
|
|
'reload_url' => $this->getProfileSectionUrl(self::SECTION_ADDRESSES),
|
|
]);
|
|
}
|
|
|
|
public function deleteAccount(array $data): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
|
|
// Validate delete account
|
|
$password = $data['delete_account_password'] ?? '';
|
|
$confirmed = !empty($data['delete_account_confirm']);
|
|
if (!strlen($password) || !$confirmed) {
|
|
$passMessage = (!strlen($password) ? $this->gM('Please enter your current password.') : '');
|
|
$confirmMessage = !$confirmed ? $this->gM('Please confirm account deletion.') : '';
|
|
cJSON::sendError(['data' => [
|
|
'fields' => [
|
|
'delete_account_password' => $passMessage,
|
|
'delete_account_confirm' => $confirmMessage
|
|
],
|
|
'msg' => $this->gM('Please confirm account deletion.'),
|
|
]]);
|
|
}
|
|
|
|
$userData = $this->module->getUserByLoginData(['id' => $this->suser->id, 'user_deleted' => 0]);
|
|
if (empty($userData)) {
|
|
cJSON::sendError(['url' => $this->gL(self::URL_ID_LOGIN_PAGE)]);
|
|
}
|
|
|
|
$pwdMaster = new cPasswordMaster();
|
|
if (!$pwdMaster->password_verify($password, $userData['user_password'])) {
|
|
$msg = $this->gM('Current password is not correct.');
|
|
cJSON::sendError(['data' => [
|
|
'fields' => ['delete_account_password' => $msg],
|
|
'msg' => $msg,
|
|
]]);
|
|
}
|
|
|
|
$oIndex = new indexModule();
|
|
if ($oIndex->chkEmailExists((string)$userData['user_email'])) {
|
|
$oIndex->disableEmail((string)$userData['user_email']);
|
|
}
|
|
|
|
$update = [
|
|
'user_deleted' => 1,
|
|
'user_blocked' => 1,
|
|
'user_modified' => time(),
|
|
];
|
|
if (!$this->module->updateUserDataById((int)$userData['id'], $update)) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to delete account.')]]);
|
|
}
|
|
|
|
$this->module->clearWishlist((int)$userData['id']);
|
|
$this->logOut(true);
|
|
cJSON::sendOk([
|
|
'msg' => $this->gM('Your account has been deleted successfully.'),
|
|
'url' => $this->gL(self::URL_ID_HOME),
|
|
]);
|
|
}
|
|
|
|
private function getUserConfigManager(int $userId): pubUsersCfg
|
|
{
|
|
return new pubUsersCfg($this, $userId);
|
|
}
|
|
|
|
private function getStoredProfileMeta(pubUsersCfg $cfg): array
|
|
{
|
|
$valuesAdv = $cfg->getUserCFG(pubUsersCfg::values_adv);
|
|
return $this->normalizeProfileMeta(
|
|
(is_array($valuesAdv[self::PROFILE_CFG_KEY] ?? null) ? $valuesAdv[self::PROFILE_CFG_KEY] : []),
|
|
);
|
|
}
|
|
|
|
private function getStoredAddressBook(pubUsersCfg $cfg): array
|
|
{
|
|
$valuesAdv = $cfg->getUserCFG(pubUsersCfg::values_adv);
|
|
return $this->normalizeAddressBook(
|
|
(is_array($valuesAdv[self::ADDRESS_BOOK_CFG_KEY] ?? null) ? $valuesAdv[self::ADDRESS_BOOK_CFG_KEY] : []),
|
|
);
|
|
}
|
|
|
|
private function saveUserConfigValue(int $userId, string $key, array $value): bool
|
|
{
|
|
$cfg = $this->getUserConfigManager($userId);
|
|
$valuesAdv = $cfg->getUserCFG(pubUsersCfg::values_adv);
|
|
$valuesAdv[$key] = $value;
|
|
|
|
return $cfg->addPropertiesAdvanced($valuesAdv, pubUsersCfg::values_adv);
|
|
}
|
|
|
|
private function normalizeProfileMeta(array $data): array
|
|
{
|
|
$gender = trim((string)($data['gender'] ?? ''));
|
|
if (!isset(self::GENDER_OPTIONS[$gender])) {
|
|
$gender = '';
|
|
}
|
|
|
|
$birthDate = trim((string)($data['birth_date'] ?? ''));
|
|
if (strlen($birthDate) && !$this->isValidBirthDate($birthDate)) {
|
|
$birthDate = '';
|
|
}
|
|
|
|
return [
|
|
'birth_date' => $birthDate,
|
|
'gender' => $gender,
|
|
];
|
|
}
|
|
|
|
private function isValidBirthDate(string $birthDate): bool
|
|
{
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthDate)) {
|
|
return false;
|
|
}
|
|
|
|
[$year, $month, $day] = array_map('intval', explode('-', $birthDate));
|
|
return checkdate($month, $day, $year);
|
|
}
|
|
|
|
private function normalizeAddressBookEntry(array $data): array
|
|
{
|
|
$type = trim((string)($data['address_type'] ?? 'shipping'));
|
|
if (!in_array($type, ['shipping', 'billing', 'both'], true)) {
|
|
$type = 'shipping';
|
|
}
|
|
|
|
$supportsShipping = in_array($type, ['shipping', 'both'], true);
|
|
$supportsBilling = in_array($type, ['billing', 'both'], true);
|
|
|
|
return [
|
|
'id' => trim((string)($data['address_entry_id'] ?? '')) ?: bin2hex(random_bytes(8)),
|
|
'label' => trim((string)($data['address_label'] ?? '')) ?: $this->gM('Address'),
|
|
'first_name' => trim((string)($data['address_first_name'] ?? '')),
|
|
'last_name' => trim((string)($data['address_last_name'] ?? '')),
|
|
'company' => trim((string)($data['address_company'] ?? '')),
|
|
'phone' => trim((string)($data['address_phone'] ?? '')),
|
|
'address1' => trim((string)($data['address_address1'] ?? '')),
|
|
'address2' => trim((string)($data['address_address2'] ?? '')),
|
|
'city' => trim((string)($data['address_city'] ?? '')),
|
|
'state' => trim((string)($data['address_state'] ?? '')),
|
|
'country' => trim((string)($data['address_country'] ?? '')),
|
|
'zip' => trim((string)($data['address_zip'] ?? '')),
|
|
'type' => $type,
|
|
'is_default_shipping' => (!empty($data['address_is_default_shipping']) && $supportsShipping ? 1 : 0),
|
|
'is_default_billing' => (!empty($data['address_is_default_billing']) && $supportsBilling ? 1 : 0),
|
|
'created_at' => time(),
|
|
'updated_at' => time(),
|
|
];
|
|
}
|
|
|
|
private function normalizeAddressBook(array $addresses): array
|
|
{
|
|
$normalized = [];
|
|
|
|
foreach ($addresses as $address) {
|
|
if (!is_array($address)) {
|
|
continue;
|
|
}
|
|
|
|
$entry = $this->normalizeAddressBookEntry([
|
|
'address_entry_id' => $address['id'] ?? '',
|
|
'address_label' => $address['label'] ?? '',
|
|
'address_first_name' => $address['first_name'] ?? '',
|
|
'address_last_name' => $address['last_name'] ?? '',
|
|
'address_company' => $address['company'] ?? '',
|
|
'address_phone' => $address['phone'] ?? '',
|
|
'address_address1' => $address['address1'] ?? '',
|
|
'address_address2' => $address['address2'] ?? '',
|
|
'address_city' => $address['city'] ?? '',
|
|
'address_state' => $address['state'] ?? '',
|
|
'address_country' => $address['country'] ?? '',
|
|
'address_zip' => $address['zip'] ?? '',
|
|
'address_type' => $address['type'] ?? 'shipping',
|
|
'address_is_default_shipping' => !empty($address['is_default_shipping']) ? 1 : 0,
|
|
'address_is_default_billing' => !empty($address['is_default_billing']) ? 1 : 0,
|
|
]);
|
|
$entry['created_at'] = (int)($address['created_at'] ?? $entry['created_at']);
|
|
$entry['updated_at'] = (int)($address['updated_at'] ?? time());
|
|
$normalized[] = $entry;
|
|
}
|
|
|
|
$hasDefaultShipping = false;
|
|
$hasDefaultBilling = false;
|
|
foreach ($normalized as $address) {
|
|
if (!empty($address['is_default_shipping'])) {
|
|
$hasDefaultShipping = true;
|
|
}
|
|
if (!empty($address['is_default_billing'])) {
|
|
$hasDefaultBilling = true;
|
|
}
|
|
}
|
|
|
|
foreach ($normalized as &$address) {
|
|
$supportsShipping = in_array($address['type'], ['shipping', 'both'], true);
|
|
$supportsBilling = in_array($address['type'], ['billing', 'both'], true);
|
|
|
|
if (!$supportsShipping) {
|
|
$address['is_default_shipping'] = 0;
|
|
}
|
|
if (!$supportsBilling) {
|
|
$address['is_default_billing'] = 0;
|
|
}
|
|
}
|
|
unset($address);
|
|
|
|
if (!$hasDefaultShipping) {
|
|
foreach ($normalized as &$address) {
|
|
if (in_array($address['type'], ['shipping', 'both'], true)) {
|
|
$address['is_default_shipping'] = 1;
|
|
break;
|
|
}
|
|
}
|
|
unset($address);
|
|
}
|
|
if (!$hasDefaultBilling) {
|
|
foreach ($normalized as &$address) {
|
|
if (in_array($address['type'], ['billing', 'both'], true)) {
|
|
$address['is_default_billing'] = 1;
|
|
break;
|
|
}
|
|
}
|
|
unset($address);
|
|
}
|
|
|
|
usort($normalized, static function ($a, $b) {
|
|
$aScore = (int)!empty($a['is_default_shipping']) + (int)!empty($a['is_default_billing']);
|
|
$bScore = (int)!empty($b['is_default_shipping']) + (int)!empty($b['is_default_billing']);
|
|
if ($aScore === $bScore) {
|
|
return strcmp((string)$a['label'], (string)$b['label']);
|
|
}
|
|
return $bScore <=> $aScore;
|
|
});
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
private function prepareAddressBookForView(array $addresses): array
|
|
{
|
|
$viewData = [];
|
|
foreach ($addresses as $address) {
|
|
$viewData[] = [
|
|
'id' => (string)$address['id'],
|
|
'label' => (string)$address['label'],
|
|
'first_name' => (string)$address['first_name'],
|
|
'last_name' => (string)$address['last_name'],
|
|
'company' => (string)$address['company'],
|
|
'phone' => (string)$address['phone'],
|
|
'address1' => (string)$address['address1'],
|
|
'address2' => (string)$address['address2'],
|
|
'city' => (string)$address['city'],
|
|
'state' => (string)$address['state'],
|
|
'country' => (string)$address['country'],
|
|
'zip' => (string)$address['zip'],
|
|
'type' => (string)$address['type'],
|
|
'is_default_shipping' => !empty($address['is_default_shipping']),
|
|
'is_default_billing' => !empty($address['is_default_billing']),
|
|
'json' => json_encode($address, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
];
|
|
}
|
|
|
|
return $viewData;
|
|
}
|
|
|
|
private function resolveProfileSection(string $section): string
|
|
{
|
|
$allowed = [
|
|
self::SECTION_PROFILE,
|
|
self::SECTION_ADDRESSES,
|
|
self::SECTION_ORDERS,
|
|
self::SECTION_WISHLIST,
|
|
self::SECTION_DELETE,
|
|
];
|
|
|
|
return (in_array($section, $allowed, true) ? $section : self::SECTION_PROFILE);
|
|
}
|
|
|
|
private function getProfileSectionUrls(): array
|
|
{
|
|
return [
|
|
self::SECTION_PROFILE => $this->getProfileSectionUrl(self::SECTION_PROFILE),
|
|
self::SECTION_ADDRESSES => $this->getProfileSectionUrl(self::SECTION_ADDRESSES),
|
|
self::SECTION_ORDERS => $this->getProfileSectionUrl(self::SECTION_ORDERS),
|
|
self::SECTION_WISHLIST => $this->getProfileSectionUrl(self::SECTION_WISHLIST),
|
|
self::SECTION_DELETE => $this->getProfileSectionUrl(self::SECTION_DELETE),
|
|
];
|
|
}
|
|
|
|
private function getProfileSectionUrl(string $section): string
|
|
{
|
|
return $this->gL(self::URL_ID_PROFILE_PAGE) . '?section=' . urlencode($section);
|
|
}
|
|
|
|
private function prepareOrderHistory(int $userId): array
|
|
{
|
|
$buyModule = new pubBuyModule();
|
|
$orders = $buyModule->getUserOrderHistory($userId, 50);
|
|
|
|
if (empty($orders)) {
|
|
return [];
|
|
}
|
|
|
|
$paymentIds = [];
|
|
foreach ($orders as $order) {
|
|
if (!empty($order['o_payment_met'])) {
|
|
$paymentIds[] = (int)$order['o_payment_met'];
|
|
}
|
|
}
|
|
|
|
$paymentTitles = (!empty($paymentIds) ? $buyModule->getPaymentData(array_unique($paymentIds), $this->langId) : []);
|
|
$deliveryTitles = $buyModule->getClassificatorDataById(BuyController::CL_ID_SHIPPING_METHODS);
|
|
$prepared = [];
|
|
|
|
foreach ($orders as $order) {
|
|
$status = $this->getOrderStatusData($order);
|
|
$paymentId = (int)($order['o_payment_met'] ?? 0);
|
|
$deliveryId = (int)($order['o_delivery_met'] ?? 0);
|
|
$currency = (string)($order['rec_currency'] ?? '');
|
|
$currency = $currency === 'EUR' || !strlen($currency) ? '€' : $currency;
|
|
$total = (!empty($order['rec_value']) ? ((float)$order['rec_value'] / 100) : (float)($order['subtotal'] ?? 0));
|
|
$decodedInput = cJSON::decode((string)($order['o_input_data'] ?? ''), true);
|
|
|
|
$deliveryTitle = !empty($deliveryTitles[$deliveryId]['n'])
|
|
? $this->gM($deliveryTitles[$deliveryId]['n'])
|
|
: $this->gM('Shipping');
|
|
|
|
$paymentTitle = !empty($paymentTitles[$paymentId]['title'])
|
|
? $paymentTitles[$paymentId]['title']
|
|
: $this->gM('Payment');
|
|
|
|
$prepared[] = [
|
|
'id' => (int)($order['id'] ?? 0),
|
|
'order_nr' => (string)($order['o_nr'] ?? ''),
|
|
'date_label' => (!empty($order['o_date']) ? date('d.m.Y', (int)$order['o_date']) : ''),
|
|
'payment_title' => $paymentTitle,
|
|
'payment_status_label' => $status['payment_label'],
|
|
'payment_status_class' => $status['payment_class'],
|
|
'status_label' => $status['status_label'],
|
|
'status_class' => $status['status_class'],
|
|
'delivery_title' => $deliveryTitle,
|
|
'items_qty' => (int)($order['items_qty'] ?? 0),
|
|
'total_display' => sprintf('%s %s', $currency, number_format($total, 2, '.', ' ')),
|
|
'note' => trim((string)($decodedInput['check-note'] ?? '')),
|
|
'shipping_name' => trim(($order['o_addr_name'] ?? '') . ' ' . ($order['o_addr_lastname'] ?? '')),
|
|
'shipping_address' => trim((string)($order['o_addr_address'] ?? '')),
|
|
'shipping_city' => (string)($order['o_addr_city'] ?? ''),
|
|
'shipping_country' => (string)($order['o_addr_country'] ?? ''),
|
|
'shipping_zip' => (string)($order['o_addr_zip'] ?? ''),
|
|
'shipping_phone' => (string)($order['o_addr_phone'] ?? ''),
|
|
];
|
|
}
|
|
|
|
return $prepared;
|
|
}
|
|
|
|
private function getOrderStatusData(array $order): array
|
|
{
|
|
$isPaid = ((int)($order['rec_status'] ?? 0) === 1 || (string)($order['rec_result_message'] ?? '') === BuyController::STATUS_CAPTURED);
|
|
if ($isPaid) {
|
|
return [
|
|
'payment_label' => $this->gM('Paid'),
|
|
'payment_class' => 'text-success',
|
|
'status_label' => $this->gM('Completed'),
|
|
'status_class' => 'text-success',
|
|
];
|
|
}
|
|
|
|
if ((int)($order['o_payment_met'] ?? 0) === BuyController::PMET_OTHER_E) {
|
|
return [
|
|
'payment_label' => $this->gM('Awaiting bank transfer'),
|
|
'payment_class' => 'text-warning',
|
|
'status_label' => $this->gM('Pending'),
|
|
'status_class' => 'text-warning',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'payment_label' => $this->gM('Awaiting payment'),
|
|
'payment_class' => 'text-warning',
|
|
'status_label' => $this->gM('Processing'),
|
|
'status_class' => 'text-warning',
|
|
];
|
|
}
|
|
|
|
private function replaceOrderInputEmail($input, string $email): array
|
|
{
|
|
if (!is_array($input)) {
|
|
return [
|
|
'customer_info' => [
|
|
'checkout_email' => $email,
|
|
],
|
|
];
|
|
}
|
|
|
|
foreach (['shipping_address', 'delivery_info'] as $addressKey) {
|
|
if (!empty($input[$addressKey]) && is_array($input[$addressKey])) {
|
|
$input[$addressKey]['checkout_email'] = $email;
|
|
}
|
|
}
|
|
if (empty($input['customer_info']) || !is_array($input['customer_info'])) {
|
|
$input['customer_info'] = [];
|
|
}
|
|
$input['customer_info']['checkout_email'] = $email;
|
|
|
|
return $input;
|
|
}
|
|
|
|
public function runWishlist(): void
|
|
{
|
|
if (!$this->isLoggedIn()) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('You must be logged in to manage wishlist.')]);
|
|
}
|
|
|
|
$action = getV('wl_action');
|
|
$productIdRaw = trim((string)getV('product_id'));
|
|
$productId = 0;
|
|
|
|
if (!$action) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Invalid wishlist request.')]);
|
|
}
|
|
|
|
$userId = (int)$this->suser->id;
|
|
|
|
if ($productIdRaw !== '') {
|
|
if (ctype_digit($productIdRaw)) {
|
|
$productId = (int)$productIdRaw;
|
|
} else {
|
|
$cry = new cDefuseCrypto();
|
|
try {
|
|
$productId = (int)$cry->decrypt($productIdRaw);
|
|
} catch (\Throwable $e) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Invalid product id.')]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (in_array($action, ['add', 'remove'], true) && $productId < 1) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Invalid wishlist request.')]);
|
|
}
|
|
|
|
if ($action === 'add') {
|
|
if ($this->module->isProductInWishlist($userId, $productId)) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Product already in wishlist.')]);
|
|
}
|
|
if (!$this->module->addToWishlist($userId, $productId)) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Failed to add to wishlist.')]);
|
|
}
|
|
$count = $this->module->getWishlistCount($userId);
|
|
cJSON::sendOk(['success' => true, 'msg' => $this->gM('Added to wishlist.'), 'count' => $count]);
|
|
} elseif ($action === 'remove') {
|
|
if (!$this->module->removeFromWishlist($userId, $productId)) {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Failed to remove from wishlist.')]);
|
|
}
|
|
$count = $this->module->getWishlistCount($userId);
|
|
cJSON::sendOk(['success' => true, 'msg' => $this->gM('Removed from wishlist.'), 'count' => $count]);
|
|
} elseif ($action === 'getIds') {
|
|
// Return all wishlist product IDs for current user
|
|
$productIds = $this->module->getWishlistProductIds($userId);
|
|
cJSON::sendOk(['success' => true, 'ids' => $productIds, 'count' => count($productIds)]);
|
|
} elseif ($action === 'load') {
|
|
$productIds = $this->module->getWishlistProductIds($userId);
|
|
if (empty($productIds)) {
|
|
cJSON::sendOk(['success' => true, 'html' => '', 'count' => 0]);
|
|
}
|
|
|
|
$products = $this->prepareWishlistProducts($productIds);
|
|
if (empty($products)) {
|
|
cJSON::sendOk(['success' => true, 'html' => '', 'count' => 0]);
|
|
}
|
|
|
|
$this->oView->PRODUCTS = $products;
|
|
$this->oView->WISHLIST_SIMPLE_VIEW = true;
|
|
$html = $this->oView->genTemplate('wishlist_items');
|
|
|
|
cJSON::sendOk(['success' => true, 'html' => $html, 'count' => count($productIds)]);
|
|
} else {
|
|
cJSON::sendError(['success' => false, 'msg' => $this->gM('Invalid wishlist action.')]);
|
|
}
|
|
}
|
|
|
|
public function processProfilePasswordChange(array $fields): void
|
|
{
|
|
$this->getCurrentProfileUser(true);
|
|
|
|
$valid = new cValidator($this);
|
|
$errors = $valid->checkArray($fields, $this->getPasswdValCfg());
|
|
if ($errors !== true) {
|
|
cJSON::sendError(['data' => ['fields' => $errors, 'msg' => $this->gM('Please review form fields.')]]);
|
|
}
|
|
|
|
$vUserData = $this->getCurrentProfileUser(true);
|
|
|
|
$oPwdMaster = new cPasswordMaster();
|
|
if (!$oPwdMaster->password_verify($fields['old_password'], $vUserData['user_password'])) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'msg' => $this->gM("Old user password doesn't match!"),
|
|
'fields' => ['old_password' => $this->gM("Old user password doesn't match!")],
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
if ($oPwdMaster->password_verify($fields['new_password'], $vUserData['user_password'])) {
|
|
cJSON::sendError(
|
|
[
|
|
'data' => [
|
|
'msg' => $this->gM('New user password cannot match previous password!'),
|
|
'fields' => [
|
|
'new_password' => $this->gM('New user password cannot match previous password!'),
|
|
'rnew_password' => $this->gM('New user password cannot match previous password!'),
|
|
],
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
$sNewPwd = $oPwdMaster->password_hash($fields['new_password']);
|
|
if (!$sNewPwd) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to update user password.')]]);
|
|
}
|
|
|
|
if (!$this->module->updateUserDataById((int)$this->suser->id, [
|
|
'user_fchange' => 0,
|
|
'user_pwdtime' => 0,
|
|
'user_newpwd' => '',
|
|
'user_password' => $sNewPwd,
|
|
'user_modified' => time(),
|
|
'user_modified_by' => 0,
|
|
])) {
|
|
cJSON::sendError(['data' => ['msg' => $this->gM('Failed to update user password.')]]);
|
|
}
|
|
|
|
if (is_array($this->suser->data)) {
|
|
$this->suser->data['user_password'] = $sNewPwd;
|
|
}
|
|
|
|
cJSON::sendOk(['msg' => $this->gM('User password successfully changed!')]);
|
|
}
|
|
|
|
public function getUserValCfg(): array
|
|
{
|
|
return [
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
'user_name' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'user_surname' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_STR],
|
|
],
|
|
'user_email' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_EMAIL],
|
|
|
|
],
|
|
'user_phone' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 0,
|
|
cValidator::P_TEST => [cValidator::CHK_PHONE],
|
|
],
|
|
];
|
|
}
|
|
|
|
public function getAddressBookValCfg(): array
|
|
{
|
|
return [
|
|
'cfg' => [
|
|
cValidator::CFG_BIND_FIELD => 'data-validator',
|
|
],
|
|
'address_label' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_first_name' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_last_name' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_phone' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::CHK_PHONE],
|
|
],
|
|
'address_address1' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_address2' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 0,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_city' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_state' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 0,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_country' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_zip' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
'address_type' => [
|
|
cValidator::P_TYPE => cValidator::TY_STR,
|
|
cValidator::P_COMP => 1,
|
|
cValidator::P_TEST => [cValidator::TY_STR],
|
|
],
|
|
];
|
|
}
|
|
}
|