src/Products/NotificationsBundle/Service/ListItemProvider.php line 148

Open in your IDE?
  1. <?php
  2. namespace Products\NotificationsBundle\Service;
  3. use App\Doctrine\Repository\SearchableRepositoryInterface;
  4. use App\Model\Query\ConditionQuery\ConditionQuery;
  5. use App\Model\Searching\AbstractSearch;
  6. use Cms\CoreBundle\Doctrine\Hydrators\SingleColumnHydrator;
  7. use Cms\CoreBundle\Util\Doctrine\EntityManager;
  8. use Doctrine\ORM\AbstractQuery;
  9. use Doctrine\ORM\Query\Expr\Join;
  10. use Doctrine\ORM\QueryBuilder;
  11. use Doctrine\ORM\Tools\Pagination\Paginator;
  12. use Products\NotificationsBundle\Entity\AbstractList;
  13. use Products\NotificationsBundle\Entity\AbstractNotification;
  14. use Products\NotificationsBundle\Entity\AbstractRecipient;
  15. use Products\NotificationsBundle\Entity\Lists\ConditionList;
  16. use Products\NotificationsBundle\Entity\Notifications\Invocation;
  17. use Products\NotificationsBundle\Entity\Profile;
  18. use Products\NotificationsBundle\Entity\Recipients\PhoneRecipient;
  19. use Products\NotificationsBundle\Entity\Student;
  20. use Products\NotificationsBundle\Form\Forms\Profiles\ProfileBuilderSearchForm;
  21. use Products\NotificationsBundle\Form\Forms\Profiles\ProfileSearchForm;
  22. use Products\NotificationsBundle\Form\Forms\Students\StudentBuilderSearchForm;
  23. use Products\NotificationsBundle\Form\Forms\Students\StudentSearchForm;
  24. use Products\NotificationsBundle\Model\Searching\ProfileSearch;
  25. use Products\NotificationsBundle\Model\Searching\StudentSearch;
  26. use Products\NotificationsBundle\Service\ChannelHandlers\AbstractChannelHandler;
  27. use Products\NotificationsBundle\Util\ListBuilder\AbstractListBuilder;
  28. use Products\NotificationsBundle\Util\Preferences;
  29. class ListItemProvider
  30. {
  31.     public const ITEM__TYPE_PROFILES 'profiles';
  32.     public const ITEM__TYPE_STUDENTS 'students';
  33.     public const SEARCH__CLASS_MAP = [
  34.         self::ITEM__TYPE_PROFILES => ProfileSearch::class,
  35.         self::ITEM__TYPE_STUDENTS => StudentSearch::class,
  36.     ];
  37.     public const SEARCH__FORM_CLASS_MAP = [
  38.         self::ITEM__TYPE_PROFILES => ProfileSearchForm::class,
  39.         self::ITEM__TYPE_STUDENTS => StudentSearchForm::class,
  40.     ];
  41.     public const BUILDER__SEARCH_FORM_CLASS_MAP = [
  42.         self::ITEM__TYPE_PROFILES => ProfileBuilderSearchForm::class,
  43.         self::ITEM__TYPE_STUDENTS => StudentBuilderSearchForm::class,
  44.     ];
  45.     public const ITEM__TYPE_CLASS_MAP = [
  46.         self::ITEM__TYPE_PROFILES => Profile::class,
  47.         self::ITEM__TYPE_STUDENTS => Student::class,
  48.     ];
  49.     /**
  50.      * @var EntityManager
  51.      */
  52.     private EntityManager $em;
  53.     /**
  54.      * @var ListBuilderService
  55.      */
  56.     private ListBuilderService $listBuilderService;
  57.     /**
  58.      * @param EntityManager $em
  59.      * @param ListBuilderService $listBuilderService
  60.      */
  61.     public function __construct(EntityManager $emListBuilderService $listBuilderService)
  62.     {
  63.         $this->em $em;
  64.         $this->listBuilderService $listBuilderService;
  65.     }
  66.     /**
  67.      * @param AbstractList $list
  68.      * @param AbstractSearch $search
  69.      * @param int|null $limit
  70.      * @param int|null $offset
  71.      * @return Paginator
  72.      */
  73.     public function getItems(
  74.         AbstractList $list,
  75.         AbstractSearch $search,
  76.         ?int $limit null,
  77.         ?int $offset null
  78.     ): Paginator
  79.     {
  80.         // get the repository, should be a searchable one
  81.         $repo $this->em->getRepository($list->getEntityClass());
  82.         if ( ! $repo instanceof SearchableRepositoryInterface) {
  83.             throw new \RuntimeException();
  84.         }
  85.         return $repo->findBySearch(
  86.             $search,
  87.             $limit,
  88.             $offset,
  89.             $this->listBuilderService->build(
  90.                 $list,
  91.                 $list->getEntityClass(),
  92.             ),
  93.         );
  94.     }
  95.     /**
  96.      * @param AbstractList $list
  97.      * @return string
  98.      */
  99.     public function getItemType(AbstractList $list): string
  100.     {
  101.         if ($list instanceof ConditionList &&
  102.             ($conditionQuery $list->getConditionQuery()) instanceof ConditionQuery &&
  103.             $conditionQuery->getEntity(true) === ConditionQuery::STUDENT_ENTITY) {
  104.             return self::ITEM__TYPE_STUDENTS;
  105.         }
  106.         return self::ITEM__TYPE_PROFILES;
  107.     }
  108.     /**
  109.      * Counts all the total items in the database for the "entity" class of a given list.
  110.      *
  111.      * @param AbstractList $list
  112.      * @return int
  113.      */
  114.     public function count(AbstractList $list): int
  115.     {
  116.         return $this->em->getRepository($list->getEntityClass())->count([]);
  117.     }
  118.     /**
  119.      * Counts only the items in the given list, based on that lists "entity" class.
  120.      *
  121.      * @param array<AbstractList>|AbstractList $lists
  122.      * @return int
  123.      */
  124.     public function countByLists($lists): int
  125.     {
  126.         if ( ! $lists) {
  127.             return 0;
  128.         }
  129.         return $this->listBuilderService
  130.             ->count($lists)
  131.             ->getQuery()
  132.             ->getSingleScalarResult();
  133.     }
  134.     /**
  135.      * @param AbstractNotification $notification
  136.      * @param int $preferences
  137.      * @param string|null $recipientType
  138.      * @return array<int>|array<array<string,int>>
  139.      */
  140.     public function identifyByNotification(
  141.         AbstractNotification $notification,
  142.         int $preferences,
  143.         ?string $recipientType null
  144.     ): array
  145.     {
  146.         // create the base query builder for identification based on this notification's lists
  147.         $qb $this->identifyByLists(
  148.             $notification->getListsAsArray(),
  149.         );
  150.         // apply the main preferences to the query
  151.         $qb
  152.             ->andWhere(
  153.                 sprintf(
  154.                     'BIT_AND(%s.%s, :preferences) > 0',
  155.                     AbstractListBuilder::ENTITIES__PROFILE_CONTACTS,
  156.                     // urgent setting determines if primary or secondary preferences are used
  157.                     $notification->isUrgent() ? 'primaryPreferences' 'secondaryPreferences',
  158.                 )
  159.             )
  160.             ->setParameter('preferences'$preferences);
  161.         // if we are using a phone type, we need to do some extra work...
  162.         if (($preferences & (Preferences::PREFERENCES__SMS Preferences::PREFERENCES__VOICE))) {
  163.             $qb
  164.                 ->andWhere(AbstractListBuilder::ENTITIES__RECIPIENTS.'.method IN (:methods)')
  165.                 ->setParameter(
  166.                     'methods',
  167.                     array_values(array_filter(array_unique(array_merge(
  168.                         ($preferences Preferences::PREFERENCES__SMS) ? PhoneRecipient::SMS_METHODS : [],
  169.                         ($preferences Preferences::PREFERENCES__VOICE) ? PhoneRecipient::VOICE_METHODS : [],
  170.                     )))),
  171.                 );
  172.         }
  173.         // track hydration
  174.         $hydrator SingleColumnHydrator::HYDRATOR;
  175.         // handle invocations
  176.         if ($notification instanceof Invocation) {
  177.             $itemEntity AbstractListBuilder::ENTITIES__PROFILES;
  178.             $itemId AbstractChannelHandler::ITEMS__PROFILE;
  179.             if ($notification->getConditionQuery()->getEntity(true) === ConditionQuery::STUDENT_ENTITY) {
  180.                 $itemEntity AbstractListBuilder::ENTITIES__STUDENTS;
  181.                 $itemId AbstractChannelHandler::ITEMS__STUDENT;
  182.             }
  183.             // modify the query
  184.             $qb
  185.                 ->select(
  186.                     sprintf(
  187.                         '%s.id AS %s, %s.id AS %s',
  188.                         AbstractListBuilder::ENTITIES__RECIPIENTS,
  189.                         AbstractChannelHandler::ITEMS__TARGET,
  190.                         $itemEntity,
  191.                         $itemId,
  192.                     )
  193.                 )
  194.                 ->addGroupBy(AbstractListBuilder::ENTITIES__RECIPIENTS.'.id')
  195.                 ->addGroupBy($itemEntity.'.id');
  196.             if ($itemEntity === AbstractListBuilder::ENTITIES__STUDENTS) {
  197.                 $qb
  198.                     ->leftJoin(
  199.                         AbstractListBuilder::ENTITIES__PROFILES '.contacts',
  200.                         AbstractListBuilder::ENTITIES__PROFILE_CONTACTS,
  201.                     )
  202.                     ->leftJoin(
  203.                         AbstractListBuilder::ENTITIES__PROFILE_CONTACTS '.recipient',
  204.                         AbstractListBuilder::ENTITIES__RECIPIENTS,
  205.                     );
  206.             }
  207.             // need to use a different hydrator as we are returning an array of information
  208.             $hydrator AbstractQuery::HYDRATE_ARRAY;
  209.         }
  210.         // need to replace the join with the proper Recipient class
  211.         if ($recipientType !== null) {
  212.             $joinDqlPart $qb->getDQLPart('join');
  213.             $qb->resetDQLPart('join');
  214.             $rootAlias $qb->getRootAliases()[0];
  215.             /** @var Join $join */
  216.             foreach ($joinDqlPart[$rootAlias] as $join) {
  217.                 if ($join->getAlias() === AbstractListBuilder::ENTITIES__RECIPIENTS) {
  218.                     $join = new Join(
  219.                         $join->getJoinType(),
  220.                         AbstractRecipient::DISCRS[$recipientType],
  221.                         $join->getAlias(),
  222.                         Join::WITH,
  223.                         AbstractListBuilder::ENTITIES__PROFILE_CONTACTS '.recipient = ' AbstractListBuilder::ENTITIES__RECIPIENTS '.id'
  224.                     );
  225.                 }
  226.                 $qb->add('join', [$rootAlias => $join], true);
  227.             }
  228.         }
  229.         // perform the query
  230.         return $qb
  231.             ->getQuery()
  232.             ->getResult($hydrator);
  233.     }
  234.     /**
  235.      * @param AbstractList|array<AbstractList> $lists
  236.      * @return QueryBuilder
  237.      */
  238.     protected function identifyByLists($lists): QueryBuilder
  239.     {
  240.         // normalize lists
  241.         $lists = !is_array($lists) ? [$lists] : $lists;
  242.         // create the basic query builder, will need to apply preference logic to it later
  243.         $qb $this->listBuilderService->identify($lists);
  244.         // ensure the recipients are active and their contacts enabled
  245.         $qb
  246.             ->andWhere(AbstractListBuilder::ENTITIES__RECIPIENTS.'.active = :active')
  247.             ->setParameter('active'true)
  248.             ->andWhere(AbstractListBuilder::ENTITIES__PROFILE_CONTACTS.'.enabled = :enabled')
  249.             ->setParameter('enabled'true);
  250.         return $qb
  251.             ->resetDQLParts(['select''orderBy'])
  252.             ->select(
  253.                 sprintf(
  254.                     'DISTINCT(%s.id)',
  255.                     AbstractListBuilder::ENTITIES__RECIPIENTS
  256.                 )
  257.             );
  258.     }
  259. }