Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
32.23% covered (danger)
32.23%
68 / 211
25.00% covered (danger)
25.00%
3 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Notification
32.23% covered (danger)
32.23%
68 / 211
25.00% covered (danger)
25.00%
3 / 12
1339.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getModelClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryBuilder
64.29% covered (warning)
64.29%
18 / 28
0.00% covered (danger)
0.00%
0 / 1
10.92
 findAll
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 findAllUnread
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
3.04
 persist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 findOneBy
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 count
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 findPaginated
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 addTableSearches
15.49% covered (danger)
15.49%
11 / 71
0.00% covered (danger)
0.00%
0 / 1
314.09
 addTableOrder
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
182
 objectExists
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
5.68
1<?php
2
3/**
4 * This file is part of the MADIS - RGPD Management application.
5 *
6 * @copyright Copyright (c) 2018-2019 Soluris - Solutions Numériques Territoriales Innovantes
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22declare(strict_types=1);
23
24namespace App\Infrastructure\ORM\Notification\Repository;
25
26use App\Application\Doctrine\Repository\CRUDRepository;
27use App\Application\Traits\RepositoryUtils;
28use App\Domain\Notification\Model;
29use App\Domain\Notification\Repository;
30use App\Domain\Registry\Model\ConformiteTraitement\ConformiteTraitement;
31use App\Domain\Registry\Model\Mesurement;
32use App\Domain\User\Dictionary\UserMoreInfoDictionary;
33use App\Domain\User\Dictionary\UserRoleDictionary;
34use App\Domain\User\Model\Collectivity;
35use App\Domain\User\Model\User;
36use Doctrine\Common\Collections\ArrayCollection;
37use Doctrine\Common\Collections\Collection;
38use Doctrine\ORM\Query\Expr\OrderBy;
39use Doctrine\ORM\QueryBuilder;
40use Doctrine\ORM\Tools\Pagination\Paginator;
41use Doctrine\Persistence\ManagerRegistry;
42use Symfony\Component\Security\Core\Security;
43
44class Notification extends CRUDRepository implements Repository\Notification
45{
46    use RepositoryUtils;
47
48    protected Security $security;
49    protected bool $notificationsActive;
50    protected string $saveNotificationsDuration;
51
52    public function __construct(ManagerRegistry $registry, Security $security, bool $notificationsActive, string $saveNotificationsDuration)
53    {
54        parent::__construct($registry);
55        $this->security                  = $security;
56        $this->notificationsActive       = $notificationsActive;
57        $this->saveNotificationsDuration = $saveNotificationsDuration;
58    }
59
60    protected function getModelClass(): string
61    {
62        return Model\Notification::class;
63    }
64
65    private function getQueryBuilder(array $order = ['createdAt' => 'DESC']): QueryBuilder
66    {
67        /**
68         * @var User $user
69         */
70        $user = $this->security->getUser();
71
72        $allNotifs = false;
73
74        $allowedRoles = [UserRoleDictionary::ROLE_REFERENT, UserRoleDictionary::ROLE_ADMIN];
75        if ($user && (count($user->getRoles()) && in_array($user->getRoles()[0], $allowedRoles)) || in_array(UserMoreInfoDictionary::MOREINFO_DPD, $user->getMoreInfos())) {
76            // Find notifications with null user if current user is dpo
77            $allNotifs = true;
78        }
79
80        $qb = $this->createQueryBuilder()
81            ->addSelect('collectivity')
82            ->leftJoin('o.collectivity', 'collectivity')
83        ;
84
85        if ($allNotifs) {
86            $qb->where('o.dpo = 1');
87            if (!in_array(UserRoleDictionary::ROLE_ADMIN, $user->getRoles())) {
88                // For non admin users
89                if (in_array(UserRoleDictionary::ROLE_REFERENT, $user->getRoles())) {
90                    $cf = $user->getCollectivitesReferees();
91                    $cf = new ArrayCollection([...$cf]);
92                    //                    if (!is_object($cf) || ArrayCollection::class !== get_class($cf)) {
93                    //                        $cf = new ArrayCollection([...$cf]);
94                    //                    }
95                    $collectivityIds = $cf->map(function (Collectivity $c) {return $c->getId()->__toString(); })->toArray();
96                } else {
97                    $collectivityIds = [$user->getCollectivity()->getId()->__toString(), null];
98                }
99                $qb->innerJoin('o.collectivity', 'c');
100                $qb->andWhere(
101                    $qb->expr()->in('c.id', ':ids')
102                );
103                $qb->setParameter('ids', $collectivityIds);
104            }
105        } else {
106            $qb->leftJoin('o.notificationUsers', 'u')
107                ->where('u.active = 1')
108                ->where('u.user = :user')
109                ->setParameter('user', $user)
110            ;
111        }
112
113        return $qb;
114    }
115
116    public function findAll(array $order = ['createdAt' => 'DESC']): array
117    {
118        $qb = $this->getQueryBuilder($order);
119
120        foreach ($order as $field => $direction) {
121            $qb->addOrderBy(new OrderBy('o.' . $field, $direction));
122        }
123
124        return $qb->getQuery()->getResult();
125    }
126
127    public function findAllUnread(array $order = ['createdAt' => 'DESC']): array
128    {
129        // If notifications are not active, delete all notifications
130        if (false === $this->notificationsActive) {
131            $em = $this->getManager();
132            $em->createQuery('DELETE FROM App\Domain\Notification\Model\NotificationUser')->execute();
133            $em->createQuery('DELETE FROM App\Domain\Notification\Model\Notification')->execute();
134
135            return [];
136        }
137
138        // If notifications are active, delete old notifications
139
140        $qb = $this->getManager()->createQueryBuilder();
141        $qb->where('o.createdAt < :date');
142        $prevDate = new \DateTime();
143        $prevDate->modify('-' . $this->saveNotificationsDuration);
144        $qb->setParameter('date', $prevDate);
145        $qb->delete('App\Domain\Notification\Model\NotificationUser as o');
146        $qb->getQuery()->execute();
147
148        $qb = $this->getManager()->createQueryBuilder();
149        $qb->where('o.createdAt < :date');
150        $prevDate = new \DateTime();
151        $prevDate->modify('-' . $this->saveNotificationsDuration);
152        $qb->setParameter('date', $prevDate);
153        $qb->delete('App\Domain\Notification\Model\Notification as o');
154        $qb->getQuery()->execute();
155
156        $qb = $this->getQueryBuilder($order);
157
158        foreach ($order as $field => $direction) {
159            $qb->addOrderBy(new OrderBy('o.' . $field, $direction));
160        }
161
162        $this->addTableSearches($qb, ['state' => 'unread']);
163
164        return $qb->getQuery()->getResult();
165    }
166
167    public function persist($object)
168    {
169        $this->getManager()->persist($object);
170    }
171
172    public function findOneBy(array $criteria)
173    {
174        $notifs = $this->registry
175            ->getManager()
176            ->getRepository($this->getModelClass())
177            ->findBy($criteria)
178        ;
179        if (count($notifs) > 0) {
180            return $notifs[0];
181        }
182    }
183
184    public function count(array $criteria = [])
185    {
186        $qb = $this->getQueryBuilder();
187
188        $qb->select('COUNT(o.id)');
189
190        if (isset($criteria['collectivity']) && $criteria['collectivity'] instanceof Collection) {
191            $collectivityIds = $criteria['collectivity']->toArray();
192            array_push($collectivityIds, null);
193            // $qb->innerJoin('o.collectivity', 'collectivite');
194            $qb->andWhere(
195                $qb->expr()->in('collectivity', ':collectivities')
196            )
197                ->setParameter('collectivities', $collectivityIds)
198            ;
199            unset($criteria['collectivity']);
200        }
201        //
202        //        dd($criteria);
203
204        foreach ($criteria as $key => $value) {
205            $this->addWhereClause($qb, $key, $value);
206        }
207
208        return $qb->getQuery()->getSingleScalarResult();
209    }
210
211    public function findPaginated($firstResult, $maxResults, $orderColumn, $orderDir, $searches, $criteria = [])
212    {
213        $qb = $this->getQueryBuilder();
214
215        //        if (isset($criteria['collectivity']) && $criteria['collectivity'] instanceof Collection) {
216        //            $collectivityIds = $criteria['collectivity']->map(function($c) {
217        //                if ($c instanceof Collectivity) {
218        //                    return $c->getId()->__toString();
219        //                }
220        //                return $c;
221        //            })->toArray();
222        //            //dd($collectivityIds);
223        //            array_push($collectivityIds, null);
224        //            //$qb->innerJoin('o.collectivity', 'collectivite');
225        //            $qb->andWhere(
226        //                $qb->expr()->in('collectivity', ':collectivities')
227        //            )
228        //                ->setParameter('collectivities', $collectivityIds)
229        //            ;
230        //
231        //            //dd($qb->getQuery()->getParameters());
232        unset($criteria['collectivity']);
233        //        }
234
235        foreach ($criteria as $key => $value) {
236            $this->addWhereClause($qb, $key, $value);
237        }
238
239        $this->addTableOrder($qb, $orderColumn, $orderDir);
240        $this->addTableSearches($qb, $searches);
241
242        $qb = $qb->getQuery();
243        $qb->setFirstResult($firstResult);
244        $qb->setMaxResults($maxResults);
245
246        return new Paginator($qb);
247    }
248
249    private function addTableSearches(QueryBuilder $queryBuilder, $searches)
250    {
251        /** @var User $user */
252        $user = $this->security->getUser();
253        foreach ($searches as $columnName => $search) {
254            switch ($columnName) {
255                case 'state':
256                    if ('read' === $search) {
257                        if ($this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_REFERENT') || in_array(UserMoreInfoDictionary::MOREINFO_DPD, $user->getMoreInfos())) {
258                            $queryBuilder->andWhere('o.readAt IS NOT NULL');
259                        } else {
260                            $queryBuilder->leftJoin('o.notificationUsers', 'nu');
261                            $queryBuilder->andWhere('nu.user=:user');
262                            $queryBuilder->andWhere('nu.readAt IS NOT NULL');
263                            $queryBuilder->setParameter('user', $user);
264                        }
265                    } else {
266                        if ($this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_REFERENT') || in_array(UserMoreInfoDictionary::MOREINFO_DPD, $user->getMoreInfos())) {
267                            $queryBuilder->andWhere('o.readAt IS NULL');
268                        } else {
269                            $queryBuilder->leftJoin('o.notificationUsers', 'nu');
270                            $queryBuilder->andWhere('nu.user=:user');
271                            $queryBuilder->andWhere('nu.readAt IS NULL');
272                            $queryBuilder->setParameter('user', $user);
273                        }
274                    }
275
276                    break;
277                case 'collectivity':
278                    $queryBuilder->andWhere('collectivity.name LIKE :nom')
279                        ->setParameter('nom', '%' . $search . '%');
280                    break;
281                case 'name':
282                    $queryBuilder->andWhere('o.name LIKE :name')
283                        ->setParameter('name', '%' . $search . '%');
284                    break;
285                case 'action':
286                    if ('automatic' === $search) {
287                        $queryBuilder
288                            ->andWhere('o.action LIKE :a1 OR o.action LIKE :a2 OR o.action LIKE :a3')
289                            ->setParameter('a1', '%no_login')
290                            ->setParameter('a2', '%late_survey')
291                            ->setParameter('a3', '%late_action')
292                        ;
293                    } else {
294                        $queryBuilder->andWhere('o.action LIKE :action')
295                            ->setParameter('action', '%' . $search . '%');
296                    }
297
298                    break;
299                case 'module':
300                    $queryBuilder->andWhere('o.module LIKE :module')
301                        ->setParameter('module', '%' . $search . '%');
302                    break;
303                case 'object':
304                    $queryBuilder->andWhere('o.subject LIKE :object')
305                        ->setParameter('object', '%' . $search . '%');
306                    break;
307                case 'date':
308                    $queryBuilder->andWhere('o.createdAt BETWEEN :createdAt_start AND :createdAt_end')
309                        ->setParameter('createdAt_start', date_create_from_format('d/m/y', substr($search, 0, 8))->format('Y-m-d 00:00:00'))
310                        ->setParameter('createdAt_end', date_create_from_format('d/m/y', substr($search, 11, 8))->format('Y-m-d 23:59:59'));
311                    break;
312                case 'user':
313                    $queryBuilder->leftJoin('o.createdBy', 'cb');
314                    $queryBuilder->andWhere('CONCAT(cb.firstName, \' \', cb.lastName) LIKE :user')
315                        ->setParameter('user', '%' . $search . '%');
316                    break;
317                case 'read_date':
318                    if ($this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_REFERENT')) {
319                        $queryBuilder->andWhere('o.readAt BETWEEN :readAt_start AND :readAt_end')
320                            ->setParameter('readAt_start', date_create_from_format('d/m/y', substr($search, 0, 8))->format('Y-m-d 00:00:00'))
321                            ->setParameter('readAt_end', date_create_from_format('d/m/y', substr($search, 11, 8))->format('Y-m-d 23:59:59'));
322                    } else {
323                        $queryBuilder->leftJoin('o.notificationUsers', 'nu2');
324                        $queryBuilder->andWhere('nu2.user=:user');
325                        $queryBuilder->andWhere('nu2.readAt BETWEEN :readAt_start AND :readAt_end')
326                            ->setParameter('readAt_start', date_create_from_format('d/m/y', substr($search, 0, 8))->format('Y-m-d 00:00:00'))
327                            ->setParameter('readAt_end', date_create_from_format('d/m/y', substr($search, 11, 8))->format('Y-m-d 23:59:59'));
328                        $queryBuilder->setParameter('user', $user);
329                    }
330                    break;
331                case 'updatedAt':
332                    $queryBuilder->andWhere('o.updatedAt BETWEEN :updatedAt_start AND :updatedAt_end')
333                        ->setParameter('updatedAt_start', date_create_from_format('d/m/y', substr($search, 0, 8))->format('Y-m-d 00:00:00'))
334                        ->setParameter('updatedAt_end', date_create_from_format('d/m/y', substr($search, 11, 8))->format('Y-m-d 23:59:59'));
335                    break;
336            }
337        }
338    }
339
340    private function addTableOrder(QueryBuilder $queryBuilder, $orderColumn, $orderDir)
341    {
342        switch ($orderColumn) {
343            case 'read_date':
344            case 'state':
345                if ($this->security->isGranted('ROLE_ADMIN') || $this->security->isGranted('ROLE_REFERENT')) {
346                    $queryBuilder->addOrderBy('o.readAt', $orderDir);
347                } else {
348                    $user = $this->security->getUser();
349                    $queryBuilder->leftJoin('o.notificationUsers', 'nu3');
350                    $queryBuilder->andWhere('nu3.user=:user');
351                    $queryBuilder->addOrderBy('nu3.readAt');
352                    $queryBuilder->setParameter('user', $user);
353                }
354                break;
355            case 'name':
356                $queryBuilder->addOrderBy('o.name', $orderDir);
357                break;
358            case 'collectivity':
359                $queryBuilder->addOrderBy('collectivity.name', $orderDir);
360                break;
361            case 'action':
362                $queryBuilder->addOrderBy('o.action', $orderDir);
363                break;
364            case 'object':
365                $queryBuilder->addOrderBy('o.subject', $orderDir);
366                break;
367            case 'module':
368                $queryBuilder->addOrderBy('o.module', $orderDir);
369                break;
370            case 'date':
371                $queryBuilder->addOrderBy('o.createdAt', $orderDir);
372                break;
373            case 'user':
374                $queryBuilder->leftJoin('o.createdBy', 'cb');
375                $queryBuilder->addSelect('CONCAT(cb.firstName, \' \', cb.lastName)
376                    AS HIDDEN person_name')
377                    ->addOrderBy('person_name', $orderDir);
378                break;
379            case 'updatedAt':
380                $queryBuilder->addOrderBy('o.updatedAt', $orderDir);
381                break;
382        }
383    }
384
385    public function objectExists(Model\Notification $notification): bool
386    {
387        if (!$notification->getObject()) {
388            return false;
389        }
390
391        $object     = $notification->getObject();
392        $moduleName = str_replace('notification.modules.', '', $notification->getModule());
393
394        if ('action_plan' === $moduleName) {
395            $objectClass = Mesurement::class;
396        } elseif ('aipd' === $moduleName && 'notifications.actions.treatment_needs_aipd' === $notification->getAction()) {
397            $objectClass = ConformiteTraitement::class;
398        } else {
399            $objectClass = array_flip(Model\Notification::MODULES)[$moduleName];
400        }
401
402        return (bool) $this->registry->getRepository($objectClass)->find($object->id);
403    }
404}