src/Products/NotificationsBundle/Entity/Automation.php line 39

Open in your IDE?
  1. <?php
  2. namespace Products\NotificationsBundle\Entity;
  3. use App\Entity\System\School;
  4. use App\Model\Async\StringSemaphoreInterface;
  5. use App\Model\Async\StringSemaphoreTrait;
  6. use App\Model\Query\ConditionQuery\Condition\ContainsCondition;
  7. use App\Model\Query\ConditionQuery\Condition\EqualCondition;
  8. use App\Model\Query\ConditionQuery\Condition\LessThanCondition;
  9. use App\Model\Query\ConditionQuery\Condition\NullCondition;
  10. use App\Model\Query\ConditionQuery\ConditionGroup;
  11. use App\Model\Query\ConditionQuery\ConditionGroupInterface;
  12. use App\Model\Query\ConditionQuery\ConditionQuery;
  13. use App\Model\Schedule\ScheduleInterface;
  14. use App\Model\Schedule\ScheduleTrait;
  15. use Cms\CoreBundle\Model\Interfaces\Loggable\LoggableInterface;
  16. use Cms\TenantBundle\Entity\TenantedEntity;
  17. use DateTime;
  18. use Doctrine\Common\Collections\ArrayCollection;
  19. use Doctrine\Common\Collections\Collection;
  20. use Doctrine\Common\Collections\Criteria;
  21. use Doctrine\ORM\Mapping as ORM;
  22. use Products\NotificationsBundle\Entity\Notifications\Invocation;
  23. use Products\NotificationsBundle\Entity\Notifications\Template;
  24. use Products\NotificationsBundle\Model\AutomationList;
  25. use Products\NotificationsBundle\Util\ListBuilder\AbstractListBuilder;
  26. use Reinder83\BinaryFlags\Bits;
  27. /**
  28.  *
  29.  * @ORM\Entity(
  30.  *     repositoryClass = "Products\NotificationsBundle\Doctrine\Repository\AutomationRepository",
  31.  * )
  32.  * @ORM\Table(
  33.  *     name = "notis__automation",
  34.  * )
  35.  */
  36. class Automation extends TenantedEntity implements LoggableInterfaceStringSemaphoreInterfaceScheduleInterface
  37. {
  38.     public const TYPES__GENERAL 0;
  39.     public const TYPES__ATTENDANCE Bits::BIT_1;
  40.     public const TYPES = [
  41.         'general' => self::TYPES__GENERAL,
  42.         'attendance' => self::TYPES__ATTENDANCE,
  43.     ];
  44.     use StringSemaphoreTrait;
  45.     use ScheduleTrait;
  46.     /**
  47.      * @var bool
  48.      *
  49.      * @ORM\Column(
  50.      *     type = "boolean",
  51.      *     nullable = false,
  52.      *     options = {
  53.      *         "default" = true,
  54.      *     },
  55.      * )
  56.      */
  57.     protected bool $active true;
  58.     /**
  59.      * @var string|null
  60.      *
  61.      * @ORM\Column(
  62.      *     type = "string",
  63.      *     nullable = false,
  64.      *     length = 255,
  65.      * )
  66.      */
  67.     protected ?string $name null;
  68.     /**
  69.      * @var School|null
  70.      *
  71.      * @ORM\ManyToOne(
  72.      *     targetEntity = School::class,
  73.      * )
  74.      * @ORM\JoinColumn(
  75.      *     name = "school",
  76.      *     referencedColumnName = "id",
  77.      *     nullable = true,
  78.      *     onDelete = "SET NULL",
  79.      * )
  80.      */
  81.     protected ?School $school null;
  82.     /**
  83.      * @var ConditionQuery|null
  84.      *
  85.      * @ORM\Column(
  86.      *     type = "condition_query",
  87.      *     nullable = false,
  88.      * )
  89.      */
  90.     protected ?ConditionQuery $conditionQuery null;
  91.     /**
  92.      * @var Template|null
  93.      *
  94.      * @ORM\ManyToOne(
  95.      *     targetEntity = Template::class,
  96.      * )
  97.      * @ORM\JoinColumn(
  98.      *     name = "template",
  99.      *     referencedColumnName = "id",
  100.      *     nullable = true,
  101.      *     onDelete = "SET NULL",
  102.      * )
  103.      */
  104.     protected ?Template $template null;
  105.     /**
  106.      * @var bool
  107.      *
  108.      * @ORM\Column(
  109.      *     type = "boolean",
  110.      *     options = {
  111.      *         "default" = false,
  112.      *     },
  113.      * )
  114.      */
  115.     protected bool $trackable false;
  116.     /**
  117.      * @var int
  118.      *
  119.      * @ORM\Column(
  120.      *     type = "integer",
  121.      *     options = {
  122.      *         "default" = 0,
  123.      *     },
  124.      * )
  125.      */
  126.     protected int $trackableWindow 0;
  127.     /**
  128.      * @var int
  129.      *
  130.      * @ORM\Column(
  131.      *     type = "integer",
  132.      *     nullable = false,
  133.      *     options = {
  134.      *         "default" = self::TYPES__GENERAL,
  135.      *         "unsigned" = true,
  136.      *     },
  137.      * )
  138.      */
  139.     protected int $type self::TYPES__GENERAL;
  140.     /**
  141.      * @var Collection|Invocation[]
  142.      *
  143.      * @ORM\OneToMany(
  144.      *     targetEntity = Invocation::class,
  145.      *     mappedBy = "automation",
  146.      * )
  147.      * @ORM\OrderBy({
  148.      *     "createdAt" = "DESC",
  149.      * })
  150.      */
  151.     protected Collection $invocations;
  152.     public function __construct()
  153.     {
  154.         $this->invocations = new ArrayCollection();
  155.     }
  156.     /**
  157.      * @return string|null
  158.      */
  159.     public function getName(): ?string
  160.     {
  161.         return $this->name;
  162.     }
  163.     /**
  164.      * @param string|null $name
  165.      * @return self
  166.      */
  167.     public function setName(?string $name): self
  168.     {
  169.         $this->name $name;
  170.         return $this;
  171.     }
  172.     /**
  173.      * @return School|null
  174.      */
  175.     public function getSchool(): ?School
  176.     {
  177.         return $this->school;
  178.     }
  179.     /**
  180.      * @param School|null $school
  181.      * @return self
  182.      */
  183.     public function setSchool(?School $school): self
  184.     {
  185.         $this->school $school;
  186.         return $this;
  187.     }
  188.     /**
  189.      * @return bool
  190.      */
  191.     public function isActive(): bool
  192.     {
  193.         return $this->active;
  194.     }
  195.     /**
  196.      * @param bool $active
  197.      * @return self
  198.      */
  199.     public function setActive(bool $active): self
  200.     {
  201.         $this->active $active;
  202.         return $this;
  203.     }
  204.     /**
  205.      * This sets the "raw" query that the user defines.
  206.      * For most purposes, you likely want to use the "getRunnableConditionQuery" method.
  207.      *
  208.      * @return ConditionQuery|null
  209.      * @internal
  210.      */
  211.     public function getConditionQuery(): ?ConditionQuery
  212.     {
  213.         return $this->conditionQuery;
  214.     }
  215.     /**
  216.      * @param ConditionQuery|null $conditionQuery
  217.      * @return self
  218.      */
  219.     public function setConditionQuery(?ConditionQuery $conditionQuery): self
  220.     {
  221.         $this->conditionQuery $conditionQuery;
  222.         return $this;
  223.     }
  224.     /**
  225.      * Takes the user-provided query and performs any final "transformations" to it.
  226.      * This adds things like the additional pieces required to run "trackable" automations, etc.
  227.      *
  228.      * @return ConditionQuery|null
  229.      */
  230.     public function getRunnableConditionQuery(): ?ConditionQuery
  231.     {
  232.         // get teh base query
  233.         $conditionQuery $this->getConditionQuery();
  234.         if ( ! $conditionQuery instanceof ConditionQuery) {
  235.             return null;
  236.         }
  237.         // determine if the school needs added
  238.         // attach it if needed
  239.         $school $this->getSchool();
  240.         if ($school && ! $school->isTypeDistrict() && $school->getOneRosterOrg()) {
  241.             $conditionQuery = new ConditionQuery(
  242.                 $conditionQuery->getEntity(),
  243.                 [
  244.                     new ContainsCondition(
  245.                         sprintf(
  246.                             '%s.metadata/_orgs',
  247.                             $conditionQuery->getEntityAlias(),
  248.                         ),
  249.                         [$school->getOneRosterOrg()]
  250.                     ),
  251.                     new ConditionGroup(
  252.                         $conditionQuery->getConditions(),
  253.                         $conditionQuery->getMode()
  254.                     ),
  255.                 ],
  256.                 ConditionGroupInterface::MODES__AND
  257.             );
  258.         }
  259.         // if this is not a trackable query, then we are done
  260.         if ( ! $this->isTrackable()) {
  261.             return $conditionQuery;
  262.         }
  263.         // only profile condition query is "trackable"
  264.         if ($conditionQuery->getEntity() !== ConditionQuery::PROFILE_ENTITY) {
  265.             return $conditionQuery;
  266.         }
  267.         $trackableDate null;
  268.         if ($this->getTrackableWindow() > 0) {
  269.             // TODO: should we be timestamping people based on the start of the day instead of using NOW()?
  270.             // TODO: does customer timezone have any affect on this calculation?
  271.             $trackableDate = new DateTime(
  272.                 sprintf(
  273.                     '-%d days',
  274.                     $this->getTrackableWindow()
  275.                 )
  276.             );
  277.         }
  278.         // if window equals zero: (automations.automation IS NULL ) AND (...other clauses generated from customers query...)
  279.         // if window greater than zero: (automations.automation IS NULL OR (automations.automation  = automationId AND automations.timestamp < :timestamp)) AND (...other clauses generated from customers query...)
  280.         return new ConditionQuery(
  281.             $conditionQuery->getEntity(),
  282.             [
  283.                 new ConditionGroup(
  284.                     array_filter([
  285.                         new NullCondition(
  286.                             AbstractListBuilder::ENTITIES__PROFILE_AUTOMATION_RECORDS '.automation',
  287.                         ),
  288.                         // if trackable window is 0 (i.e. no date calculated), we send only once
  289.                         // so, the following condition group is not needed for a window of 0
  290.                         $trackableDate ? new ConditionGroup(
  291.                             [
  292.                                 new EqualCondition(
  293.                                     AbstractListBuilder::ENTITIES__PROFILE_AUTOMATION_RECORDS '.automation',
  294.                                     $this->getId()
  295.                                 ),
  296.                                 new LessThanCondition(
  297.                                     AbstractListBuilder::ENTITIES__PROFILE_AUTOMATION_RECORDS '.timestamp',
  298.                                     $trackableDate->format('Y-m-d H:m:i')
  299.                                 ),
  300.                             ],
  301.                             ConditionGroupInterface::MODES__AND,
  302.                         ) : null,
  303.                     ]), ConditionGroupInterface::MODES__OR
  304.                 ),
  305.                 new ConditionGroup(
  306.                     $conditionQuery->getConditions(),
  307.                     $conditionQuery->getMode()
  308.                 ),
  309.             ],
  310.             ConditionGroupInterface::MODES__AND
  311.         );
  312.     }
  313.     /**
  314.      * @return Template|null
  315.      */
  316.     public function getTemplate(): ?Template
  317.     {
  318.         return $this->template;
  319.     }
  320.     /**
  321.      * @param Template|null $template
  322.      * @return self
  323.      */
  324.     public function setTemplate(?Template $template): self
  325.     {
  326.         $this->template $template;
  327.         return $this;
  328.     }
  329.     /**
  330.      * Returns a ConditionList that uses the "runnable" condition query for the automation.
  331.      * This is the condition query that applies other logic (like "trackable" settings) to whatever the user has provided.
  332.      * If a condition query hasn't been set yet, this returns NULL.
  333.      *
  334.      * @return AutomationList|null
  335.      */
  336.     public function getRunnableConditionList(): ?AutomationList
  337.     {
  338.         $conditionQuery $this->getRunnableConditionQuery();
  339.         if ( ! $conditionQuery instanceof ConditionQuery) {
  340.             return null;
  341.         }
  342.         return (new AutomationList($conditionQuery))
  343.             ->setTenant($this->getTenant());
  344.     }
  345.     /**
  346.      * @return bool
  347.      */
  348.     public function isTrackable(): bool
  349.     {
  350.         return $this->trackable;
  351.     }
  352.     /**
  353.      * @param bool $trackable
  354.      * @return self
  355.      */
  356.     public function setTrackable(bool $trackable): self
  357.     {
  358.         $this->trackable $trackable;
  359.         return $this;
  360.     }
  361.     /**
  362.      * @return int
  363.      */
  364.     public function getTrackableWindow(): int
  365.     {
  366.         return $this->trackableWindow;
  367.     }
  368.     /**
  369.      * @param int $trackableWindow
  370.      * @return self
  371.      */
  372.     public function setTrackableWindow(int $trackableWindow): self
  373.     {
  374.         $this->trackableWindow max(0$trackableWindow);
  375.         return $this;
  376.     }
  377.     /**
  378.      * @return int
  379.      */
  380.     public function getType(): int
  381.     {
  382.         return $this->type;
  383.     }
  384.     /**
  385.      * @param int $type
  386.      * @return self
  387.      */
  388.     public function setType(int $type): self
  389.     {
  390.         $this->type $type;
  391.         return $this;
  392.     }
  393.     /**
  394.      * {@inheritDoc}
  395.      */
  396.     public function getLoggableDetails(): array
  397.     {
  398.         return [
  399.             'id' => (string)$this->getId(),
  400.             'title' => $this->getName(),
  401.         ];
  402.     }
  403.     /**
  404.      * @param Criteria|null $criteria
  405.      * @return Collection|Invocation[]
  406.      */
  407.     public function getInvocations(?Criteria $criteria null): Collection
  408.     {
  409.         if ( ! empty($criteria)) {
  410.             return $this->invocations->matching($criteria);
  411.         }
  412.         return $this->invocations;
  413.     }
  414.     /**
  415.      * @return Invocation|null
  416.      */
  417.     public function getLastInvocation(): ?Invocation
  418.     {
  419.         if ($this->invocations->isEmpty()) {
  420.             return null;
  421.         }
  422.         return $this->getInvocations(new Criteria(null, ['createdAt' => 'DESC'], null1))->first();
  423.     }
  424.     /**
  425.      * @param Collection $invocations
  426.      * @return self
  427.      */
  428.     public function setInvocations(Collection $invocations): self
  429.     {
  430.         $this->invocations $invocations;
  431.         return $this;
  432.     }
  433. }