src/Service/MemberService.php line 101

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Dictionary\MemberSex;
  4. use App\Entity\Account;
  5. use App\Entity\Member;
  6. use App\Entity\MemberProperty;
  7. use App\Event\RedisEventManager;
  8. use App\Event\User\AmateurUpdateEvent;
  9. use App\Event\User\MemberCreatedEvent;
  10. use App\Exception\AlreadyHasMemberException;
  11. use App\Repository\MemberRepository;
  12. use App\Response\EntityUpdated;
  13. use App\Service\Property\MemberPropertyService;
  14. use App\Service\User\AccountService;
  15. use DateTime;
  16. use Doctrine\ORM\Exception\ORMException;
  17. use Doctrine\ORM\Query;
  18. use Doctrine\ORM\Query\Expr\Join;
  19. use Doctrine\ORM\Tools\Pagination\Paginator;
  20. use Frivol\Common\Dict\AccountStatus;
  21. use JetBrains\PhpStorm\ArrayShape;
  22. use Symfony\Component\HttpFoundation\ParameterBag;
  23. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  24. class MemberService extends AbstractService implements EventDispatcherAwareInterface
  25. {
  26.     /**
  27.      * @var MemberRepository
  28.      */
  29.     protected $repo;
  30.     protected string $supportUsername 'Support';
  31.     protected MemberPropertyService $propertyService;
  32.     protected RedisEventManager $redisEvents;
  33.     protected EventDispatcherInterface $dispatcher;
  34.     public function __construct(
  35.         MemberRepository $repository,
  36.         MemberPropertyService $propertyService,
  37.         RedisEventManager $redisEvents,
  38.         EventDispatcherInterface $dispatcher
  39.     ) {
  40.         parent::__construct($repository);
  41.         $this->propertyService $propertyService;
  42.         $this->redisEvents $redisEvents;
  43.         $this->setEventDispatcher($dispatcher);
  44.     }
  45.     public function setEventDispatcher(EventDispatcherInterface $dispatcher): EventDispatcherAwareInterface
  46.     {
  47.         $this->dispatcher $dispatcher;
  48.         return $this;
  49.     }
  50.     /**
  51.      * @throws AlreadyHasMemberException
  52.      */
  53.     public function createMemberByAccount(Account $accountstring $username ''): Member
  54.     {
  55.         if ($account->hasMember()) {
  56.             throw new AlreadyHasMemberException(
  57.                 "Can not create member. Account {$account->getId()} has already a member assigned."
  58.             );
  59.         }
  60.         if ('' === $username) {
  61.             $username $this->generateUniqueUsername();
  62.         }
  63.         $member = new Member();
  64.         $member->setUsername($username);
  65.         $member->setAccount($account);
  66.         $account->setMember($member);
  67.         return $member;
  68.     }
  69.     protected function generateUniqueUsername(): string
  70.     {
  71.         $prefix 'User';
  72.         $isUnique false;
  73.         $username $prefix mt_rand(1999999);
  74.         while (!$isUnique) {
  75.             $isUnique null === $this->getMemberByUsername($usernamefalse);
  76.             if (!$isUnique) {
  77.                 $username $prefix mt_rand(1999999);
  78.             }
  79.         }
  80.         return $username;
  81.     }
  82.     public function getMemberByUsername(string $usernamebool $activeOnly): ?Member
  83.     {
  84.         $qb $this->repo->getMemberByUsernameQueryBuilder($username$activeOnly);
  85.         $result $qb->getQuery()->getResult();
  86.         if (count($result)) {
  87.             return $result[0];
  88.         }
  89.         return null;
  90.     }
  91.     /**
  92.      * @throws \Doctrine\ORM\ORMException
  93.      * @throws \Doctrine\ORM\OptimisticLockException
  94.      */
  95.     public function storeMember(Member $member): Member
  96.     {
  97.         $isNew null === $member->getId();
  98.         $this->storeEntity($member);
  99.         if ($isNew) {
  100.             $event = new MemberCreatedEvent($member);
  101.             $this->getEventDispatcher()->dispatch($event);
  102.         } elseif (true === $member->getIsAmateur()) {
  103.             $event = new AmateurUpdateEvent($member);
  104.             $this->getEventDispatcher()->dispatch($event);
  105.         }
  106.         return $member;
  107.     }
  108.     public function getEventDispatcher(): EventDispatcherInterface
  109.     {
  110.         return $this->dispatcher;
  111.     }
  112.     public function getSupportMember(): ?Member
  113.     {
  114.         return $this->getMemberByUsername($this->getSupportUsername(), true);
  115.     }
  116.     public function getSupportUsername(): string
  117.     {
  118.         return $this->supportUsername;
  119.     }
  120.     public function setSupportUsername(string $username): self
  121.     {
  122.         $this->supportUsername $username;
  123.         return $this;
  124.     }
  125.     public function getMemberById(int $id): ?Member
  126.     {
  127.         /**
  128.          * @var $result Member|null
  129.          */
  130.         $result $this->findById($id);
  131.         return $result;
  132.     }
  133.     public function getPublicIndex(int $pageint $limitbool $activeOnly): Paginator
  134.     {
  135.         $qb $this->repo->getIndexQueryBuilder($activeOnly);
  136.         $qb->setMaxResults($limit);
  137.         $qb->setFirstResult($page $limit $limit);
  138.         return new Paginator($qb->getQuery());
  139.     }
  140.     /**
  141.      * @throws \Doctrine\ORM\ORMException
  142.      * @throws \Doctrine\ORM\OptimisticLockException
  143.      */
  144.     public function updateField(Member $memberParameterBag $bag): EntityUpdated
  145.     {
  146.         $field $bag->get('field');
  147.         $value trim($bag->get('value'));
  148.         $updatedToAmateur false;
  149.         $success true;
  150.         switch ($field) {
  151.             case 'username':
  152.                 $member->setUsername($value);
  153.                 break;
  154.             case 'is_active':
  155.                 $member->setIsActive($bag->getBoolean('value'));
  156.                 break;
  157.             case 'is_amateur':
  158.                 if (true === $bag->getBoolean('value') && false === $member->getIsAmateur()) {
  159.                     $updatedToAmateur true;
  160.                 }
  161.                 $member->setIsAmateur($bag->getBoolean('value'));
  162.                 break;
  163.             case 'is_public_profile':
  164.                 $member->setIsPublicProfile($bag->getBoolean('value'));
  165.                 break;
  166.             case 'shall_hardcore':
  167.                 $member->setShallHardcore((bool)$bag->getBoolean('value'));
  168.                 break;
  169.             case 'hardcore_reason':
  170.                 $member->setHardcoreReason($value);
  171.                 break;
  172.             case 'sex':
  173.                 $member->setSex($bag->getInt('value'MemberSex::MALE));
  174.                 break;
  175.             case 'tax-country':
  176.                 $member->setTaxCountry('' === $value null $value);
  177.                 break;
  178.             default:
  179.                 $success false;
  180.                 break;
  181.         }
  182.         if ($success) {
  183.             $this->storeEntity($member);
  184.             if ($updatedToAmateur) {
  185.                 // @TODO: Migrate to Doctrine Events
  186.                 $this->propertyService->initFrontendApiKey($member);
  187.             }
  188.         }
  189.         return new EntityUpdated($success$member->getId());
  190.     }
  191.     /**
  192.      * @throws \Exception
  193.      */
  194.     public function applyValuesToEntity(ParameterBag $bagMember $entity): Member
  195.     {
  196.         if ($bag->has('is_amateur')) {
  197.             $entity->setIsAmateur($bag->getBoolean('is_amateur'false));
  198.         }
  199.         if ($bag->has('is_active')) {
  200.             $entity->setIsActive($bag->getBoolean('is_active'true));
  201.         }
  202.         if ($bag->has('is_webcam_active')) {
  203.             $entity->setIsWebcamActive($bag->getBoolean('is_webcam_active'false));
  204.         }
  205.         if ($bag->has('is_public_profile')) {
  206.             $entity->setIsPublicProfile($bag->getBoolean('is_public_profile'true));
  207.         }
  208.         if ($bag->has('sex')) {
  209.             $entity->setSex($bag->getInt('sex'MemberSex::MALE));
  210.         }
  211.         if ($bag->has('username')) {
  212.             $entity->setUsername($bag->get('username'));
  213.         }
  214.         if ($bag->has('public_country')) {
  215.             $entity->setPublicCountry($bag->get('public_country''DE'));
  216.         }
  217.         if ($bag->has('public_region')) {
  218.             $entity->setPublicRegion($bag->get('public_region'));
  219.         }
  220.         if ($bag->has('public_zipcode')) {
  221.             if ('' === $bag->get('public_zipcode')) {
  222.                 $entity->setPublicZipcode(null);
  223.             } else {
  224.                 $entity->setPublicZipcode($bag->get('public_zipcode'));
  225.             }
  226.         }
  227.         if ($bag->has('date_of_birth')) {
  228.             $entity->setDateOfBirth(new DateTime($bag->get('date_of_birth')));
  229.         }
  230.         if ($bag->has('registration_date')) {
  231.             $entity->setRegistrationDate(new DateTime($bag->get('registration_date')));
  232.         }
  233.         return $entity;
  234.     }
  235.     #[ArrayShape([=> 'bool'=> 'string'])]
  236.     public function changeUsername(Member $memberstring $newUsername): array // TODO: Use DTO
  237.     {
  238.         if (mb_strlen($newUsername) > 32) {
  239.             return [false'Der Benutzername ist zu lang. Bitte auf 32 Zeichen beschränken.'];
  240.         }
  241.         if (!== preg_match('#^[a-zA-Z0-9\-]{2,32}$#'$newUsername)) {
  242.             return [false'Bitte verwende im Benutzernamen nur Buchstaben und Ziffern.'];
  243.         }
  244.         $usernameWithoutSpecials str_replace('-'''$newUsername);
  245.         if (strlen($usernameWithoutSpecials) <= 1) {
  246.             return [false'Bitte verwende im neuen Benutzernamen mehr Buchstaben oder Ziffern.'];
  247.         }
  248.         $newUsernameIsDefault \Frivol\Common\Service\MemberService::qualifiesForChangeUsername($newUsername);
  249.         $oldUsername $member->getUsername();
  250.         if ($newUsernameIsDefault || mb_strtolower($newUsername) === mb_strtolower($oldUsername)) {
  251.             return [false'Bitte wähle einen anderen Benutzername, der sich natürlicher anhört.'];
  252.         }
  253.         $hasDefaultUsername \Frivol\Common\Service\MemberService::qualifiesForChangeUsername($member->getUsername());
  254.         if (!$hasDefaultUsername) {
  255.             $this->propertyService->deleteEntityByMemberAndName(
  256.                 $member,
  257.                 AccountService::PROPERTY_ACCOUNT_USERNAME_CHANGE_PERMITTED
  258.             );
  259.             return [false'Dein Benutzername kann nicht mehr geändert werden.'];
  260.         }
  261.         $property $this->propertyService->getPropertyForMember(
  262.             $member,
  263.             AccountService::PROPERTY_ACCOUNT_USERNAME_CHANGE_PERMITTED
  264.         );
  265.         // If there is no property, the username has already been changed.
  266.         if (!$property instanceof MemberProperty) {
  267.             return [false'Dein Benutzername wurde schon mal geändert und kann momentan nicht mehr geändert werden.'];
  268.         }
  269.         try {
  270.             $member->setUsername($newUsername);
  271.             $this->storeEntity($member);
  272.             $this->propertyService->deleteEntityByMemberAndName(
  273.                 $member,
  274.                 AccountService::PROPERTY_ACCOUNT_USERNAME_CHANGE_PERMITTED
  275.             );
  276.             // Publishing name changed event currently not required
  277.             // $this->redisEvents->publishServerEvent(new UsernameChangedEvent($member->getId(), $oldUsername, $newUsername));
  278.             return [true'Danke! Benutzername wurde geändert.'];
  279.         } catch (\Exception|\Doctrine\ORM\ORMException|ORMException $e) {
  280.             if (false !== stripos($e->getMessage(), 'duplicate entry')) {
  281.                 return [false'Der gewählte Benutzername ist bereits vergeben.'];
  282.             }
  283.             return [false'Es ist ein Fehler aufgetreten. Bitte probiere es nochmal.'];
  284.         }
  285.     }
  286.     public function getMembersAmateurStatus(array $memberIdsbool $activeOnly true): array
  287.     {
  288.         $qb $this->repo->createQueryBuilder('m');
  289.         $qb $qb->select('m.id, m.is_amateur AS isAmateur')
  290.             ->where('m.id IN (:memberIds)')
  291.             ->andWhere('m.is_active = :activeOnly')
  292.             ->setParameters([
  293.                 'memberIds' => $memberIds,
  294.                 'activeOnly' => ($activeOnly 0),
  295.             ])->setMaxResults(1000);
  296.         if ($activeOnly) {
  297.             $qb->join(Account::class, 'a'Join::WITH'a.member = m.id')
  298.                 ->andWhere('a.status = ' AccountStatus::ACTIVE);
  299.         }
  300.         $arrayResult $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
  301.         if (empty($arrayResult)) {
  302.             return [];
  303.         }
  304.         $results = [];
  305.         foreach ($arrayResult as $row) {
  306.             $results[$row['id']] = $row['isAmateur'];
  307.         }
  308.         return $results;
  309.     }
  310.     public function getInaccessible(): array
  311.     {
  312.         return $this->repo->findInaccessible()->getResult();
  313.     }
  314.     public function getAccessibleMemberIds(): array
  315.     {
  316.         return $this->repo->findAccessibleMemberIds();
  317.     }
  318.     public function amateursWithoutFrontendApiKey(): array
  319.     {
  320.         return $this->repo->findAmateursWithoutFrontendApiKey();
  321.     }
  322. }