select('u.*')->from('users', 'u') ->where($spec); if (findModule(Modules::PRICE_LEVELS)) { $qb->addSelect('upl.id_price_level as idUserPriceLevel') ->leftJoin('u', 'users_dealer_price_level', 'upl', 'u.id = upl.id_user'); } if (findModule(Modules::USERS_GROUPS_TYPES)) { $qb->addSelect('GROUP_CONCAT(distinct ug.types) as types') ->leftJoin('u', 'users_groups_relations', 'ugr', 'ugr.id_user = u.id') ->leftJoin('ugr', 'users_groups', 'ug', 'ug.id = ugr.id_group') ->groupBy('u.id'); } $SQL = $qb->execute(); $data = sqlFetchAssoc($SQL); if (empty($data)) { return null; } $ret = new User(); $ret->loadData($data); return $ret; } public static function getFields() { return self::$fields; } /** * @deprecated Use user context */ public static function getCurrentUser() { return Contexts::get(UserContext::class)->getActive(); } /** * @deprecated Use user context */ public static function getCurrentUserId() { return Contexts::get(UserContext::class)->getActiveId(); } public static function createFromId($id) { return static::createFromSpec(Operator::equals(['u.id' => $id])); } public static function createFromLogin($login) { return static::createFromSpec( Operator::andX( Operator::equals(['email' => $login]), Operator::equals(['figure' => 'Y'])) ); } public static function createFromUserKey($userKey) { return static::createFromSpec(Operator::equals(['user_key' => $userKey, 'figure' => 'Y'])); } public static function createFromFeedToken($feed_token) { return static::createFromSpec(Operator::equals(['feed_token' => $feed_token, 'figure' => 'Y'])); } /** Login current user and notify cart. */ public function login($userKey = null, $skipSymfonyLogin = false) { // Update user key if (empty($this->user_key)) { sqlQuery('UPDATE users SET user_key=REPLACE(UUID(), "-", ""), date_logged = NOW() WHERE id=:id', ['id' => $this->id]); } else { sqlQuery('UPDATE users SET date_logged = NOW() WHERE id=:id', ['id' => $this->id]); } $this->activateUser(); // Notify cart $cart = ServiceContainer::getService(CartFactory::class)->create(); $cart->userLoggedIn($this->id); // Notify favorite products $favoriteProductsUtil = ServiceContainer::getService(FavoriteProductsUtil::class); $favoriteProductsUtil->userLoggedIn((int) $this->id, FavoriteProductsUtil::getCookieProducts()); if (!$skipSymfonyLogin && !isFunctionalTests()) { $this->loginToSymfony(); } } protected function loginToSymfony() { $userProvider = ServiceContainer::getService(UserProvider::class); $user = $userProvider->loadUserByUsername($this->email); $security = ServiceContainer::getService(\Symfony\Bundle\SecurityBundle\Security::class); $security->login($user, 'form_login', 'main'); } public function loadData($data) { foreach ($data as $name => $value) { $this->$name = $value; } if (!empty($data['custom_data'])) { $this->custom_data = json_decode($data['custom_data'], true); } if (!empty($data['types'])) { $config = Config::get(); $this->types = explode(',', $data['types']); $this->types = array_combine($this->types, array_filter($config['Modules'][\Modules::USERS_GROUPS_TYPES], function ($value, $key) { return in_array($key, $this->types); }, ARRAY_FILTER_USE_BOTH)); } $this->types = $this->types ?: []; if ($this->isActive()) { $this->types = array_merge($this->types, ['logged' => ['name' => translate('LoggedUser', 'settings', false, true)]]); } } public function getChangePasswordHash(?DateTime $date = null) { if (!$date) { $date = new DateTime(); } return md5($this->email.$this->passw.$date->format('Y-m-d')); } public function updatePassword($plaintext) { $encoder = ServiceContainer::getService(LegacyPasswordEncoder::class); $this->passw = $encoder->encodePassword($plaintext, ''); sqlQueryBuilder() ->update('users') ->directValues( [ 'passw' => $this->passw, 'date_updated' => (new DateTime())->format('Y-m-d H:i:s'), ] ) ->where(Operator::equals(['id' => $this->id])) ->execute(); } public function fetchAddresses(?array $user = null) { if ($user === null) { $user = sqlQueryBuilder() ->select('*') ->from('users') ->where(Operator::equals(['id' => $this->id])) ->execute()->fetchAssociative(); } if (!$user) { return false; } // invoice foreach (self::getFields() as $field) { $this->invoice[$field] = $user[$field]; } $this->invoice['email'] = $user['email']; $this->invoice['ico'] = $user['ico']; $this->invoice['dic'] = $user['dic']; $this->invoice['copy_email'] = $user['copy_email']; if (empty($this->gender)) { $this->gender = $user['gender'] ?? null; } if (empty($this->transport)) { $this->transport = $user['prefer_transport']; } $this->invoice['phone'] = ''; if ($user['phone'] != '') { $this->invoice['phone'] = $user['phone']; } if ($user['mobile'] != '') { $this->invoice['mobile'] = $user['mobile']; } // delivery foreach (self::getFields() as $field) { $this->delivery[$field] = $user['delivery_'.$field]; } if (!empty($user['custom_data'])) { $this->custom_data = json_decode($user['custom_data'], true); } return true; } public function updateAddresses($invoice, $delivery, $in_person = false) { $this->sanitizeAddress($invoice); $this->invoice = array_merge($this->invoice, $invoice); if (!is_null($delivery)) { $this->sanitizeAddress($delivery); $this->delivery = array_merge($this->delivery, $delivery); } $errors = array_keys($this->checkAddresses($in_person)); if (empty($errors)) { return null; } return reset($errors); } public function checkAddresses($in_person = false): array { $errors = []; if (empty($this->invoice['email'])) { $errors[10] = true; } if (empty($this->invoice['name']) || empty($this->invoice['surname'])) { $errors[6] = true; } $this->invoice['phone'] = trim(strtr($this->invoice['phone'], [' ' => ''])); if (findModule(\Modules::USERS, \Modules::SUB_USERS_PHONE_LOGIN)) { $validator = ServiceContainer::getService(PhoneNumberValidator::class); try { $validated = $validator->validate($this->invoice['phone']); } catch (PhoneValidationException $e) { throw new ValidationException(translate('error', 'user')['invalid_phone']); } $this->invoice['phone'] = $validated->dbNumber; } if (empty($this->invoice['phone'])) { $errors[11] = true; } $country = getVal('country', $this->invoice); $isCZorSK = $country == 'SK' || $country == 'CZ'; if ($isCZorSK) { if ($phone = $this->checkPhoneNumber($this->invoice['phone'], $country)) { $this->invoice['phone'] = $phone; } else { $errors[14] = true; } } else { $phone = preg_replace('/[^0-9+]/', '', $this->invoice['phone']); $this->invoice['phone'] = $phone; } $d_country = getVal('country', $this->delivery); $d_isCZorSK = $d_country == 'SK' || $d_country == 'CZ'; if (!empty($this->delivery['phone'])) { $this->delivery['phone'] = trim(strtr($this->delivery['phone'], [' ' => ''])); if ($d_isCZorSK) { if ($phone = $this->checkPhoneNumber($this->delivery['phone'], $d_country)) { $this->delivery['phone'] = $phone; } else { $errors[14] = true; } } else { $phone = preg_replace('/[^0-9+]/', '', $this->delivery['phone']); $this->delivery['phone'] = $phone; } } if (!$in_person) { if (empty($this->invoice['zip'])) { $errors[9] = true; } if (empty($this->invoice['city'])) { $errors[8] = true; } if (empty($this->invoice['street'])) { $errors[7] = true; } if ($isCZorSK) { if (!empty($this->invoice['zip']) && !preg_match('/^[0-9]{5}$/', $this->invoice['zip'])) { $errors[13] = true; } } if ($d_isCZorSK) { if (!empty($this->delivery['zip']) && !preg_match('/^[0-9]{5}$/', $this->delivery['zip'])) { $errors[13] = true; } } if (empty($this->invoice['country'])) { $errors[17] = true; } } return $errors; } public function checkPhoneNumber($phone = '', $country = 'CZ') { if (preg_match('/^((?:\+|00)[0-9]{3}|0)?(\d{6,9})$/', trim(strtr($phone, [' ' => '', '/' => ''])), $matches)) { if (empty($matches[1])) { if ($country == 'SK') { $matches[1] = '+421'; } else { $matches[1] = '+420'; } } elseif (substr($matches[1], 0, 2) == '00') { $matches[1] = '+'.substr($matches[1], 2); } elseif ($matches[1] == '0') { // Slovaci maji takhle uchylne nulu na zacatku $matches[1] = '+421'; } elseif (substr($matches[1], 0, 1) != '+') { $matches[1] = '+'.$matches[1]; } $phone = $matches[1].$matches[2]; return $phone; } else { return false; } } public function sanitizeRegistration($password = null) { $error = false; $qb = sqlQueryBuilder()->select('COUNT(id)') ->from('users'); $where = Operator::equals(['email' => $this->invoice['email']]); if (findModule(\Modules::USERS, \Modules::SUB_USERS_PHONE_LOGIN)) { $where = Operator::orX( Operator::equals(['email' => $this->invoice['email']]), Operator::equals(['phone' => $this->invoice['phone']]) ); } $qb->where($where); if (!is_null($password)) { $qb->andWhere(Operator::equals(['figure' => 'Y'])); } if ($this->id > 0) { $qb->andWhere(Operator::not(Operator::equals(['id' => $this->id]))); } $no = $qb->execute()->fetchOne(); if ($no > 0) { $error = 15; } else { if ($this->id) { if (strlen($password) > 0 && $this->sanitizePassword($password)) { $error = 16; } elseif ($this->sanitizePassword($password)) { $error = 16; } } } return $error; } public function sanitizePassword($password) { $error = false; if (strlen($password) < 6) { $error = 16; } return $error; } protected function sanitizeAddress(&$address) { // kontrola poslanych dat z formulare foreach (self::getFields() as $field) { $address[$field] = StringUtil::unicode_trim(getVal($field, $address)); } if (($address['country'] == 'CZ') || ($address['country'] == 'SK')) { $address['zip'] = preg_replace('/[^0-9]/', '', $address['zip']); } $address['ico'] = StringUtil::unicode_trim(getVal('ico', $address)); $address['dic'] = StringUtil::unicode_trim(getVal('dic', $address)); $address['phone'] = StringUtil::unicode_trim(getVal('phone', $address)); $address['email'] = StringUtil::unicode_trim(getVal('email', $address)); $address['copy_email'] = StringUtil::unicode_trim(getVal('copy_email', $address)); if (findModule('currencies') && empty($address['currency'])) { $address['currency'] = Contexts::get(CurrencyContext::class)->getActiveId(); } } // used as update too public function update() { $unreg_user_id = null; $SQL = sqlQuery('SELECT id FROM '.getTableName('users').' WHERE email = :email AND figure=\'N\'', ['email' => $this->invoice['email']]); if (sqlNumRows($SQL) == 1) { $unreg_user_id = sqlFetchArray($SQL)['id']; } $fields = []; // invoice foreach (self::getFields() as $field) { $fields[$field] = $this->invoice[$field] ?? ''; } // delivery foreach (self::getFields() as $field) { $fields['delivery_'.$field] = $this->delivery[$field] ?? ''; } $fields['ico'] = $this->invoice['ico'] ?? ''; $fields['dic'] = $this->invoice['dic'] ?? ''; $fields['phone'] = $this->invoice['phone'] ?? ''; $fields['email'] = $this->invoice['email'] ?? ''; $fields['copy_email'] = $this->invoice['copy_email'] ?? ''; if (isset($this->invoice['gender'])) { $fields['gender'] = $this->invoice['gender']; } if (isset($this->invoice['birthdate'])) { $fields['birthdate'] = $this->invoice['birthdate']; } if (isset($this->invoice['transport'])) { $fields['prefer_transport'] = $this->invoice['transport'] ?: null; } if (findModule('currencies') && !empty($this->invoice['currency'])) { $fields['currency'] = $this->invoice['currency']; $this->currency = $this->invoice['currency']; } if (!empty($this->custom_data)) { $fields['custom_data'] = json_encode($this->custom_data); } if (empty($this->email)) { $this->email = $fields['email'] ?? ''; } $userContext = Contexts::get(UserContext::class); if ($userContext->getActiveId() > 0) { $fields['date_updated'] = date('Y-m-d H:i'); $this->id = $userContext->getActiveId(); $this->updateSQL('users', $fields, ['id' => $this->id]); return $this->id; } else { if (!isset($this->id_language)) { $languageContext = Contexts::get(LanguageContext::class); $this->id_language = $languageContext->getActiveId(); } $fields['id_language'] = $this->id_language; $fields['date_reg'] = date('Y-m-d H:i'); if ($unreg_user_id) { $fields['figure'] = 'Y'; $this->id = $unreg_user_id; $this->updateSQL('users', $fields, ['id' => $this->id]); // dispatch registered event $this->sendUserRegisteredEvent(true); return $this->id; } else { if ($this->insertSQL('users', $fields)) { $this->id = sqlInsertId(); // dispatch registered event $this->sendUserRegisteredEvent(true); return $this->id; } } } return false; } public function getGreeting($prioritize = 'name') { if (empty($this->invoice['name'])) { $this->fetchAddresses(); } return Greeting::getGreeting($this->invoice['name'], $this->invoice['surname'], $prioritize); } public function getPriceLevel() { if ($this->userPriceLevel === null) { // save `false` on null, so it is cached and not loaded again $this->userPriceLevel = static::getUserPriceLevel($this->id) ?? false; } return $this->userPriceLevel ?: null; } public function getUserKey() { return $this->user_key; } public static function getUserPriceLevel($id) { $price_level = null; if (!empty($id)) { $pl = sqlQuery('SELECT * FROM users_dealer_price_level WHERE id_user=:id_user', ['id_user' => $id]); if (sqlNumRows($pl) == 1) { $pl = sqlFetchArray($pl); $price_level = ServiceContainer::getService(\KupShop\CatalogBundle\PriceLevel::class); $price_level = $price_level->createFromDB($pl['id_price_level']); } } return $price_level; } public function getUserPriceLevelId() { return $this->idUserPriceLevel; } public function getUserCurrency() { if (isset($this->currency)) { return $this->currency; } return null; } public function getGroups() { if (is_null($this->groups)) { $this->groups = sqlFetchAll(sqlQueryBuilder()->select('ug.*') ->from('users_groups_relations', 'ugr') ->join('ugr', 'users_groups', 'ug', 'ug.id=ugr.id_group') ->where(Operator::equals(['ugr.id_user' => $this->id])) ->orderBy('ug.id'), 'id'); foreach ($this->groups as &$group) { $group['data'] = json_decode($group['data'] ?: '', true) ?: []; } } return $this->groups; } public function getShoppingLists(): ?array { if (!findModule(Modules::SHOPPING_LIST)) { return null; } if (!isset($this->shoppingLists)) { $this->shoppingLists = sqlQueryBuilder() ->select('sl.*, count(p.id) as pocet') ->from('shopping_list', 'sl') ->leftJoin('sl', 'shopping_list_products', 'psl', 'psl.id_shopping_list = sl.id') ->leftJoin('psl', 'products', 'p', 'psl.id_product = p.id AND p.figure = "Y"') ->andWhere(Operator::equals(['sl.id_user' => $this->id])) ->groupBy('sl.id') ->orderBy('label = "favorites"', 'DESC') ->addOrderBy('sl.name, sl.id', 'ASC') ->execute()->fetchAllAssociative(); } return $this->shoppingLists; } public function isType(string $type): bool { return !empty($this->types[$type]); } public function getType(string $type): ?array { return $this->types[$type]; } public function hasGroupId($groupId) { return isset($this->getGroups()[$groupId]); } public function isActive(): bool { return $this->figure === 'Y'; } public function isRegistered(): bool { return $this->isActive(); } /** * Activate not logged user. * * @return bool */ public function activateUser() { if ($this->id > 0) { Contexts::get(UserContext::class)->activate($this->id); if (findModule(Modules::PRICE_LEVELS) && $this->idUserPriceLevel) { $priceLevelContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\PriceLevelContext::class); $priceLevelContext->activate($this->idUserPriceLevel); } return true; } return false; } public function getAllOrdersPrice() { return sqlQueryBuilder()->select('SUM(total_price)') ->from('orders') ->where('id_user=:id_user') ->andWhere('status_storno!=1') ->setParameter('id_user', $this->id) ->execute()->fetchColumn(); } /** * Implements ArrayAccess interface. */ public function offsetSet($offset, $value): void { $this->{$offset} = $value; } public function offsetExists($offset): bool { return isset($this->{$offset}); } public function offsetUnset($offset): void { unset($this->{$offset}); } public function offsetGet($offset): mixed { return isset($this->{$offset}) ? $this->{$offset} : null; } public function setCustomData($key, $value) { $customData = $this->getCustomData(true); $customData[$key] = $value; $this->setCustomDataAll($customData); } public function getCustomData(bool $forceLoad = false) { if ($forceLoad && $this->id > 0) { $data = sqlQueryBuilder()->select('custom_data') ->from('users') ->where(Operator::equals(['id' => $this->id])) ->sendToMaster() // Může volat setCustomData kdokoli, tak když by nešlo na master, ztrácí se data :-( ->execute()->fetchOne(); $this->custom_data = json_decode($data, true); } return $this->custom_data; } private function setCustomDataAll($customData) { $this->custom_data = $customData; if ($this->id > 0) { $encoded = json_encode($customData); $this->updateSQL('users', ['custom_data' => $encoded], ['id' => $this->id]); } } private function sendUserRegisteredEvent(bool $refreshUser = false): void { $user = $this; if ($refreshUser) { $user = QueryHint::withRouteToMaster(fn () => static::createFromId($this->id)); } $this->getEventDispatcher()->dispatch( new UserBundle\Event\UserRegisteredEvent($user) ); } private function getEventDispatcher(): EventDispatcherInterface { return ServiceContainer::getService('event_dispatcher'); } } if (empty($subclass)) { class User extends UserBase { } }