diff --git a/Annotation/Notifiable.php b/Annotation/Notifiable.php new file mode 100644 index 0000000..1e60d4d --- /dev/null +++ b/Annotation/Notifiable.php @@ -0,0 +1,44 @@ +name; + } + + /** + * @param mixed $name + * + * @return Notifiable + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + +} \ No newline at end of file diff --git a/Controller/NotificationController.php b/Controller/NotificationController.php index 65c0548..d2467ca 100755 --- a/Controller/NotificationController.php +++ b/Controller/NotificationController.php @@ -2,13 +2,12 @@ namespace Mgilet\NotificationBundle\Controller; -use Mgilet\NotificationBundle\Model\AbstractNotification; -use Mgilet\NotificationBundle\Model\UserNotificationInterface; +use Mgilet\NotificationBundle\Entity\Notification; +use Mgilet\NotificationBundle\NotifiableInterface; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\Security\Core\Exception\AuthenticationException; /** * Class NotificationController @@ -20,33 +19,44 @@ class NotificationController extends Controller /** * List of all notifications * - * @Route("/", name="notifications_list") + * @Route("/{notifiable}", name="notification_list") * @Method("GET") - * @throws \LogicException + * @param NotifiableInterface $notifiable + * + * @return \Symfony\Component\HttpFoundation\Response */ - public function listAction() + public function listAction($notifiable) { + $notifiableRepo = $this->get('doctrine.orm.entity_manager')->getRepository('MgiletNotificationBundle:NotifiableNotification'); return $this->render('MgiletNotificationBundle::notifications.html.twig', array( - 'notifications' => $this->get('mgilet.notification')->getUserNotifications($this->getUser()) + 'notifiableNotifications' => $notifiableRepo->findAllForNotifiableId($notifiable) )); } /** * Set a Notification as seen * - * @Route("/{notification}/mark_as_seen", name="notification_mark_as_seen") + * @Route("/{notifiable}/mark_as_seen/{notification}", name="notification_mark_as_seen") * @Method("POST") - * @param AbstractNotification $notification + * @param int $notifiable + * @param Notification $notification + * * @return JsonResponse + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\EntityNotFoundException * @throws \LogicException */ - public function markAsSeenAction($notification) + public function markAsSeenAction($notifiable, $notification) { - $em = $this->getDoctrine()->getEntityManager(); - $notification = $this->get('mgilet.notification')->getNotificationById($notification); - $notification->setSeen(true); - $em->persist($notification); - $em->flush(); + $manager = $this->get('mgilet.notification'); + $manager->markAsSeen( + $manager->getNotifiableInterface($manager->getNotifiableEntityById($notifiable)), + $manager->getNotification($notification), + true + ); return new JsonResponse(true); } @@ -54,19 +64,27 @@ public function markAsSeenAction($notification) /** * Set a Notification as unseen * - * @Route("/{notification}/mark_as_unseen", name="notification_mark_as_unseen") + * @Route("/{notifiable}/mark_as_unseen/{notification}", name="notification_mark_as_unseen") * @Method("POST") - * @param AbstractNotification $notification + * @param $notifiable + * @param $notification + * * @return JsonResponse + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\EntityNotFoundException * @throws \LogicException */ - public function markAsUnSeenAction($notification) + public function markAsUnSeenAction($notifiable, $notification) { - $em = $this->getDoctrine()->getEntityManager(); - $notification = $this->get('mgilet.notification')->getNotificationById($notification); - $notification->setSeen(false); - $em->persist($notification); - $em->flush(); + $manager = $this->get('mgilet.notification'); + $manager->markAsUnseen( + $manager->getNotifiableInterface($manager->getNotifiableEntityById($notifiable)), + $manager->getNotification($notification), + true + ); return new JsonResponse(true); } @@ -74,24 +92,22 @@ public function markAsUnSeenAction($notification) /** * Set all Notifications for a User as seen * - * @Route("/markAllAsSeen", name="notification_mark_all_as_seen") + * @Route("/{notifiable}/markAllAsSeen", name="notification_mark_all_as_seen") * @Method("POST") - * @throws \LogicException - * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException + * @param $notifiable + * + * @return JsonResponse + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\OptimisticLockException */ - public function markAllAsSeenAction() + public function markAllAsSeenAction($notifiable) { - $em = $this->getDoctrine()->getEntityManager(); - $user = $this->getUser(); - if (!is_object($user) || !$user instanceof UserNotificationInterface) { - throw new AuthenticationException('This user does not have access to this section.'); - } - $notifications = $this->get('mgilet.notification')->getUnseenUserNotifications($user); - foreach ($notifications as $notification) { - $notification->setSeen(true); - $em->persist($notification); - } - $em->flush(); + $manager = $this->get('mgilet.notification'); + $manager->markAllAsSeen( + $manager->getNotifiableInterface($manager->getNotifiableEntityById($notifiable)), + true + ); return new JsonResponse(true); } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php deleted file mode 100755 index 945c9bf..0000000 --- a/DependencyInjection/Configuration.php +++ /dev/null @@ -1,39 +0,0 @@ -buildConfigTree(); - } - - private function buildConfigTree() - { - - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('mgilet_notification'); - $rootNode - ->children() - ->scalarNode('notification_class') - ->cannotBeEmpty() - ->defaultValue('AppBundle\\Entity\\Notification') - ->info('Entity for a notification (default: AppBundle\\Entity\\Notification)') - ->end(); - - return $treeBuilder; - } -} diff --git a/DependencyInjection/MgiletNotificationExtension.php b/DependencyInjection/MgiletNotificationExtension.php index c53f939..a7d76d2 100755 --- a/DependencyInjection/MgiletNotificationExtension.php +++ b/DependencyInjection/MgiletNotificationExtension.php @@ -20,14 +20,12 @@ class MgiletNotificationExtension extends Extension */ public function load(array $configs, ContainerBuilder $container) { - $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - $loader = new YamlFileLoader( + $yamlLoader = new YamlFileLoader( $container, new FileLocator(__DIR__.'/../Resources/config') ); - $loader->load('services.yml'); + $yamlLoader->load('services.yml'); - $container->setParameter('mgilet_notification.notification_class', $config['notification_class']); } + } diff --git a/Entity/NotifiableEntity.php b/Entity/NotifiableEntity.php new file mode 100644 index 0000000..8fc865b --- /dev/null +++ b/Entity/NotifiableEntity.php @@ -0,0 +1,147 @@ +identifier = $identifier; + $this->class = $class; + $this->notifiableNotifications = new ArrayCollection(); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * @param string $identifier + * + * @return NotifiableEntity + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + + return $this; + } + + /** + * @return string + */ + public function getClass() + { + return $this->class; + } + + /** + * @param string $class + * + * @return NotifiableEntity + */ + public function setClass($class) + { + $this->class = $class; + + return $this; + } + + /** + * @return ArrayCollection|NotifiableNotification[] + */ + public function getNotifiableNotifications() + { + return $this->notifiableNotifications; + } + + /** + * @param NotifiableNotification $notifiableNotification + * + * @return $this + */ + public function addNotifiableNotification(NotifiableNotification $notifiableNotification) + { + if (!$this->notifiableNotifications->contains($notifiableNotification)) { + $this->notifiableNotifications[] = $notifiableNotification; + $notifiableNotification->setNotifiableEntity($this); + } + + return $this; + } + + /** + * @param NotifiableNotification $notifiableNotification + * + * @return $this + */ + public function removeNotifiableNotification(NotifiableNotification $notifiableNotification) + { + if ($this->notifiableNotifications->contains($notifiableNotification)) { + $this->notifiableNotifications->removeElement($notifiableNotification); + $notifiableNotification->setNotifiableEntity(null); + } + + return $this; + } +} \ No newline at end of file diff --git a/Entity/NotifiableNotification.php b/Entity/NotifiableNotification.php new file mode 100644 index 0000000..fdd5d05 --- /dev/null +++ b/Entity/NotifiableNotification.php @@ -0,0 +1,120 @@ +seen = false; + } + + /** + * @return int Notification Id + */ + public function getId() + { + return $this->id; + } + + /** + * @return boolean Seen status of the notification + */ + public function isSeen() + { + return $this->seen; + } + + /** + * @param boolean $isSeen Seen status of the notification + * @return $this + */ + public function setSeen($isSeen) + { + $this->seen = $isSeen; + + return $this; + } + + /** + * @return Notification + */ + public function getNotification() + { + return $this->notification; + } + + /** + * @param Notification $notification + * + * @return NotifiableNotification + */ + public function setNotification($notification) + { + $this->notification = $notification; + + return $this; + } + + /** + * @return NotifiableEntity + */ + public function getNotifiableEntity() + { + return $this->notifiableEntity; + } + + /** + * @param NotifiableEntity $notifiableEntity + * + * @return NotifiableNotification + */ + public function setNotifiableEntity(NotifiableEntity $notifiableEntity = null) + { + $this->notifiableEntity = $notifiableEntity; + + return $this; + } +} \ No newline at end of file diff --git a/Model/AbstractNotification.php b/Entity/Notification.php similarity index 55% rename from Model/AbstractNotification.php rename to Entity/Notification.php index b81518d..526372a 100755 --- a/Model/AbstractNotification.php +++ b/Entity/Notification.php @@ -1,62 +1,68 @@ seen = false; $this->date = new \DateTime(); + $this->notifiableNotifications = new ArrayCollection(); } /** @@ -143,22 +149,40 @@ public function setLink($link) return $this; } + /** + * @return ArrayCollection|NotifiableNotification[] + */ + public function getNotifiableNotifications() + { + return $this->notifiableNotifications; + } /** - * @return boolean Seen status of the notification + * @param NotifiableNotification $notifiableNotification + * + * @return $this */ - public function isSeen() + public function addNotifiableNotification(NotifiableNotification $notifiableNotification) { - return $this->seen; + if (!$this->notifiableNotifications->contains($notifiableNotification)) { + $this->notifiableNotifications[] = $notifiableNotification; + $notifiableNotification->setNotification($this); + } + + return $this; } /** - * @param boolean $isSeen Seen status of the notification + * @param NotifiableNotification $notifiableNotification + * * @return $this */ - public function setSeen($isSeen) + public function removeNotifiableNotification(NotifiableNotification $notifiableNotification) { - $this->seen = $isSeen; + if ($this->notifiableNotifications->contains($notifiableNotification)) { + $this->notifiableNotifications->removeElement($notifiableNotification); + $notifiableNotification->setNotification(null); + } return $this; } diff --git a/Entity/Repository/NotifiableNotificationRepository.php b/Entity/Repository/NotifiableNotificationRepository.php new file mode 100644 index 0000000..8d8b471 --- /dev/null +++ b/Entity/Repository/NotifiableNotificationRepository.php @@ -0,0 +1,134 @@ +createQueryBuilder('nn') + ->join('nn.notification', 'n') + ->join('nn.notifiableEntity', 'ne') + ->where('n.id = :notification_id') + ->andWhere('ne.id = :notifiable_id') + ->setParameter('notification_id', $notification_id) + ->setParameter('notifiable_id', $notifiable_id) + ->getQuery() + ->getOneOrNullResult() + ; + } + + /** + * Get all NotifiableNotifications for a notifiable + * + * @param $notifiable_identifier + * @param $notifiable_class + * + * @return NotifiableNotification[] + */ + public function findAllForNotifiable($notifiable_identifier, $notifiable_class) + { + return $this->createQueryBuilder('nn') + ->join('nn.notifiableEntity','ne') + ->where('ne.identifier = :identifier') + ->andWhere('ne.class = :class') + ->setParameter('identifier', $notifiable_identifier) + ->setParameter('class', $notifiable_class) + ->getQuery() + ->getResult() + ; + } + + /** + * @param $id + * + * @return \Doctrine\ORM\QueryBuilder + */ + public function findAllForNotifiableIdQb($id) + { + return $this->createQueryBuilder('nn') + ->join('nn.notification', 'n') + ->join('nn.notifiableEntity', 'ne') + ->where('ne.id = :id') + ->setParameter('id', $id) + ; + } + + /** + * Get the NotifiableNotifications for a NotifiableEntity id + * + * @param $id + * + * @return NotifiableNotification[] + */ + public function findAllForNotifiableId($id) + { + return $this->findAllForNotifiableIdQb($id)->getQuery()->getResult(); + } + + /** + * @param $notifiable_identifier + * @param $notifiable_class + * + * @return \Doctrine\ORM\QueryBuilder + */ + protected function getNotificationCoundQb($notifiable_identifier, $notifiable_class) + { + return $this->createQueryBuilder('nn') + ->select('COUNT(nn.id)') + ->join('nn.notifiableEntity', 'ne') + ->where('ne.identifier = :notifiable_identifier') + ->andWhere('ne.class = :notifiable_class') + ->setParameter('notifiable_identifier', $notifiable_identifier) + ->setParameter('notifiable_class', $notifiable_class) + ; + } + + /** + * Get the count of Notifications for a Notifiable entity. + * + * seen option results : + * null : get all notifications + * true : get seen notifications + * false : get unseen notifications + * + * @param string $notifiable_identifier + * @param string $notifiable_class + * @param bool|null $seen + * + * @return int + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException + */ + public function getNotificationCount($notifiable_identifier, $notifiable_class, $seen = null) + { + $qb = $this->getNotificationCoundQb($notifiable_identifier,$notifiable_class); + + if ($seen !== null){ + $whereSeen = $seen ? 1 : 0; + $qb + ->andWhere('nn.seen = :seen') + ->setParameter('seen', $whereSeen); + } + return $qb->getQuery()->getSingleScalarResult(); + } + +} \ No newline at end of file diff --git a/Entity/Repository/NotifiableRepository.php b/Entity/Repository/NotifiableRepository.php new file mode 100644 index 0000000..a9e85c2 --- /dev/null +++ b/Entity/Repository/NotifiableRepository.php @@ -0,0 +1,60 @@ +createQueryBuilder('n')->select('e')->from($notifiableEntity->getClass(), 'e'); + + // map the identifier(s) to the value(s) + $identifiers = explode('-', $notifiableEntity->getIdentifier()); + foreach ($mapping as $key => $identifier) { + $qb->andWhere(sprintf('e.%s = :%s', $identifier, $identifier)); + $qb->setParameter($identifier, $identifiers[$key]); + } + + return $qb->getQuery()->getOneOrNullResult(); + } + + /** + * @param $id + * @param bool|null $seen + * + * @return NotifiableInterface[] + */ + public function findAllByNotification($id, $seen = null) + { + $qb = $this + ->createQueryBuilder('notifiable') + ->join('notifiable.notifiableNotifications', 'nn') + ->join('nn.notification', 'notification') + ->where('notification.id = :notification_id') + ->setParameter('notification_id', $id) + ; + + if ($seen !== null){ + $whereSeen = $seen ? 1 : 0; + $qb + ->andWhere('nn.seen = :seen') + ->setParameter('seen', $whereSeen) + ; + } + + return $qb->getQuery()->getResult(); + } +} \ No newline at end of file diff --git a/Entity/Repository/NotificationRepository.php b/Entity/Repository/NotificationRepository.php new file mode 100644 index 0000000..7e33bf6 --- /dev/null +++ b/Entity/Repository/NotificationRepository.php @@ -0,0 +1,48 @@ +findAllByNotifiableQb($identifier, $class); + + if ($seen !== null){ + $whereSeen = $seen ? 1 : 0; + $qb + ->andWhere('notifiable_notifications.seen = :seen') + ->setParameter('seen', $whereSeen) + ; + } + return $qb->getQuery()->getResult(); + } + + /** + * @param $identifier + * @param $class + * + * @return \Doctrine\ORM\QueryBuilder + */ + public function findAllByNotifiableQb($identifier, $class) + { + return $this->createQueryBuilder('notification') + ->addSelect('notifiable_notifications.seen') + ->join('notification.notifiableNotifications', 'notifiable_notifications') + ->join('notifiable_notifications.notifiableEntity', 'notifiable_entity') + ->where('notifiable_entity.identifier = :identifier') + ->andWhere('notifiable_entity.class = :class') + ->setParameter('identifier', $identifier) + ->setParameter('class', $class) + ; + } +} \ No newline at end of file diff --git a/Event/NotificationEvent.php b/Event/NotificationEvent.php new file mode 100644 index 0000000..ee7ee85 --- /dev/null +++ b/Event/NotificationEvent.php @@ -0,0 +1,43 @@ +notification = $notification; + $this->notifiable = $notifiable; + } + + /** + * @return Notification + */ + public function getNotification() + { + return $this->notification; + } + + /** + * @return NotifiableInterface + */ + public function getNotifiable() + { + return $this->notifiable; + } + +} diff --git a/Manager/NotificationManager.php b/Manager/NotificationManager.php index 0132e5c..31fec2d 100755 --- a/Manager/NotificationManager.php +++ b/Manager/NotificationManager.php @@ -3,8 +3,15 @@ namespace Mgilet\NotificationBundle\Manager; use Doctrine\ORM\EntityManager; -use Mgilet\NotificationBundle\Model\AbstractNotification; -use Mgilet\NotificationBundle\Model\UserNotificationInterface; +use Doctrine\ORM\EntityNotFoundException; +use Mgilet\NotificationBundle\Entity\NotifiableEntity; +use Mgilet\NotificationBundle\Entity\NotifiableNotification; +use Mgilet\NotificationBundle\Entity\Notification; +use Mgilet\NotificationBundle\Event\NotificationEvent; +use Mgilet\NotificationBundle\MgiletNotificationEvents; +use Mgilet\NotificationBundle\NotifiableDiscovery; +use Mgilet\NotificationBundle\NotifiableInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; /** * Class NotificationManager @@ -14,174 +21,620 @@ class NotificationManager { - private $om; - private $notification; - private $repository; + protected $om; + protected $notifiableRepository; + protected $notificationRepository; + protected $notifiableNotificationRepository; + protected $dispatcher; + protected $discovery; /** * NotificationManager constructor. - * @param EntityManager $om - * @param $notification - * @internal param $class + * + * @param EntityManager $om + * @param NotifiableDiscovery $discovery + * */ - public function __construct(EntityManager $om, $notification) + public function __construct(EntityManager $om, NotifiableDiscovery $discovery) { $this->om = $om; - $this->notification = $notification; - $this->repository = $om->getRepository($notification); + $this->notifiableRepository = $om->getRepository('MgiletNotificationBundle:NotifiableEntity'); + $this->notificationRepository = $om->getRepository('MgiletNotificationBundle:Notification'); + $this->notifiableNotificationRepository = $this->om->getRepository('MgiletNotificationBundle:NotifiableNotification'); + $this->dispatcher = new EventDispatcher(); + $this->discovery = $discovery; + + } + + /** + * Returns a list of available workers. + * + * @return array + * @throws \InvalidArgumentException + */ + public function getDiscoveryNotifiables() { + return $this->discovery->getNotifiables(); + } + + /** + * Returns one notifiable by name + * + * @param $name + * + * @return array + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function getNotifiable($name) { + $notifiables = $this->getDiscoveryNotifiables(); + if (isset($notifiables[$name])) { + return $notifiables[$name]; + } + + throw new \RuntimeException('Notifiable not found.'); + } + + /** + * Get the name of the notifiable + * + * @param NotifiableInterface $notifiable + * + * @return string|null + */ + public function getNotifiableName(NotifiableInterface $notifiable) + { + return $this->discovery->getNotifiableName($notifiable); + } + + /** + * @param NotifiableInterface $notifiable + * + * @return array + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getNotifiableIdentifier(NotifiableInterface $notifiable) + { + $name = $this->getNotifiableName($notifiable); + + return $this->getNotifiable($name)['identifiers']; + } + + /** + * Get the identifier mapping for a NotifiableEntity + * + * @param NotifiableEntity $notifiableEntity + * + * @return array + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getNotifiableEntityIdentifiers(NotifiableEntity $notifiableEntity) + { + $discoveryNotifiables = $this->getDiscoveryNotifiables(); + foreach ($discoveryNotifiables as $notifiable) { + if ($notifiable['class'] === $notifiableEntity->getClass()){ + return $notifiable['identifiers']; + } + } + throw new \RuntimeException('Unable to get the NotifiableEntity identifiers. This could be an Entity mapping issue'); + } + + /** + * Generates the identifier value to store a NotifiableEntity + * + * @param NotifiableInterface $notifiable + * + * @return string + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function generateIdentifier(NotifiableInterface $notifiable) + { + $Notifiableidentifiers = $this->getNotifiableIdentifier($notifiable); + $identifierValues = array(); + foreach ($Notifiableidentifiers as $identifier) { + $method = sprintf('get%s', ucfirst($identifier)); + $identifierValues[] = $notifiable->$method(); + } + + return implode('-', $identifierValues); + } + + /** + * Get a NotifiableEntity form a NotifiableInterface + * + * @param NotifiableInterface $notifiable + * + * @return NotifiableEntity + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getNotifiableEntity(NotifiableInterface $notifiable) + { + $identifier = $this->generateIdentifier($notifiable); + $class = get_class($notifiable); + $entity = $this->notifiableRepository->findOneBy(array( + 'identifier' => $identifier, + 'class' => $class + )); + + if (!$entity){ + $entity = new NotifiableEntity($identifier, $class); + $this->om->persist($entity); + $this->om->flush(); + } + + return $entity; + } + + /** + * @param NotifiableEntity $notifiableEntity + * + * @return NotifiableInterface + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getNotifiableInterface(NotifiableEntity $notifiableEntity) + { + return $this->notifiableRepository->findNotifiableInterface( + $notifiableEntity, + $this->getNotifiableEntityIdentifiers($notifiableEntity) + ); + } + + /** + * @param $id + * + * @return NotifiableEntity|null + */ + public function getNotifiableEntityById($id) + { + return $this->notifiableRepository->findOneById($id); + } + + /** + * @param NotifiableInterface $notifiable + * @param Notification $notification + * + * @return NotifiableNotification|null + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + private function getNotifiableNotification(NotifiableInterface $notifiable, Notification $notification) + { + return $this->notifiableNotificationRepository->findOne( + $notification->getId(), + $this->getNotifiableEntity($notifiable) + ); + + } + + /** + * Avoid code duplication + * + * @param $flush + * + * @throws \Doctrine\ORM\OptimisticLockException + */ + private function flush($flush) + { + if ($flush){ + $this->om->flush(); + } + } + + /** + * Get all notifications + * + * @return Notification[] + */ + public function getAll() + { + return $this->notificationRepository->findAll(); + } + + /** + * @param NotifiableInterface $notifiable + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + * + * @return array + */ + public function getNotifications(NotifiableInterface $notifiable) + { + return $this->notificationRepository->findAllByNotifiable($this->generateIdentifier($notifiable), get_class($notifiable)); + } + + /** + * @param NotifiableInterface $notifiable + * + * @return array + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getUnseenNotifications(NotifiableInterface $notifiable) + { + return $this->notificationRepository->findAllByNotifiable($this->generateIdentifier($notifiable), get_class($notifiable), false); } /** - * Get a notification by it's id + * @param NotifiableInterface $notifiable + * + * @return array + * @throws \RuntimeException + * @throws \InvalidArgumentException + */ + public function getSeenNotifications(NotifiableInterface $notifiable) + { + return $this->notificationRepository->findAllByNotifiable($this->generateIdentifier($notifiable), get_class($notifiable), true); + } + + + /** + * Get one notification by id + * * @param $id - * @return AbstractNotification + * + * @return Notification */ - public function getNotificationById($id) + public function getNotification($id) { - return $this->repository->findOneBy(array('id' => $id)); + return $this->notificationRepository->findOneById($id); } /** - * Generate a notification - * @param $subject - * @param null $message - * @param null $link - * @return AbstractNotification + * @param string $subject + * @param string $message + * @param string $link + * + * @return Notification */ - public function generateNotification($subject, $message = null, $link = null) + public function createNotification($subject, $message = null, $link = null) { - /** @var AbstractNotification $notification */ - $notification = new $this->notification; + $notification = new Notification(); $notification ->setSubject($subject) ->setMessage($message) - ->setLink($link); + ->setLink($link) + ; + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::CREATED, $event); return $notification; } /** - * Add a notification to a user - * @param UserNotificationInterface $user - * @param AbstractNotification $notification - * @return AbstractNotification - * @throws \Doctrine\ORM\ORMInvalidArgumentException + * Add a Notification to a list of NotifiableInterface entities + * + * @param NotifiableInterface[] $notifiables + * @param Notification $notification + * @param bool $flush + * * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ - public function addNotification(UserNotificationInterface $user, AbstractNotification $notification) + public function addNotification($notifiables, Notification $notification, $flush = false) { - $user->addNotification($notification); - $this->om->persist($user); - $this->om->flush(); + foreach ($notifiables as $notifiable) { + $entity = $this->getNotifiableEntity($notifiable); - return $notification; + $notifiableNotification = new NotifiableNotification(); + $entity->addNotifiableNotification($notifiableNotification); + $notification->addNotifiableNotification($notifiableNotification); + + $event = new NotificationEvent($notification, $notifiable); + $this->dispatcher->dispatch(MgiletNotificationEvents::ASSIGNED, $event); + } + + $this->flush($flush); } /** - * @param UserNotificationInterface $user - * @param string $subject - * @param string|null $message - * @param string|null $link + * Deletes the link between a Notifiable and a Notification + * + * @param array $notifiables + * @param Notification $notification + * @param bool $flush + * * @throws \Doctrine\ORM\ORMInvalidArgumentException * @throws \Doctrine\ORM\OptimisticLockException + * @throws \InvalidArgumentException + * @throws \RuntimeException */ - public function createNotification(UserNotificationInterface $user, $subject, $message = null, $link = null) + public function removeNotification(array $notifiables, Notification $notification, $flush = false) { - $this->addNotification($user, $this->generateNotification($subject,$message,$link)); + $repo = $this->om->getRepository('MgiletNotificationBundle:NotifiableNotification'); + foreach ($notifiables as $notifiable) { + $repo->createQueryBuilder('nn') + ->delete() + ->where('nn.notifiableEntity = :entity') + ->andWhere('nn.notification = :notification') + ->setParameter('entity', $this->getNotifiableEntity($notifiable)) + ->setParameter('notification', $notification) + ->getQuery() + ->execute() + ; + + $event = new NotificationEvent($notification, $notifiable); + $this->dispatcher->dispatch(MgiletNotificationEvents::REMOVED, $event); + } + + $this->flush($flush); } /** - * Delete a notification - * @param AbstractNotification $notification + * @param Notification $notification + * + * @param bool $flush + * * @throws \Doctrine\ORM\ORMInvalidArgumentException * @throws \Doctrine\ORM\OptimisticLockException */ - public function removeNotification(AbstractNotification $notification) + public function deleteNotification(Notification $notification, $flush = false) { $this->om->remove($notification); - $this->om->persist($notification); - $this->om->flush(); + $this->flush($flush); + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::DELETED, $event); } /** - * Mark a notification as seen - * @param AbstractNotification $notification - * @return AbstractNotification - * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @param NotifiableInterface $notifiable + * @param Notification $notification + * @param bool $flush + * + * @throws EntityNotFoundException * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\NonUniqueResultException */ - public function markAsSeen(AbstractNotification $notification) + public function markAsSeen(NotifiableInterface $notifiable, Notification $notification, $flush = false) { - $notification->setSeen(true); - $this->om->persist($notification); - $this->om->flush(); + $nn = $this->getNotifiableNotification($notifiable,$notification); + if ($nn){ + $nn->setSeen(true); + $event = new NotificationEvent($notification, $notifiable); + $this->dispatcher->dispatch(MgiletNotificationEvents::SEEN, $event); + $this->flush($flush); + } else { + throw new EntityNotFoundException('The link between the notifiable and the notification has not been found'); + } + } - return $notification; + /** + * @param NotifiableInterface $notifiable + * @param Notification $notification + * @param bool $flush + * + * @throws EntityNotFoundException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function markAsUnseen(NotifiableInterface $notifiable, Notification $notification, $flush = false) + { + $nn = $this->getNotifiableNotification($notifiable,$notification); + if ($nn){ + $nn->setSeen(false); + $event = new NotificationEvent($notification, $notifiable); + $this->dispatcher->dispatch(MgiletNotificationEvents::UNSEEN, $event); + $this->flush($flush); + } else { + throw new EntityNotFoundException('The link between the notifiable and the notification has not been found'); + } } /** - * Mark all notifications as seen - * @param AbstractNotification[] $notifications - * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @param NotifiableInterface $notifiable + * @param bool $flush + * + * @throws \InvalidArgumentException + * @throws \RuntimeException * @throws \Doctrine\ORM\OptimisticLockException */ - public function markAllAsSeen($notifications) + public function markAllAsSeen(NotifiableInterface $notifiable, $flush = false) { - foreach ($notifications as $notification) { - $this->markAsSeen($notification); + $nns = $this->notifiableNotificationRepository->findAllForNotifiable( + $this->generateIdentifier($notifiable), + get_class($notifiable) + ); + foreach ($nns as $nn) { + $nn->setSeen(true); + $event = new NotificationEvent($nn->getNotification(), $notifiable); + $this->dispatcher->dispatch(MgiletNotificationEvents::SEEN, $event); } + $this->flush($flush); } /** - * Get all notifications for a user - * @param UserNotificationInterface $user - * @return AbstractNotification[] list of notifications + * @param NotifiableInterface $notifiable + * @param Notification $notification + * + * @return bool + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws EntityNotFoundException */ - public function getUserNotifications($user) + public function isSeen(NotifiableInterface $notifiable, Notification $notification) { - return $this->repository->findBy(array('user' => $user),array('date' => 'DESC')); + $nn = $this->getNotifiableNotification($notifiable,$notification); + if ($nn){ + return $nn->isSeen(); + } + + throw new EntityNotFoundException('The link between the notifiable and the notification has not been found'); } /** - * Get all unseen notifications for a user - * @param UserNotificationInterface $user - * @return AbstractNotification[] + * @param NotifiableInterface $notifiable + * + * @return int + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException */ - public function getUnseenUserNotifications($user) + public function getNotificationCount(NotifiableInterface $notifiable) { - return $this->repository->findBy( - array( - 'user' => $user, - 'seen' => false - ), - array('date' => 'DESC') + return $this->notifiableNotificationRepository->getNotificationCount( + $this->generateIdentifier($notifiable), + get_class($notifiable) ); } /** - * Get notification count for a user - * @param UserNotificationInterface $user + * @param NotifiableInterface $notifiable + * * @return int + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException */ - public function getNotificationCount($user) + public function getUnseenNotificationCount(NotifiableInterface $notifiable) { - return count($this->repository->findBy(array('user' => $user),array('date' => 'DESC'))); + return $this->notifiableNotificationRepository->getNotificationCount( + $this->generateIdentifier($notifiable), + get_class($notifiable), + false + ); } /** - * Get unseen notification count for a user - * @param UserNotificationInterface $user + * @param NotifiableInterface $notifiable + * * @return int + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException */ - public function getUnseenNotificationCount($user) + public function getSeenNotificationCount(NotifiableInterface $notifiable) { - return count($this->repository->findBy( - array( - 'user' => $user, - 'seen' => false - ), - array('date' => 'DESC') - )); + return $this->notifiableNotificationRepository->getNotificationCount( + $this->generateIdentifier($notifiable), + get_class($notifiable), + true + ); + } + + /** + * @param Notification $notification + * @param \DateTime $dateTime + * @param bool $flush + * + * @return Notification + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function setDate(Notification $notification, \DateTime $dateTime, $flush = false) + { + $notification->setDate($dateTime); + $this->flush($flush); + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::MODIFIED, $event); + + return $notification; + } + + /** + * @param Notification $notification + * @param string $subject + * @param bool $flush + * + * @return Notification + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function setSubject(Notification $notification, $subject, $flush = false) + { + $notification->setSubject($subject); + $this->flush($flush); + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::MODIFIED, $event); + + return $notification; + } + + /** + * @param Notification $notification + * @param string $subject + * @param bool $flush + * + * @return Notification + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function setMessage(Notification $notification, $subject, $flush = false) + { + $notification->setSubject($subject); + $this->flush($flush); + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::MODIFIED, $event); + + return $notification; + } + + /** + * @param Notification $notification + * @param string $link + * @param bool $flush + * + * @return Notification + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function setLink(Notification $notification, $link, $flush = false) + { + $notification->setLink($link); + $this->flush($flush); + + $event = new NotificationEvent($notification); + $this->dispatcher->dispatch(MgiletNotificationEvents::MODIFIED, $event); + + return $notification; + } + + /** + * @param Notification $notification + * + * @return NotifiableInterface[] + */ + public function getNotifiables(Notification $notification) + { + return $this->notifiableRepository->findAllByNotification($notification); + } + + /** + * @param Notification $notification + * + * @return NotifiableInterface[] + */ + public function getUnseenNotifiables(Notification $notification) + { + return $this->notifiableRepository->findAllByNotification($notification, true); + } + + /** + * @param Notification $notification + * + * @return NotifiableInterface[] + */ + public function getSeenNotifiables(Notification $notification) + { + return $this->notifiableRepository->findAllByNotification($notification, false); } } diff --git a/MgiletNotificationBundle.php b/MgiletNotificationBundle.php index bd27d28..0258a95 100755 --- a/MgiletNotificationBundle.php +++ b/MgiletNotificationBundle.php @@ -15,5 +15,5 @@ */ class MgiletNotificationBundle extends Bundle { - + } diff --git a/MgiletNotificationEvents.php b/MgiletNotificationEvents.php new file mode 100644 index 0000000..9f62915 --- /dev/null +++ b/MgiletNotificationEvents.php @@ -0,0 +1,55 @@ +annotationReader = $annotationReader; + $this->em = $em; + $this->discoverNotifiables(); + } + + /** + * Returns all the workers + * @throws \InvalidArgumentException + */ + public function getNotifiables() + { + return $this->notifiables; + } + + /** + * @param NotifiableInterface $notifiable + * + * @return string|null + */ + public function getNotifiableName(NotifiableInterface $notifiable) + { + $annotation = $this->annotationReader->getClassAnnotation(new \ReflectionClass($notifiable), 'Mgilet\NotificationBundle\Annotation\Notifiable'); + if ($annotation){ + return $annotation->getName(); + } + + return null; + } + + /** + * Discovers workers + * @throws \InvalidArgumentException + */ + private function discoverNotifiables() + { + /** @var ClassMetadata[] $entities */ + $entities = $this->em->getMetadataFactory()->getAllMetadata(); + foreach ($entities as $entity) { + $class = $entity->name; + $annotation = $this->annotationReader->getClassAnnotation(new \ReflectionClass($class), 'Mgilet\NotificationBundle\Annotation\Notifiable'); + if ($annotation) { + $this->notifiables[$annotation->getName()] = [ + 'class' => $entity->name, + 'annotation' => $annotation, + 'identifiers' => $entity->getIdentifier() + ]; + } + } + } +} \ No newline at end of file diff --git a/NotifiableInterface.php b/NotifiableInterface.php new file mode 100644 index 0000000..5aaa2c7 --- /dev/null +++ b/NotifiableInterface.php @@ -0,0 +1,15 @@ +mgilet/notification-bundle

-A simple Symfony bundle to notify user +An easy yet powerful notification bundle for Symfony

Latest Stable Version @@ -16,22 +16,21 @@ A simple Symfony bundle to notify user

mgilet/notificationBundle

-Create and manage user notifications in an efficient way. +Create and manage notifications in an efficient way. Symfony support : * 2.7.x * 2.8.x * 3.x - -Bootstrap > 3.x highly recommended ## Features -- Easy notification management -- Simple Twig render method -- Pretty Twig template (dropdown using Bootstrap 3) -- Fully customizable - Easy setup +- Easy to use +- Powerful notification management +- Simple Twig render methods +- Fully customizable +- Multiple notifiables entities - No bloated dependencies (little requirements) Notice: Only Doctrine ORM is supported for now. @@ -44,65 +43,48 @@ Notice: Only Doctrine ORM is supported for now. This bundle is available on [packagist](https://packagist.org/packages/mgilet/notification-bundle). -Notice : The bundle is actually in alpha state (no major issue encountered) - -In order to install it, add the following line in your composer.json - -```json -// composer.json - -... -"require": { - "mgilet/notification-bundle": "dev-master", -}, -``` - -Then perform a +First : ```bash -$ composer install +$ composer require mgilet/notification-bundle ``` -This will install the latest commited version of the master branch. -When a stable version will come out you will just have to enter the following command: -```bash -$ composer require mgilet/notification-bundle -``` +**See [documentation](Resources/doc/index.rst) for next steps** + -See [documentation](Resources/doc/index.rst) for next steps ### Basic usage ```php -class DefaultController extends Controller +class MyController extends Controller { ... - /** - * @Route("/send-notification", name="send_notification") - * @param Request $request - * @return \Symfony\Component\HttpFoundation\RedirectResponse - */ public function sendNotification(Request $request) { - $manager = $this->get('mgilet.notification'); - $notif = $manager->generateNotification('Hello world !'); - $notif - ->setMessage('This a notification.') - ->setLink('http://symfony.com/'); - $manager->addNotification($this->getUser(), $notif); - - // or the one-line method : - // $manager->createNotification($this->getUser(), 'Notification subject','Some random text','http://google.fr'); - - return $this->redirectToRoute('homepage'); + $manager = $this->get('mgilet.notification'); + $notif = $manager->createNotification('Hello world !'); + $notif->setMessage('This a notification.'); + $notif->setLink('http://symfony.com/'); + // or the one-line method : + // $manager->createNotification('Notification subject','Some random text','http://google.fr'); + + // you can add a notification to a list of entities + // the third parameter `$flush` allows you to directly flush the entities + $manager->addNotification(array($this->getUser()), $notif, true); + + ... } ``` -See [HERE](Resources/doc/usage.rst) for more + + +**See [HERE](Resources/doc/usage.rst) for more** + + ## Translations diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml new file mode 100644 index 0000000..c8c4605 --- /dev/null +++ b/Resources/config/routing.yml @@ -0,0 +1,3 @@ +notifications: + resource: "@MgiletNotificationBundle/Controller/" + type: annotation \ No newline at end of file diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 151eb4e..8c60b22 100755 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -2,11 +2,16 @@ services: mgilet.notification: class: Mgilet\NotificationBundle\Manager\NotificationManager - arguments: ["@doctrine.orm.entity_manager", "%mgilet_notification.notification_class%"] + arguments: ["@doctrine.orm.entity_manager", '@mgilet.notifiable_discovery'] + + mgilet.notifiable_discovery: + class: Mgilet\NotificationBundle\NotifiableDiscovery + arguments: [ '@doctrine.orm.entity_manager', '@annotation_reader'] + mgilet.twig_extension: class: Mgilet\NotificationBundle\Twig\NotificationExtension public: false tags: - { name: twig.extension } - arguments: ['@mgilet.notification', '@security.token_storage', '@twig'] + arguments: ['@mgilet.notification', '@security.token_storage', '@twig', '@router'] diff --git a/Resources/doc/advanced-configuration.rst b/Resources/doc/advanced-configuration.rst deleted file mode 100755 index 15480e4..0000000 --- a/Resources/doc/advanced-configuration.rst +++ /dev/null @@ -1,67 +0,0 @@ -======================== -MgiletNotificationBundle -======================== ------------------------------------------------- -A simple Symfony 3 bundle for user notifications ------------------------------------------------- - -Advanced configuration -====================== - -By default this bundle is configured to use Implementations of ``UserNotificationInterface`` as user and ``AppBundle\Entity\Notification`` as notification entity. - -You can change this behavior by editing your ``config.yml`` file. - -Default bundle configuration: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Default configuration: - -.. code-block:: yaml - - # config.yml - - mgilet_notification: - notification_class: AppBundle\Entity\Notification - - -How to configure: -~~~~~~~~~~~~~~~~~ - -* ``notification_class``: the entity defining a notification (this class MUST extends the MappedSuperClass ``AbstractNotification`` ) - -Change the default configuration according to your existing class. - -Example : - -.. code-block:: yaml - - # config.yml - - mgilet_notification: - notification_class: AcmeBundle\Entity\MyNotification - - -Go further : ------------- - -`go further`_ - ----------------------------------------------- - -* `installation`_ - -* `basic usage`_ - -* `overriding parts of the bundle`_ - -* `advanced configuration`_ - -* `go further`_ - - -.. _installation: index.rst -.. _basic usage: usage.rst -.. _overriding parts of the bundle: overriding.rst -.. _advanced configuration: advanced-configuration.rst -.. _go further: further.rst diff --git a/Resources/doc/further.rst b/Resources/doc/further.rst index c2770d1..810aca0 100755 --- a/Resources/doc/further.rst +++ b/Resources/doc/further.rst @@ -32,17 +32,11 @@ And thank you for using this. * `basic usage`_ -* `overriding parts of the bundle`_ - -* `advanced configuration`_ - * `go further`_ .. _installation: index.rst .. _basic usage: usage.rst -.. _overriding parts of the bundle: overriding.rst -.. _advanced configuration: advanced-configuration.rst .. _go further: further.rst .. _Github: https://github.com/maximilienGilet/notification-bundle \ No newline at end of file diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 49030ab..c2ecb2a 100755 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -12,25 +12,10 @@ Installation Prerequisites ------------- -This version of the bundle requires Symfony 2.7+. For better rendering Bootstrap 3 is recommended. +This version of the bundle requires Symfony 2.7+. Warning : For now only Doctrine ORM is supported -Translations -~~~~~~~~~~~~ - -If you wish to use default texts provided in this bundle, you have to make -sure you have translator enabled in your config. - -.. code-block:: yaml - - # app/config/config.yml - - framework: - translator: ~ - -For more information about translations, check `Symfony documentation`_. - Basic installation: ------------------- @@ -59,19 +44,17 @@ Then add the following line in the AppKernel.php: ); } -Define User and Notification classes: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configure notifiables classes: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The goal of this bundle is to provide a ``Notification`` to a ``User``, so you need to define these classes. +The goal of this bundle is to make one or many entities ``notifiables``. -The bundle provides base classes which are already mapped for most fields -to make it easier to create your entity. Here is how you use it: +1. Use the ``@Notifiable`` annotation on your entity +2. Implement the ``Mgilet\NotificationBundle\NotifiableInterface`` interface (it's an empty interface) -1. Implement ``UserNotificationInterface`` interface (from the ``Model`` folder ) on your ``User`` entity -2. Map the ``notifications`` field (we will create it just after) -3. Implement ``UserNotificationInterface`` methods in your ``User`` class +And that's it ! -Sample configuration: +Example: .. code-block:: php @@ -83,145 +66,20 @@ Sample configuration: ... use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; - use Mgilet\NotificationBundle\Model\UserNotificationInterface; + use Mgilet\NotificationBundle\Entity\UserNotificationInterface; /** * @ORM\Entity - * @ORM\Table(name="user") + * @ORM\Table(name="my_entity") + * @Notifiable(name="my_entity") */ - class User implements UserNotificationInterface + class MyEntity implements Mgilet\NotificationBundle\NotifiableInterface { ... - // link to notifications - /** - * @var Notification - * @ORM\OneToMany(targetEntity="AppBundle\Entity\Notification", mappedBy="user", orphanRemoval=true) - */ - protected $notifications; - - ... - - public function __construct() - { - ... - $this->notifications = new ArrayCollection(); - } - - ... - - // method implementation for UserNotificationInterface - - /** - * {@inheritdoc} - */ - public function getNotifications() - { - return $this->notifications; - } - - /** - * {@inheritdoc} - */ - public function addNotification($notification) - { - if (!$this->notifications->contains($notification)) { - $this->notifications[] = $notification; - $notification->setUser($this); - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function removeNotification($notification) - { - if ($this->notifications->contains($notification)) { - $this->notifications->removeElement($notification); - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getIdentifier() - { - $this->getId(); - } - - } - -Now we need the Notification class. - -Simply extend the provided MappedSuperClass ``AbstractNotification`` class (from the ``Model`` folder) and link it to the ``User`` entity. - -Here is a sample configuration: - -.. code-block:: php - - id; - } - - /** - * @return User - */ - public function getUser() - { - return $this->user; - } - - /** - * @param User $user - * @return Notification - */ - public function setUser($user) - { - $this->user = $user; - $user->addNotification($this); - - return $this; - } - - } +You can set as many entities ``notifiables`` as you want. +Entities with multiple identifiers are supported Update Doctrine ~~~~~~~~~~~~~~~ @@ -259,38 +117,20 @@ In order to enable the controller, simply put this in your ``routing.yml`` : prefix: /notifications -Assets : -~~~~~~~~ - -By installing this bundle with composer, all assets will be copied. if it doesn't work, execute the following command: - -**Symfony 2.x** - -.. code-block:: bash - - $ php app/console assets:install +Translations (optionnal) +~~~~~~~~~~~~~~~~~~~~~~~~ -**Symfony 3.x** - -.. code-block:: bash - - $ php bin/console assets:install - - -Class not located in AppBundle : -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If your ``Notification`` entity is not located in ``AppBundle`` or have different name than default, you must define it's path in your ``config`` file. - -Example of configuration : +If you wish to use default texts provided in this bundle, you have to make +sure you have translator enabled in your config. .. code-block:: yaml - # config.yml + # app/config/config.yml - mgilet_notification: - notification_class: AnotherBundle\Entity\MyNotification # default value is AppBundle\Entity\Notification + framework: + translator: ~ +For more information about translations, check `Symfony documentation`_. Basic usage : ~~~~~~~~~~~~~ @@ -303,17 +143,11 @@ Go to `basic usage`_ * `basic usage`_ -* `overriding parts of the bundle`_ - -* `advanced configuration`_ - * `go further`_ .. _installation: index.rst .. _basic usage: usage.rst -.. _overriding parts of the bundle: overriding.rst -.. _advanced configuration: advanced-configuration.rst .. _go further: further.rst .. _Symfony documentation: https://symfony.com/doc/current/book/translation.html diff --git a/Resources/doc/overriding.rst b/Resources/doc/overriding.rst deleted file mode 100755 index 7b98504..0000000 --- a/Resources/doc/overriding.rst +++ /dev/null @@ -1,66 +0,0 @@ -======================== -MgiletNotificationBundle -======================== ------------------------------------------------- -A simple Symfony 3 bundle for user notifications ------------------------------------------------- - -Overriding parts of the bundle -============================== - -While providing configured templates, you can customize them to adjust it to your needs. - -The following will show you how. - -For more information about overriding parts of bundles, see the `Symfony documentation`_. - - -Twig templates: ---------------- - -In order to override templates provided by this bundle, you need to setup folders like this:: - - app/Resources - ├─ MgiletNotificationBundle/ - │ ├─ views/ - │ │ ├─ notification_dropdown.html.twig - │ │ └─ notification_list.html.twig - │ │ └─ notifications.html.twig - - -* ``notification_dropdown.html.twig`` is the template called when you use the dropdown option of ``mgilet_notification_render`` Twig method. - -* ``notification_list.html.twig`` is the template called when you use the list option of ``mgilet_notification_render`` Twig method. - -* ``notifications.html.twig`` is the template called by the ``NotificationController`` when responding to the ``/notifications`` route call. - -Rest of the bundle: -------------------- - -According to the `Symfony documentation`_ you can override any other part of this bundle. - -Advanced configuration : ------------------------- - -Go to `advanced configuration`_. - ----------------------------------------------- - -* `installation`_ - -* `basic usage`_ - -* `overriding parts of the bundle`_ - -* `advanced configuration`_ - -* `go further`_ - - -.. _installation: index.rst -.. _basic usage: usage.rst -.. _overriding parts of the bundle: overriding.rst -.. _advanced configuration: advanced-configuration.rst -.. _go further: further.rst - -.. _Symfony documentation: http://symfony.com/doc/current/cookbook/bundles/override.html \ No newline at end of file diff --git a/Resources/doc/usage.rst b/Resources/doc/usage.rst index 9c1836f..3e7bbe6 100755 --- a/Resources/doc/usage.rst +++ b/Resources/doc/usage.rst @@ -8,7 +8,7 @@ A simple Symfony 3 bundle for user notifications Usage ===== -This bundle provides some methods and helpers in order to be as simple as possible to use. +This bundle provides manuy methods and helpers in order to be as simple as possible to use. mgilet.notification service --------------------------- @@ -18,10 +18,10 @@ This is the notification manager. It's designed to do manage notification in a e Currently, it allows you to: * create notifications -* add notifications to users -* mark notifications as "seen" -* get notifications for a user -* get notification count +* add notifications to entities +* mark notifications as "seen"/"unseen" +* get notifications for an entity +* get notification counts * ... and much more ! Basic usage @@ -31,6 +31,8 @@ Lets write a minimalistic route : ``send-notification``. This sample route will send a notification to the user going there. +Your ``User`` entity will need to be ``notifiable`` (see `installation`_) + Sample route: .. code-block:: php @@ -54,13 +56,15 @@ Sample route: public function sendNotification(Request $request) { $manager = $this->get('mgilet.notification'); - $notif = $manager->generateNotification('Hello world !'); + $notif = $manager->createNotification('Hello world !'); $notif->setMessage('This a notification.'); $notif->setLink('http://symfony.com/'); - $manager->addNotification($this->getUser(), $notif); - // or the one-line method : - // $manager->createNotification($this->getUser(), 'Notification subject','Some random text','http://google.fr'); + // $manager->createNotification('Notification subject','Some random text','http://google.fr'); + + // you can add a notification to a list of entities + // the third parameter ``$flush`` allows you to directly flush the entities + $manager->addNotification(array($this->getUser()), $notif); return $this->redirectToRoute('homepage'); } @@ -68,23 +72,42 @@ Sample route: This will create a notification and associate it with the current user. +Events +~~~~~~ + +By using the ``NotificationManager`` you can listen to events thrown by the manager. + +List of events: + +* ``'mgilet.notification.created'`` +* ``'mgilet.notification.assigned'`` -> added to a user +* ``'mgilet.notification.seen'`` +* ``'mgilet.notification.unseen'`` +* ``'mgilet.notification.modified'`` +* ``'mgilet.notification.removed'`` +* ``'mgilet.notification.delete'`` + Twig functions -------------- This bundle also provides some useful twig functions helping you to design a great user experience. -Notice : Bootstrap 3 is highly recommended for best results. - -If you want to make your own twig template, see : `overriding parts of the bundle`_ +If you want to make your own twig template, see : `Symfony documentation`_ List of functions : ~~~~~~~~~~~~~~~~~~~ * mgilet_notification_count -* mgilet_unseen_notification_count +* mgilet_notification_unseen_count -These functions will display the current notification count for the current user. +These functions will display the current notification count for a given notifiable + +:: + + {{ mgilet_notification_count() }} {# all notifications #} + + {{ mgilet_unseen_notification_count }} {# unseen notifications #} ------------------ @@ -94,56 +117,30 @@ This function will render notifications for a user (current by default). It take Currently, 2 options are available : -* display - * list : will display a simple list of all notifications - * dropdown : a responsive Bootstrap dropdown with full notification handling - -note : one argument is required - * seen * true : will display all notification (default behavior) * false : will display only unseen notifications -As optional second argument, you can pass a user. By default current user is selected +* template + * use the the twig file you provide instead of the default one. NOTE : the notification list is called ``notificationList`` -Usage: -~~~~~~ -**Notification count :** :: - {{ mgilet_notification_count() }} {# all notifications #} - - {{ mgilet_unseen_notification_count() }} {# unseen notifications #} - -**Rendering:** - -Dropdown with all notifications:: - - {{ mgilet_notification_render({ 'display': 'dropdown', 'seen': true }) }} - -Or:: - - {{ mgilet_notification_render({ 'display': 'dropdown' }) }} - - -Only unseen notifications in dropdown:: + {{ mgilet_notification_render(notifiableEntity) }} - {{ mgilet_notification_render({ 'display': 'dropdown', 'seen': false }) }} + // only unseen notifications + {{ mgilet_notification_render(notifiableEntity ,{'seen': false }) }} -List with all notifications:: + // custom template + {{ mgilet_notification_render({ 'template': 'Path/to/my/template.html.twig'}) }} - {{ mgilet_notification_render({ 'display': 'list', 'seen': true }) }} - - -Or:: - - {{ mgilet_notification_render({ 'display': 'list' }) }} {# does the same thing #} +------------------ +* mgilet_notification_generate_path -List with only unseen notifications:: +this function will help you using the bundle's controller. It will generate links to the provided routes (list, mark_as_seen, mark_as_unseen, mark_all_as_seen) - {{ mgilet_notification_render({ 'display': 'list', 'seen': false }) }} Notification controller: @@ -155,18 +152,11 @@ The controller is located in ``vendor/mgilet/notification-bundle/Controller/NotificationController``. -Built in routes : -~~~~~~~~~~~~~~~~~ -* ``/notifications`` : return the ``list`` template with all notifications -* ``/notifications/{notification}/markAsSeen`` : mark the given notification as seen -* ``/notifications/{notification}/markAsUnseen``: mark the given notification as unseen -* ``/notifications/markAllAsSeen`` : mark all notifications as seen for the user +Go further : +------------ -Overriding parts of the bundle : --------------------------------- - -Go to `overriding parts of the bundle`_ +Go to `go further`_ ---------------------------------------------- @@ -174,15 +164,11 @@ Go to `overriding parts of the bundle`_ * `basic usage`_ -* `overriding parts of the bundle`_ - -* `advanced configuration`_ - * `go further`_ .. _installation: index.rst .. _basic usage: usage.rst -.. _overriding parts of the bundle: overriding.rst -.. _advanced configuration: advanced-configuration.rst .. _go further: further.rst + +.. _Symfony documentation: http://symfony.com/doc/current/bundles/override.html diff --git a/Resources/public/css/mgilet_notification.css b/Resources/public/css/mgilet_notification.css deleted file mode 100755 index 9b26477..0000000 --- a/Resources/public/css/mgilet_notification.css +++ /dev/null @@ -1,54 +0,0 @@ -.notification-menu { - width: 40vw; - max-width: 430px; - position: absolute; - top: 100%; - right: 0; - z-index: 1000; -} - -.scrollable-menu { - height: auto; - max-height: 50vh; - overflow-x: hidden; - margin: 0; -} - -.scrollable-menu a { - text-decoration: none; -} - -.scrollable-menu li { - padding: 6px 0 6px 10px; -} - -.scrollable-menu li:hover { - background-color: #eee; -} - -.scrollable-menu a.text-muted:hover { - color: #777; -} - -.no-padding { - padding: 0 !important; -} - -.no-margin { - margin: 0 !important; -} - -.no-float { - float: none; -} - -.list-group .list-group-item .row-content .action-secondary { - position: absolute; - right: 16px; - top: 8px; -} - -.seen *{ - color: #777; - font-weight:normal; -} diff --git a/Resources/public/js/ajax-notification.js b/Resources/public/js/ajax-notification.js deleted file mode 100755 index 44b25c7..0000000 --- a/Resources/public/js/ajax-notification.js +++ /dev/null @@ -1,69 +0,0 @@ -(function() { - - // ajax request to mark a notification as seen - function markAsSeen(e) { - var xhttp = new XMLHttpRequest(); - var element = e.target; - xhttp.onreadystatechange = function () { - // on success - if (xhttp.readyState == 4 && xhttp.status == 200) { - // mark notification as seen - element.parentNode.classList+= ' seen'; - // remove button - element.remove(); - // decrease notification count - var notificationCounter = document.getElementById('notificationCount'); - var notificationNumber = parseInt(notificationCounter.innerHTML); - notificationNumber--; - notificationCounter.innerHTML = notificationNumber.toString(); - if (notificationNumber == 0){ - notificationCounter.parentNode.parentNode.classList = ''; - } - } - }; - xhttp.open("POST", element.href, true); - xhttp.send(); - } - - function markAllAsSeen(e) { - var xhttp = new XMLHttpRequest(); - var element = e.target; - xhttp.onreadystatechange = function () { - // on success - if (xhttp.readyState == 4 && xhttp.status == 200) { - // add "seen" class for all notifications - var notifications = document.getElementsByClassName('notification'); - for (var notification of notifications){ - notification.children[0].classList += ' seen'; - } - // remove action buttons - var paras = document.getElementsByClassName('ajax-notification'); - while(paras[0]) { - paras[0].parentNode.removeChild(paras[0]); - } - // set notification count to 0 - var notificationCount = document.getElementById('notificationCount'); - notificationCount.innerHTML = '0'; - notificationCount.parentNode.parentNode.classList = ''; - } - }; - xhttp.open("POST", element.href, true); - xhttp.send(); - } - - // mark as seen button handler - var btns = document.getElementsByClassName('ajax-notification'); - for(var btn of btns){ - btn.addEventListener('click', function (e) { - e.preventDefault(); - markAsSeen(e); - }); - } - - // mark all as seen button handler - document.getElementById('notification-MarkAllAsSeen').addEventListener('click',function (e) { - e.preventDefault(); - markAllAsSeen(e); - }); - -})(); diff --git a/Resources/views/notification_list.html.twig b/Resources/views/notification_list.html.twig index 3d4addf..efdd7ba 100755 --- a/Resources/views/notification_list.html.twig +++ b/Resources/views/notification_list.html.twig @@ -1,4 +1,4 @@ Notifications : -{% for notification in notifications %} - {{ notification }} +{% for notificationItem in notificationList %} + {{ notificationItem[0] }} {% endfor %} diff --git a/Resources/views/notifications.html.twig b/Resources/views/notifications.html.twig index 3d4addf..8f99ac3 100755 --- a/Resources/views/notifications.html.twig +++ b/Resources/views/notifications.html.twig @@ -1,4 +1,22 @@ Notifications : -{% for notification in notifications %} - {{ notification }} -{% endfor %} +
+ +
+ diff --git a/Twig/NotificationExtension.php b/Twig/NotificationExtension.php index bf26332..a4bf497 100755 --- a/Twig/NotificationExtension.php +++ b/Twig/NotificationExtension.php @@ -2,7 +2,12 @@ namespace Mgilet\NotificationBundle\Twig; +use Doctrine\DBAL\Exception\InvalidArgumentException; +use Mgilet\NotificationBundle\Entity\NotifiableEntity; +use Mgilet\NotificationBundle\Entity\Notification; use Mgilet\NotificationBundle\Manager\NotificationManager; +use Mgilet\NotificationBundle\NotifiableInterface; +use Symfony\Component\Routing\Router; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Twig_Extension; @@ -14,6 +19,7 @@ class NotificationExtension extends Twig_Extension protected $notificationManager; protected $storage; protected $twig; + protected $router; /** * NotificationExtension constructor. @@ -21,11 +27,12 @@ class NotificationExtension extends Twig_Extension * @param TokenStorage $storage * @param \Twig_Environment $twig */ - public function __construct(NotificationManager $notificationManager, TokenStorage $storage, \Twig_Environment $twig) + public function __construct(NotificationManager $notificationManager, TokenStorage $storage, \Twig_Environment $twig, Router $router) { $this->notificationManager = $notificationManager; $this->storage = $storage; $this->twig = $twig; + $this->router = $router; } /** @@ -40,7 +47,10 @@ public function getFunctions() new \Twig_SimpleFunction('mgilet_notification_count', array($this, 'countNotifications'), array( 'is_safe' => array('html') )), - new \Twig_SimpleFunction('mgilet_unseen_notification_count', array($this, 'countUnseenNotifications'), array( + new \Twig_SimpleFunction('mgilet_notification_unseen_count', array($this, 'countUnseenNotifications'), array( + 'is_safe' => array('html') + )), + new \Twig_SimpleFunction('mgilet_notification_generate_path', array($this, 'generatePath'), array( 'is_safe' => array('html') )) ); @@ -48,101 +58,148 @@ public function getFunctions() /** * Rendering notifications in Twig - * @param array $options - * @param null $user + * + * @param array $options + * @param NotifiableInterface $notifiable + * * @return null|string + * @throws \RuntimeException + * @throws \InvalidArgumentException */ - public function render($options = array(), $user = null) + public function render(NotifiableInterface $notifiable, array $options = array()) { if( !array_key_exists('seen',$options)) { $options['seen'] = true; } - if ($options['display'] === 'list') { - return $this->renderNotifications($user, $options['seen']); - } - if ($options['display'] === 'dropdown') { - return $this->renderDropdownNotifications($user, $options['seen']); - } - return null; + + return $this->renderNotifications($notifiable, $options); } /** - * Render notifications for a user - * @param null $user - * @param bool $seen + * Render notifications of the notifiable as a list + * + * @param NotifiableInterface $notifiable + * @param array $options + * * @return string - * @internal param \Twig_Environment $twig + * + * @throws \RuntimeException + * @throws \InvalidArgumentException */ - public function renderNotifications($user = null, $seen = true) + public function renderNotifications(NotifiableInterface $notifiable, array $options) { - $user = $this->getUser($user); - if ($seen) { - $notifications = $this->notificationManager->getUserNotifications($user); + if ($options['seen']) { + $notifications = $this->notificationManager->getNotifications($notifiable); } else { - $notifications = $this->notificationManager->getUnseenUserNotifications($user); + $notifications = $this->notificationManager->getUnseenNotifications($notifiable); } - return $this->twig->render('MgiletNotificationBundle::notification_list.html.twig', + // if the template option is set, use custom template + $template = array_key_exists('template', $options) ? $options['template'] : 'MgiletNotificationBundle::notification_list.html.twig'; + + return $this->twig->render($template, array( - 'notifications' => $notifications + 'notificationList' => $notifications ) ); } /** - * Render notifications for a user in a dropdown (Bootstrap 3 highly recommended) - * @param null $user - * @param bool $seen - * @return mixed - */ - public function renderDropdownNotifications($user = null, $seen = true) - { - $user = $this->getUser($user); - if ($seen) { - $notifications = $this->notificationManager->getUserNotifications($user); - } else { - $notifications = $this->notificationManager->getUnseenUserNotifications($user); - } - - return $this->twig->render('MgiletNotificationBundle::notification_dropdown.html.twig', array( - 'notifications' => $notifications - )); - } - - /** - * Display the total count of notifications for this user - * @param null $user + * Display the total count of notifications for the notifiable + * + * @param NotifiableInterface $notifiable + * * @return int + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException */ - public function countNotifications($user = null) + public function countNotifications(NotifiableInterface $notifiable) { - $user = $this->getUser($user); - return $this->notificationManager->getNotificationCount($user); + return $this->notificationManager->getNotificationCount($notifiable); } /** - * Display the count of unseen notifications for this user - * @param null $user + * Display the count of unseen notifications for this notifiable + * + * @param NotifiableInterface $notifiable + * * @return int + * + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Doctrine\ORM\NonUniqueResultException + * @throws \Doctrine\ORM\NoResultException */ - public function countUnseenNotifications($user = null) + public function countUnseenNotifications(NotifiableInterface $notifiable) { - $user = $this->getUser($user); - return $this->notificationManager->getUnseenNotificationCount($user); + return $this->notificationManager->getUnseenNotificationCount($notifiable); } /** - * If no user is specified return current user - * @param null $user - * @return mixed user + * Returns the path to the NotificationController action + * + * @param $route + * @param $notifiable + * @param Notification|null $notification + * + * @return \InvalidArgumentException|string + * @throws \RuntimeException + * @throws \Doctrine\DBAL\Exception\InvalidArgumentException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Doctrine\ORM\ORMInvalidArgumentException + * @throws \InvalidArgumentException */ - private function getUser($user = null) + public function generatePath($route, $notifiable, Notification $notification = null) { - if (!$user) { - return $this->storage->getToken()->getUser(); + if ($notifiable instanceof NotifiableInterface){ + $notifiableId = $this->notificationManager->getNotifiableEntity($notifiable)->getId(); + } elseif ($notifiable instanceof NotifiableEntity){ + $notifiableId = $notifiable->getId(); + } else { + throw new InvalidArgumentException('You must provide a NotifiableInterface or NotifiableEntity object'); } - return $user; + switch ($route){ + case 'notification_list': + return $this->router->generate( + 'notification_list', + array('notifiable' => $notifiableId) + ); + break; + case 'notification_mark_as_seen': + if (!$notification){ + throw new \InvalidArgumentException('You must provide a Notification Entity'); + } + + return $this->router->generate( + 'notification_mark_as_seen', + array( + 'notifiable' => $notifiableId, + 'notification' => $notification->getId() + ) + ); + break; + case 'notification_mark_as_unseen': + if (!$notification){ + throw new \InvalidArgumentException('You must provide a Notification Entity'); + } + + return $this->router->generate( + 'notification_mark_as_unseen', + array( + 'notifiable' => $notifiableId, + 'notification' => $notification->getId() + ) + ); + break; + case 'notification_mark_all_as_seen': + return $this->router->generate('notification_mark_all_as_seen', array('notifiable' => $notifiableId)); + break; + default: + return new \InvalidArgumentException('You must provide a valid route path. Paths availables : notification_list, notification_mark_as_seen, notification_mark_as_unseen, notification_mark_all_as_seen'); + } } /** diff --git a/composer.json b/composer.json index 6dd3a5b..d36b9e3 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }], "extra" : { "branch-alias" : { - "dev-master" : "1.0-dev" + "dev-master" : "2.0-dev" } } }