createUserCollectionFilter($offset, $limit, $userSort, $userFilter); $users = $this->getUsers($filterSpec, $totalCount); return (new UserCollection($users)) ->setItemsTotalCount($totalCount) ->setLimit($limit) ->setOffset($offset); } public function getUser(?int $id, ?string $email, ?string $cartId = null): ?User { if (!$id && !$email && !$cartId) { throw new GraphQLValidationException('Cannot query `user` without id or email or cartId provided!'); } $users = $this->getUsers(Operator::equals(array_filter(['u.id' => $id, 'u.email' => $email, 'u.user_key' => $cartId]), 'OR')); if (!($user = reset($users))) { throw new GraphQLNotFoundException('User not found!'); } return $user; } /** * @return User[] */ public function getUsers(callable $filterSpec, ?int &$totalCount = null): array { $useTotalCount = count(func_get_args()) > 1; $qb = sqlQueryBuilder() ->addCalcRows() ->select('u.*') ->from('users', 'u') ->andWhere($filterSpec) ->groupBy('u.id'); $users = []; foreach ($qb->execute() as $item) { $user = new \User(); $user->loadData($item); $user->fetchAddresses($item); $users[$item['id']] = $user; } if ($useTotalCount) { $totalCount = (int) sqlFetchAssoc(sqlQuery('SELECT FOUND_ROWS() as total_count'))['total_count']; } $this->processUsersMultiFetches($users); return ApiUtil::wrapItems($users, User::class); } public function getUsersBonusPoints(int $offset, int $limit, ?Parameters $sort, ?UserBonusPointFilterInput $filter): UserBonusPointCollection { $qb = sqlQueryBuilder() ->addCalcRows() ->select('bp.*') ->from('bonus_points', 'bp') ->andWhere(ApiUtil::getLimitSpec($offset, $limit)) ->andWhere(ApiUtil::getSortSpec($sort, 'bp')); if ($filter?->id) { $qb->andWhere(Operator::inIntArray($filter->id, 'bp.id')); } if ($filter?->userId) { $qb->andWhere(Operator::inIntArray($filter->userId, 'bp.id_user')); } if ($filter?->orderId) { $qb->andWhere(Operator::inIntArray($filter->orderId, 'bp.id_order')); } if ($filter?->status) { $qb->andWhere(Operator::inStringArray(array_map(fn (BonusPointStatus $status) => $status->value, $filter->status), 'bp.status')); } if ($filter?->dateCreated) { $qb->andWhere(ApiUtil::getDateTimeFilter($filter->dateCreated, 'bp.date_created')); } $data = $qb->execute(); $totalCount = (int) sqlFetchAssoc(sqlQuery('SELECT FOUND_ROWS() as total_count'))['total_count']; return (new UserBonusPointCollection( ApiUtil::wrapItems($data, UserBonusPoint::class)) ) ->setOffset($offset) ->setLimit($limit) ->setItemsTotalCount($totalCount); } public function addOrRemoveUserGroupsRelations(UserGroupsRelationInput $input): UserMutateResponse { // zkontroluju, ze uzivatel existuje $this->validateUser($input->userId); // zkontroluju, ze vsechny uzivatelske skupiny existuji foreach ($input->userGroupIds as $group) { $this->validateUserGroup($group); } if ($input->overwrite) { sqlQueryBuilder() ->delete('users_groups_relations') ->where(Operator::equals(['id_user' => $input->userId])) ->execute(); } foreach ($input->userGroupIds as $group) { sqlQueryBuilder() ->insert('users_groups_relations') ->directValues([ 'id_user' => $input->userId, 'id_group' => $group, ]) ->onDuplicateKeyUpdate(['id_group' => $group, 'id_user' => $input->userId]) ->execute(); } return new UserMutateResponse(true, $this->getUpdatedUser($input->userId)); } public function createOrUpdateUser(UserInput $input): UserMutateResponse { if (empty($input->id) && !ObjectUtil::isPropertyInitialized($input, 'email')) { throw new GraphQLValidationException('Field "email" is required when creating user!'); } // zkontroloval, ze uzivatel se zadanym ID existuje if (isset($input->id)) { $userExists = sqlQueryBuilder() ->select('id') ->from('users') ->where(Operator::equals(['id' => $input->id])) ->execute()->fetchOne(); if (!$userExists) { throw new GraphQLNotFoundException(sprintf('User with ID "%s" does not exists!', $input->id)); } } // zkontroluju mail if (ObjectUtil::isPropertyInitialized($input, 'email')) { $this->validateUserEmail($input->email, !empty($input->id) ? $input->id : null); } // zkontroluju, ze zadavam existujici cenik if (ObjectUtil::isPropertyInitialized($input, 'priceListId')) { $this->validateUserPriceList($input->priceListId); } if (ObjectUtil::isPropertyInitialized($input, 'password')) { $this->validateUserPassword($input->password); } $userData = $this->getUserData($input); $isNewUser = false; $userId = !empty($input->id) ? $input->id : null; // vytvoreni zakaznika if (!$userId) { $defaults = [ 'figure' => 'Y', 'date_reg' => (new \DateTime())->format('Y-m-d H:i:s'), ]; $isNewUser = true; $insert = array_merge($userData, $defaults); $userId = sqlGetConnection()->transactional(function () use ($insert) { sqlQueryBuilder() ->insert('users') ->directValues($insert) ->execute(); return (int) sqlInsertId(); }); } elseif (!empty($userData)) { // aktualizace zakaznika sqlQueryBuilder() ->update('users') ->directValues($userData) ->where(Operator::equals(['id' => $userId])) ->execute(); } if (ObjectUtil::isPropertyInitialized($input, 'data')) { ApiUtil::updateObjectCustomData('users', $userId, $input->data, 'custom_data'); } if (ObjectUtil::isPropertyInitialized($input, 'newsletter')) { $this->updateUserNewsletter($userId, $isNewUser, $input->newsletter); } if ($input->sendPasswordEmail) { $this->sendPasswordResetEmail($userId); } return new UserMutateResponse(true, $this->getUpdatedUser($userId)); } protected function getUserData(UserInput $input): array { $data = []; if (ObjectUtil::isPropertyInitialized($input, 'email')) { $data['email'] = $input->email; } if (ObjectUtil::isPropertyInitialized($input, 'password')) { $data['passw'] = $input->password ? $this->passwordEncoder->hash($input->password) : ''; } if (ObjectUtil::isPropertyInitialized($input, 'ico')) { $data['ico'] = $input->ico; } if (ObjectUtil::isPropertyInitialized($input, 'dic')) { $data['dic'] = $input->dic; } if (ObjectUtil::isPropertyInitialized($input, 'isActive')) { $data['figure'] = $input->isActive ? 'Y' : 'N'; } if (ObjectUtil::isPropertyInitialized($input, 'priceListId')) { $data['id_pricelist'] = $input->priceListId; } if (ObjectUtil::isPropertyInitialized($input, 'invoiceAddress')) { $address = $this->getUserAddress($input->invoiceAddress); if (!empty($address)) { $data = array_merge($data, $address); } } if (ObjectUtil::isPropertyInitialized($input, 'deliveryAddress')) { $address = $this->getUserAddress($input->deliveryAddress); foreach ($address as $key => $value) { if (in_array($key, ['ico', 'dic'])) { continue; } $data['delivery_'.$key] = $value; } } return $data; } protected function updateUserNewsletter(int $userId, bool $isNewUser, ?UserNewsletterInput $newsletterInput): void { if (!$newsletterInput) { return; } if (ObjectUtil::isPropertyInitialized($newsletterInput, 'isSubscribed') && $newsletterInput->isSubscribed !== null) { $this->userConsent->updateNewsletter( userId: $userId, newsletter: $newsletterInput->isSubscribed ? 'Y' : 'N', new_user: $isNewUser, confirmed: true ); } } public function getUpdatedUser(int $userId): User { $users = QueryHint::withRouteToMaster(fn () => $this->getUsers(Operator::equals(['u.id' => $userId]))); return reset($users); } protected function getUserAddress(AddressInput|UserAddressInput $input): array { // validovat zemi if (ObjectUtil::isPropertyInitialized($input, 'country')) { $countries = Contexts::get(CountryContext::class)->getAll(); if (empty($countries[$input->country])) { throw new GraphQLValidationException(sprintf('Country "%s" does not exist!', $input->country)); } } return (array) $input; } protected function prepareUserDeliveryAddress(array $address): array { $userDeliveryAddress = []; if (!$this->checkUser($address['userId']) ?? false) { throw new GraphQLValidationException(sprintf('User with ID "%s" does not exist!', $address['userId'])); } $address = $this->renameAddressFields($address); foreach ($address as $key => $value) { if (in_array($key, ['id', 'id_user'])) { $userDeliveryAddress[$key] = $value; continue; } $userDeliveryAddress['delivery_'.$key] = $value; } return $userDeliveryAddress; } private function renameAddressFields(array $address) { $address['id_user'] = $address['userId']; $address['custom_address'] = $address['customAddress'] ?? ''; unset($address['userId']); unset($address['customAddress']); return $address; } private function checkUser(int $userId): ?int { return sqlQueryBuilder() ->select('id') ->from('users') ->where(Operator::equals(['id' => $userId])) ->execute()->fetchOne() ?: null; } private function checkCurrency(string $currency): ?string { return sqlQueryBuilder() ->select('id') ->from('currencies') ->where(Operator::equals(['id' => $currency])) ->execute()->fetchOne() ?: null; } protected function validateUserPassword(?string $password): void { if ($password === null) { return; } if (strlen($password) < 6) { throw new GraphQLValidationException( message: 'Password must be at least 6 characters long.', extensions: ['field' => 'password'], ); } } protected function validateUserEmail(?string $email, ?int $userId = null): void { if (empty($email)) { throw new GraphQLValidationException('Field "email" cannot be empty!'); } $spec = [ Operator::equals(['email' => $email]), ]; if ($userId) { $spec[] = Operator::not( Operator::equals(['id' => $userId]) ); } // kontrola, zda se nesnazim nastavit email, ktery uz ma jiny zakaznik $emailExists = sqlQueryBuilder() ->select('id') ->from('users') ->where(Operator::andX($spec)) ->execute()->fetchOne(); if ($emailExists) { throw new GraphQLValidationException( sprintf('Email "%s" is already set up for another user!', $email) ); } if (!$this->emailCheck->isEmailDomainValid($email)) { throw new GraphQLValidationException(sprintf('Email "%s" is not valid!', $email)); } } protected function validateUser(int $userId): void { $userExists = sqlQueryBuilder() ->select('id') ->from('users') ->where(Operator::equals(['id' => $userId])) ->sendToMaster()->execute()->fetchOne(); if (!$userExists) { throw new GraphQLNotFoundException(sprintf( 'User with ID "%s" does not exist!', $userId) ); } } protected function validateUserGroup(int $userGroup): void { $userGroupExists = sqlQueryBuilder() ->select('id') ->from('users_groups') ->where(Operator::equals(['id' => $userGroup])) ->execute()->fetchOne(); if (!$userGroupExists) { throw new GraphQLValidationException( sprintf('User group with ID "%s" does not exist!', $userGroup) ); } } protected function validateUserPriceList(?int $priceListId): void { if (!$this->priceListWorker) { return; } if ($priceListId !== null && !$this->priceListWorker->getPriceList($priceListId)) { throw new GraphQLValidationException( sprintf('Price list with ID "%s" does not exists!', $priceListId) ); } } protected function createUserCollectionFilter(int $offset, int $limit, ?Parameters $sort, ?Parameters $filter): callable { $phone = $filter?->get('phone'); if (!empty($phone)) { $filter->set('phone', Operator::like(['u.phone' => '%'.$phone.'%'])); } else { $filter?->remove('phone'); } if (!empty($filter?->get('name'))) { $filter->set('name', Search::searchFields($filter->get('name'), [ ['field' => 'u.name', 'match' => 'both'], ['field' => 'u.surname', 'match' => 'both'], ])); } else { $filter?->remove('name'); } return function (QueryBuilder $qb) use ($offset, $limit, $sort, $filter) { $this->parametersAssembler->assemblyInput($filter, $sort, $offset, $limit, $qb); }; } protected function saveDeliveryAddress(array $address): int { return sqlGetConnection()->transactional(function () use ($address) { $qb = sqlQueryBuilder(); if ($id = ($address['id'] ?? false)) { unset($address['id']); $qb->update('users_addresses') ->directValues($address) ->where(Operator::equals(['id' => $id])) ->execute(); return $id; } $qb->insert('users_addresses') ->directValues($address) ->execute(); return sqlInsertId(); }); } public function createOrUpdateUserAddress(UserAddressInput $input): UserAddressMutateResponse { $address = $this->getUserAddress($input); if (isset($address['id'])) { $addressExists = sqlQueryBuilder() ->select('id') ->from('users_addresses') ->where(Operator::equals(['id' => $address['id']])) ->execute()->fetchOne(); if (!$addressExists) { throw new GraphQLNotFoundException(sprintf('Address with ID "%s" does not exists!', $address['id'])); } } $address = $this->prepareUserDeliveryAddress($address); return new UserAddressMutateResponse(true, $this->saveDeliveryAddress($address)); } protected function processUsersMultiFetches(array &$users): void { } /** * @required */ public function setParametersAssembler(ParametersAssembler $parametersAssembler): void { $this->parametersAssembler = $parametersAssembler; } public function sendPasswordResetEmail(int $userId): void { $user = QueryHint::withRouteToMaster(fn () => \User::createFromId($userId)); $this->passwordResetEmail->setUser($user); $email = $this->passwordResetEmail->getEmail(); $email['to'] = $user->email; $this->passwordResetEmail->sendEmail($email); } }