src/EventSubscriber/ObjectSubscriber.php line 187

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\DBAL\Types\NotificationCommandType;
  4. use App\Entity\Category;
  5. use App\Entity\Notification\Map\EventNotification;
  6. use App\Entity\Notification\Map\StreamNotification;
  7. use App\Entity\Notification\Map\TaskNotification;
  8. use App\Entity\Plan;
  9. use App\Entity\Position;
  10. use App\Entity\Source;
  11. use App\Entity\Tag;
  12. use App\Entity\Template;
  13. use App\Entity\Template\Column as TemplateColumn;
  14. use App\Entity\Template\Task as TemplateTask;
  15. use App\Entity\User;
  16. use App\Entity\UserInterface;
  17. use App\Entity\Workspace;
  18. use App\Entity\Workspace\Member;
  19. use App\Entity\Workspace\Stream;
  20. use App\Entity\Workspace\Stream\Collaborator;
  21. use App\Entity\Workspace\Stream\Column;
  22. use App\Entity\Workspace\Stream\Comment;
  23. use App\Entity\Workspace\Stream\Comment\Collaborator as CommentCollaborator;
  24. use App\Entity\Workspace\Stream\Comment\Map\DocumentComment;
  25. use App\Entity\Workspace\Stream\Comment\Map\EventComment;
  26. use App\Entity\Workspace\Stream\Comment\Map\StreamComment;
  27. use App\Entity\Workspace\Stream\Comment\Map\TaskComment;
  28. use App\Entity\Workspace\Stream\Comment\Reaction as CommentReaction;
  29. use App\Entity\Workspace\Stream\Document;
  30. use App\Entity\Workspace\Stream\Event;
  31. use App\Entity\Workspace\Stream\File;
  32. use App\Entity\Workspace\Stream\File\Map\DocumentFile;
  33. use App\Entity\Workspace\Stream\File\Map\EventFile;
  34. use App\Entity\Workspace\Stream\File\Map\StreamFile;
  35. use App\Entity\Workspace\Stream\File\Map\TaskFile;
  36. use App\Entity\Workspace\Stream\Milestone;
  37. use App\Entity\Workspace\Stream\Task;
  38. use App\Entity\Workspace\Stream\Task\Item as TaskItem;
  39. use App\Entity\Workspace\Stream\Whiteboard;
  40. use App\Event\ObjectDeleteEvent;
  41. use App\Event\ObjectEvent;
  42. use App\Event\ObjectUpdateEvent;
  43. use App\Events;
  44. use App\Message;
  45. use Doctrine\Common\Collections\Collection;
  46. use Doctrine\Common\Util\ClassUtils;
  47. use Doctrine\ORM\EntityManagerInterface;
  48. use Doctrine\ORM\Query\FilterCollection;
  49. use Doctrine\Persistence\ManagerRegistry;
  50. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  51. use Symfony\Component\HttpFoundation\RequestStack;
  52. use Symfony\Component\Messenger\MessageBusInterface;
  53. use Symfony\Component\Security\Core\Security;
  54. /**
  55.  * Process object events.
  56.  */
  57. class ObjectSubscriber implements EventSubscriberInterface
  58. {
  59.     /** @var Security */
  60.     private $security;
  61.     /** @var RequestStack */
  62.     private $requestStack;
  63.     /** @var MessageBusInterface */
  64.     private $messageBus;
  65.     /** @var ManagerRegistry */
  66.     private $managerRegistry;
  67.     /**
  68.      * @param Security            $security
  69.      * @param RequestStack        $requestStack
  70.      * @param MessageBusInterface $messageBus
  71.      * @param ManagerRegistry     $managerRegistry
  72.      */
  73.     public function __construct(
  74.         Security $security,
  75.         RequestStack $requestStack,
  76.         MessageBusInterface $messageBus,
  77.         ManagerRegistry $managerRegistry
  78.     ) {
  79.         $this->security $security;
  80.         $this->requestStack $requestStack;
  81.         $this->messageBus $messageBus;
  82.         $this->managerRegistry $managerRegistry;
  83.     }
  84.     /**
  85.      * {@inheritdoc}
  86.      */
  87.     public static function getSubscribedEvents(): array
  88.     {
  89.         return [
  90.             Events::OBJECT_CREATED => 'onObjectCreated',
  91.             Events::OBJECT_UPDATED => 'onObjectUpdated',
  92.             Events::OBJECT_DELETED => 'onObjectDeleted',
  93.             Events::OBJECT_DUE => 'onObjectDue',
  94.         ];
  95.     }
  96.     /**
  97.      * @param ObjectEvent $event
  98.      */
  99.     public function onObjectCreated(ObjectEvent $event): void
  100.     {
  101.         /** @var mixed $object */
  102.         $object $event->getObject();
  103.         // Validate $object
  104.         switch (ClassUtils::getClass($object)) {
  105.             case Plan::class:
  106.             case Category::class:
  107.             case Position::class:
  108.             case Source::class:
  109.             case Tag::class:
  110.                 $params = [
  111.                     'sendAll' => true,
  112.                 ];
  113.                 // no break
  114.             case Workspace::class:
  115.             case Member::class:
  116.             case Template::class:
  117.             case TemplateColumn::class:
  118.             case TemplateTask::class:
  119.             case Collaborator::class:
  120.             case StreamFile::class:
  121.             case DocumentFile::class:
  122.             case EventFile::class:
  123.             case TaskFile::class:
  124.             case TaskItem::class:
  125.             case Whiteboard::class:
  126.             case Milestone::class:
  127.             case Column::class:
  128.                 $params array_merge(
  129.                     [
  130.                         'ignoreLogging' => true,
  131.                     ],
  132.                     $params ?? [],
  133.                 );
  134.                 // no break
  135.             case Stream::class:
  136.                 if ($object instanceof Stream && $object->isDirectMessage()) {
  137.                     $params = [
  138.                         'ignoreLogging' => true,
  139.                     ];
  140.                 }
  141.                 // no break
  142.             case Document::class:
  143.             case Event::class:
  144.             case Task::class:
  145.                 $this->triggerCommand(Message\NotifyObjectCreation::class, $object$params ?? []);
  146.                 break;
  147.             case StreamComment::class:
  148.             case DocumentComment::class:
  149.             case EventComment::class:
  150.             case TaskComment::class:
  151.                 if (!$object->isInitialized()) {
  152.                     $this->triggerCommand(Message\NotifyCommentCreation::class, $object);
  153.                 }
  154.                 break;
  155.             case CommentReaction::class:
  156.                 if ($object->getComment() && $object->getComment()->getObject()) {
  157.                     $this->triggerCommand(Message\NotifyCommentReaction::class, $object);
  158.                 }
  159.                 break;
  160.             default:
  161.                 return;
  162.         }
  163.     }
  164.     /**
  165.      * @param ObjectUpdateEvent $event
  166.      */
  167.     public function onObjectUpdated(ObjectUpdateEvent $event): void
  168.     {
  169.         /** @var mixed $object */
  170.         $object $event->getObject();
  171.         /** @var array $objectChangeSet */
  172.         $objectChangeSet $event->getObjectChangeSet();
  173.         // Validate $object
  174.         switch (ClassUtils::getClass($object)) {
  175.             case Plan::class:
  176.             case Category::class:
  177.             case Position::class:
  178.             case Source::class:
  179.             case Tag::class:
  180.                 $params = [
  181.                     'sendAll' => true,
  182.                 ];
  183.                 break;
  184.             case StreamNotification::class:
  185.             case DocumentNotification::class:
  186.             case EventNotification::class:
  187.             case TaskNotification::class:
  188.                 if (NotificationCommandType::COMMENT_ADDED !== $object->getCommand()) {
  189.                     return;
  190.                 }
  191.                 break;
  192.             case User::class:
  193.                 foreach (array_keys($objectChangeSet) as $key) {
  194.                     switch ($key) {
  195.                         case 'salt':
  196.                         case 'password':
  197.                         case 'lastLogin':
  198.                             unset($objectChangeSet[$key]);
  199.                             break;
  200.                     }
  201.                 }
  202.                 // no break
  203.             case Workspace::class:
  204.             case Member::class:
  205.             case Template::class:
  206.             case TemplateColumn::class:
  207.             case TemplateTask::class:
  208.             case Stream::class:
  209.             case Collaborator::class:
  210.             case CommentCollaborator::class:
  211.             case StreamComment::class:
  212.             case DocumentComment::class:
  213.             case EventComment::class:
  214.             case TaskComment::class:
  215.             case StreamFile::class:
  216.             case DocumentFile::class:
  217.             case EventFile::class:
  218.             case TaskFile::class:
  219.             case TaskItem::class:
  220.             case Document::class:
  221.             case Whiteboard::class:
  222.             case Milestone::class:
  223.             case Column::class:
  224.                 break;
  225.             case Event::class:
  226.             case Task::class:
  227.                 if ($event->hasChangedField('enabled')) {
  228.                     $this->triggerCommand(Message\NotifyObjectCompletion::class, $object);
  229.                 }
  230.                 break;
  231.             default:
  232.                 return;
  233.         }
  234.         // Remove unfollowed fields
  235.         unset($objectChangeSet['comments']);
  236.         // Skip empty update
  237.         if (empty($objectChangeSet)) {
  238.             return;
  239.         }
  240.         // Prepare $objectChangeSet for notification
  241.         $transformObjectField = function (&$value) {
  242.             if (($value instanceof Proxy || \is_object($value)) && method_exists($value'getId')) {
  243.                 $value = [
  244.                     'id' => (string) $value->getId(),
  245.                     'name' => (string) $value,
  246.                 ];
  247.             } elseif ($value instanceof \DateTime) {
  248.                 $value $value->format('c');
  249.             } elseif ($value instanceof Collection) {
  250.                 $values = [];
  251.                 foreach ($value as $v) {
  252.                     $values[] = [
  253.                         'id' => (string) $v->getId(),
  254.                         'name' => (string) $v,
  255.                     ];
  256.                 }
  257.                 $value $values;
  258.             }
  259.         };
  260.         foreach ($objectChangeSet as &$value) {
  261.             $transformObjectField($value[0]);
  262.             $transformObjectField($value[1]);
  263.         }
  264.         $this->triggerCommand(
  265.             Message\NotifyObjectUpdates::class,
  266.             $object,
  267.             array_merge(
  268.                 [
  269.                     'objectChangeSet' => $objectChangeSet,
  270.                 ],
  271.                 $params ?? [],
  272.             )
  273.         );
  274.     }
  275.     /**
  276.      * @param ObjectDeleteEvent $event
  277.      */
  278.     public function onObjectDeleted(ObjectDeleteEvent $event): void
  279.     {
  280.         /** @var EntityManagerInterface $entityManager */
  281.         $entityManager $this->managerRegistry->getManager();
  282.         /** @var FilterCollection $filters */
  283.         $filters $entityManager->getFilters();
  284.         /** @var mixed $object */
  285.         $object $event->getObject();
  286.         // Skip when permanently deleting "soft-deleted" objects, except for Member or Collaborator
  287.         if (!$filters->isEnabled('soft_deleteable') && !($object instanceof Member || $object instanceof Collaborator)) {
  288.             return;
  289.         }
  290.         /** @var string $objectId */
  291.         $objectId $event->getObjectId();
  292.         // Validate $object
  293.         switch (ClassUtils::getClass($object)) {
  294.             case Plan::class:
  295.             case Category::class:
  296.             case Position::class:
  297.             case Source::class:
  298.             case Tag::class:
  299.                 $params = [
  300.                     'sendAll' => true,
  301.                 ];
  302.                 break;
  303.             case StreamNotification::class:
  304.             case DocumentNotification::class:
  305.             case EventNotification::class:
  306.             case TaskNotification::class:
  307.                 $params = [
  308.                     'ownerId' => $object->getOwner()->getId(),
  309.                 ];
  310.                 break;
  311.             case User::class:
  312.             case Workspace::class:
  313.             case Template::class:
  314.             case Stream::class:
  315.                 break;
  316.             case Member::class:
  317.                 $parentObject $object->getWorkspace();
  318.                 if ($object->getUser()) {
  319.                     $params = [
  320.                         'userId' => $object->getUser()->getId(),
  321.                     ];
  322.                 }
  323.                 break;
  324.             case TemplateColumn::class:
  325.             case TemplateTask::class:
  326.                 $parentObject $object->getTemplate();
  327.                 break;
  328.             case Collaborator::class:
  329.                 if ($object->getUser()) {
  330.                     $params = [
  331.                         'userId' => $object->getUser()->getId(),
  332.                     ];
  333.                 }
  334.                 // no break
  335.             case Whiteboard::class:
  336.             case Milestone::class:
  337.                 $ignoreLogging true;
  338.                 // no break
  339.             case Document::class:
  340.             case Event::class:
  341.             case Task::class:
  342.             case Column::class:
  343.                 $ignoreLogging $ignoreLogging ?? false;
  344.                 // no break
  345.             case StreamComment::class:
  346.             case DocumentComment::class:
  347.             case EventComment::class:
  348.             case TaskComment::class:
  349.                 if ($object instanceof Comment && $object->getParent()) {
  350.                     $params = [
  351.                         'commentParentId' => $object->getParent()->getId(),
  352.                     ];
  353.                 }
  354.                 // no break
  355.             case StreamFile::class:
  356.             case DocumentFile::class:
  357.             case EventFile::class:
  358.             case TaskFile::class:
  359.                 $parentObject $object->getStream();
  360.                 break;
  361.             case TaskItem::class:
  362.                 $parentObject $object->getTask()->getStream();
  363.                 break;
  364.             default:
  365.                 return;
  366.         }
  367.         $this->triggerCommand(
  368.             Message\NotifyObjectDeletion::class,
  369.             $parentObject ?? $object,
  370.             array_merge(
  371.                 [
  372.                     'objectId' => (string) $objectId,
  373.                     'ignoreLogging' => $ignoreLogging ?? true,
  374.                     'params' => [
  375.                         'objectName' => (string) $object,
  376.                         'objectType' => $this->getObjectName($object),
  377.                     ],
  378.                 ],
  379.                 $params ?? [],
  380.             )
  381.         );
  382.     }
  383.     /**
  384.      * @param ObjectEvent $event
  385.      */
  386.     public function onObjectDue(ObjectEvent $event): void
  387.     {
  388.         /** @var mixed $object */
  389.         $object $event->getObject();
  390.         // Validate $object
  391.         switch (ClassUtils::getClass($object)) {
  392.             case Document::class:
  393.             case Event::class:
  394.             case Task::class:
  395.                 $this->triggerCommand(
  396.                     Message\NotifyObjectDue::class,
  397.                     $object,
  398.                     [
  399.                         'params' => [
  400.                             'startDate' => $object->getStartDate() ? $object->getStartDate()->format('c') : null,
  401.                             'endDate' => $object->getEndDate() ? $object->getEndDate()->format('c') : null,
  402.                         ],
  403.                     ]
  404.                 );
  405.                 break;
  406.             default:
  407.                 return;
  408.         }
  409.     }
  410.     /**
  411.      * @param mixed $object
  412.      *
  413.      * @return string
  414.      */
  415.     protected function getObjectName($object): string
  416.     {
  417.         if ($object instanceof Comment) {
  418.             return 'Comment';
  419.         } elseif ($object instanceof File) {
  420.             return 'File';
  421.         } elseif ($object instanceof TaskItem) {
  422.             return 'TaskItem';
  423.         } elseif ($object instanceof TemplateColumn) {
  424.             return 'TemplateColumn';
  425.         } elseif ($object instanceof TemplateTask) {
  426.             return 'TemplateTask';
  427.         }
  428.         return implode('', \array_slice(explode('\\', \get_class($object)), -1));
  429.     }
  430.     /**
  431.      * @param string $messageClass
  432.      * @param mixed  $object
  433.      * @param array  $params
  434.      */
  435.     private function triggerCommand(string $messageClass$object, array $params = []): void
  436.     {
  437.         /** @var UserInterface|null $user */
  438.         if ($user $this->getUser()) {
  439.             $params['loginUser'] = (string) $user->getId();
  440.         }
  441.         // Dispatch message
  442.         $this->messageBus->dispatch(
  443.             new $messageClass(
  444.                 $object->getId() ?? $param['objectId'] ?? null,
  445.                 array_merge(
  446.                     $params,
  447.                     [
  448.                         'objectClass' => ClassUtils::getClass($object),
  449.                     ]
  450.                 )
  451.             )
  452.         );
  453.     }
  454.     /**
  455.      * @return UserInterface|null
  456.      */
  457.     private function getUser(): ?UserInterface
  458.     {
  459.         try {
  460.             /** @var SessionInterface $session */
  461.             $session $this->requestStack->getSession();
  462.             if ($user $session->get('app_user')) {
  463.                 return $user;
  464.             }
  465.         } catch (SessionNotFoundException $e) {
  466.             // Do nothing
  467.         }
  468.         return $this->security->getUser();
  469.     }
  470. }