Passed
Push — master ( 81f434...30c4c3 )
by Angel Fernando Quiroz
09:29
created

ResourceRepository::isGranted()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Repository;
8
9
use Chamilo\CoreBundle\Entity\AbstractResource;
10
use Chamilo\CoreBundle\Entity\Course;
11
use Chamilo\CoreBundle\Entity\ResourceFile;
12
use Chamilo\CoreBundle\Entity\ResourceInterface;
13
use Chamilo\CoreBundle\Entity\ResourceLink;
14
use Chamilo\CoreBundle\Entity\ResourceNode;
15
use Chamilo\CoreBundle\Entity\ResourceRight;
16
use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface;
17
use Chamilo\CoreBundle\Entity\ResourceType;
18
use Chamilo\CoreBundle\Entity\Session;
19
use Chamilo\CoreBundle\Entity\User;
20
use Chamilo\CoreBundle\Helpers\CreateUploadedFileHelper;
21
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
22
use Chamilo\CoreBundle\Traits\NonResourceRepository;
23
use Chamilo\CoreBundle\Traits\Repository\RepositoryQueryBuilderTrait;
24
use Chamilo\CourseBundle\Entity\CGroup;
25
use DateTime;
26
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
27
use Doctrine\Common\Collections\ArrayCollection;
28
use Doctrine\DBAL\Types\Types;
29
use Doctrine\ORM\QueryBuilder;
30
use Exception;
31
use LogicException;
32
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
33
use Symfony\Component\HttpFoundation\File\UploadedFile;
34
use Throwable;
35
36
use const PATHINFO_EXTENSION;
37
38
/**
39
 * Extends Resource EntityRepository.
40
 */
41
abstract class ResourceRepository extends ServiceEntityRepository
42
{
43
    use NonResourceRepository;
44
    use RepositoryQueryBuilderTrait;
45
46
    protected ?ResourceType $resourceType = null;
47
48
    public function getCount(QueryBuilder $qb): int
49
    {
50
        $qb
51
            ->select('count(resource)')
52
            ->setMaxResults(1)
53
            ->setFirstResult(null)
54
        ;
55
56
        return (int) $qb->getQuery()->getSingleScalarResult();
57
    }
58
59
    public function getResourceByResourceNode(ResourceNode $resourceNode): ?ResourceInterface
60
    {
61
        return $this->findOneBy([
62
            'resourceNode' => $resourceNode,
63
        ]);
64
    }
65
66
    public function create(AbstractResource $resource): void
67
    {
68
        $this->getEntityManager()->persist($resource);
69
        $this->getEntityManager()->flush();
70
    }
71
72
    public function update(AbstractResource|User $resource, bool $andFlush = true): void
73
    {
74
        if (!$resource->hasResourceNode()) {
75
            throw new Exception('Resource needs a resource node');
76
        }
77
78
        $em = $this->getEntityManager();
79
80
        $resource->getResourceNode()->setUpdatedAt(new DateTime());
81
        $resource->getResourceNode()->setTitle($resource->getResourceName());
82
        $em->persist($resource);
83
84
        if ($andFlush) {
85
            $em->flush();
86
        }
87
    }
88
89
    public function updateNodeForResource(ResourceInterface $resource): ResourceNode
90
    {
91
        $em = $this->getEntityManager();
92
93
        $resourceNode = $resource->getResourceNode();
94
        $resourceName = $resource->getResourceName();
95
96
        foreach ($resourceNode->getResourceFiles() as $resourceFile) {
97
            if (null !== $resourceFile) {
98
                $originalName = $resourceFile->getOriginalName();
99
                $originalExtension = pathinfo($originalName, PATHINFO_EXTENSION);
100
101
                // $originalBasename = \basename($resourceName, $originalExtension);
102
                /*$slug = sprintf(
103
                    '%s.%s',
104
                    $this->slugify->slugify($originalBasename),
105
                    $this->slugify->slugify($originalExtension)
106
                );*/
107
108
                $newOriginalName = \sprintf('%s.%s', $resourceName, $originalExtension);
109
                $resourceFile->setOriginalName($newOriginalName);
110
111
                $em->persist($resourceFile);
112
            }
113
        }
114
        // $slug = $this->slugify->slugify($resourceName);
115
116
        $resourceNode->setTitle($resourceName);
117
        // $resourceNode->setSlug($slug);
118
119
        $em->persist($resourceNode);
120
        $em->persist($resource);
121
122
        $em->flush();
123
124
        return $resourceNode;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $resourceNode could return the type null which is incompatible with the type-hinted return Chamilo\CoreBundle\Entity\ResourceNode. Consider adding an additional type-check to rule them out.
Loading history...
125
    }
126
127
    public function findCourseResourceByTitle(
128
        string $title,
129
        ResourceNode $parentNode,
130
        Course $course,
131
        ?Session $session = null,
132
        ?CGroup $group = null
133
    ): ?ResourceInterface {
134
        $qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
135
        $this->addTitleQueryBuilder($title, $qb);
136
        $qb->setMaxResults(1);
137
138
        return $qb->getQuery()->getOneOrNullResult();
139
    }
140
141
    public function findCourseResourceBySlug(
142
        string $title,
143
        ResourceNode $parentNode,
144
        Course $course,
145
        ?Session $session = null,
146
        ?CGroup $group = null
147
    ): ?ResourceInterface {
148
        $qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
149
        $this->addSlugQueryBuilder($title, $qb);
150
        $qb->setMaxResults(1);
151
152
        return $qb->getQuery()->getOneOrNullResult();
153
    }
154
155
    /**
156
     * Find resources ignoring the visibility.
157
     */
158
    public function findCourseResourceBySlugIgnoreVisibility(
159
        string $title,
160
        ResourceNode $parentNode,
161
        Course $course,
162
        ?Session $session = null,
163
        ?CGroup $group = null
164
    ): ?ResourceInterface {
165
        $qb = $this->getResourcesByCourseIgnoreVisibility($course, $session, $group, $parentNode);
166
        $this->addSlugQueryBuilder($title, $qb);
167
        $qb->setMaxResults(1);
168
169
        return $qb->getQuery()->getOneOrNullResult();
170
    }
171
172
    /**
173
     * @return ResourceInterface[]
174
     */
175
    public function findCourseResourcesByTitle(
176
        string $title,
177
        ResourceNode $parentNode,
178
        Course $course,
179
        ?Session $session = null,
180
        ?CGroup $group = null
181
    ) {
182
        $qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
183
        $this->addTitleQueryBuilder($title, $qb);
184
185
        return $qb->getQuery()->getResult();
186
    }
187
188
    /**
189
     * @todo clean path
190
     */
191
    public function addFileFromPath(ResourceInterface $resource, string $fileName, string $path, bool $flush = true): ?ResourceFile
192
    {
193
        if (!empty($path) && file_exists($path) && !is_dir($path)) {
194
            $mimeType = mime_content_type($path);
195
            $file = new UploadedFile($path, $fileName, $mimeType, null, true);
196
197
            return $this->addFile($resource, $file, '', $flush);
198
        }
199
200
        return null;
201
    }
202
203
    public function addFileFromString(ResourceInterface $resource, string $fileName, string $mimeType, string $content, bool $flush = true): ?ResourceFile
204
    {
205
        $file = CreateUploadedFileHelper::fromString($fileName, $mimeType, $content);
206
207
        return $this->addFile($resource, $file, '', $flush);
208
    }
209
210
    public function addFileFromFileRequest(ResourceInterface $resource, string $fileKey, bool $flush = true): ?ResourceFile
211
    {
212
        $request = $this->getRequest();
213
        if ($request->files->has($fileKey)) {
214
            $file = $request->files->get($fileKey);
215
            if (null !== $file) {
216
                $resourceFile = $this->addFile($resource, $file);
217
                if ($flush) {
218
                    $this->getEntityManager()->flush();
219
                }
220
221
                return $resourceFile;
222
            }
223
        }
224
225
        return null;
226
    }
227
228
    public function addFile(ResourceInterface $resource, UploadedFile $file, string $description = '', bool $flush = false): ?ResourceFile
229
    {
230
        $resourceNode = $resource->getResourceNode();
231
232
        if (null === $resourceNode) {
233
            throw new LogicException('Resource node is null');
234
        }
235
236
        $em = $this->getEntityManager();
237
238
        $resourceFile = new ResourceFile();
239
        $resourceFile
240
            ->setFile($file)
241
            ->setDescription($description)
242
            ->setTitle($resource->getResourceName())
243
            ->setResourceNode($resourceNode)
244
        ;
245
        $resourceNode->addResourceFile($resourceFile);
246
        $em->persist($resourceNode);
247
248
        if ($flush) {
249
            $em->flush();
250
        }
251
252
        return $resourceFile;
253
    }
254
255
    public function getResourceType(): ResourceType
256
    {
257
        $resourceTypeName = $this->toolChain->getResourceTypeNameByEntity($this->getClassName());
0 ignored issues
show
Bug introduced by
The method getResourceTypeNameByEntity() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

257
        /** @scrutinizer ignore-call */ 
258
        $resourceTypeName = $this->toolChain->getResourceTypeNameByEntity($this->getClassName());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
258
        $repo = $this->getEntityManager()->getRepository(ResourceType::class);
259
260
        return $repo->findOneBy([
261
            'title' => $resourceTypeName,
262
        ]);
263
    }
264
265
    public function addVisibilityQueryBuilder(?QueryBuilder $qb = null, bool $checkStudentView = false, bool $displayOnlyPublished = true): QueryBuilder
266
    {
267
        $qb = $this->getOrCreateQueryBuilder($qb);
268
269
        // TODO Avoid global assumption for a request, and inject
270
        // the request stack instead.
271
        $request = $this->getRequest();
272
        $sessionStudentView = null;
273
        if (null !== $request) {
274
            $sessionStudentView = $request->getSession()->get('studentview');
275
        }
276
277
        $checker = $this->getAuthorizationChecker();
278
        $isAdminOrTeacher =
279
            $checker->isGranted('ROLE_ADMIN')
280
            || $checker->isGranted('ROLE_CURRENT_COURSE_TEACHER');
281
282
        if ($displayOnlyPublished) {
283
            if (!$isAdminOrTeacher
284
                || ($checkStudentView && 'studentview' === $sessionStudentView)
285
            ) {
286
                $qb
287
                    ->andWhere('links.visibility = :visibility')
288
                    ->setParameter('visibility', ResourceLink::VISIBILITY_PUBLISHED, Types::INTEGER)
289
                ;
290
            }
291
        }
292
293
        // @todo Add start/end visibility restrictions.
294
295
        return $qb;
296
    }
297
298
    public function addCourseQueryBuilder(Course $course, QueryBuilder $qb): QueryBuilder
299
    {
300
        $qb
301
            ->andWhere('links.course = :course')
302
            ->setParameter('course', $course)
303
        ;
304
305
        return $qb;
306
    }
307
308
    public function addCourseSessionGroupQueryBuilder(
309
        ?Course $course = null,
310
        ?Session $session = null,
311
        ?CGroup $group = null,
312
        ?QueryBuilder $qb = null
313
    ): QueryBuilder {
314
        $reflectionClass = $this->getClassMetadata()->getReflectionClass();
315
316
        // Check if this resource type requires to load the base course resources when using a session
317
        $loadBaseSessionContent = \in_array(
318
            ResourceShowCourseResourcesInSessionInterface::class,
319
            $reflectionClass->getInterfaceNames(),
320
            true
321
        );
322
323
        if ($course) {
324
            $this->addCourseQueryBuilder($course, $qb);
325
        }
326
327
        if (null === $session) {
328
            $qb->andWhere(
329
                $qb->expr()->orX(
0 ignored issues
show
Bug introduced by
The method expr() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

329
                $qb->/** @scrutinizer ignore-call */ 
330
                     expr()->orX(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
330
                    $qb->expr()->isNull('links.session'),
331
                    $qb->expr()->eq('links.session', 0)
332
                )
333
            );
334
        } elseif ($loadBaseSessionContent) {
335
            // Load course base content.
336
            $qb->andWhere('links.session = :session OR links.session IS NULL');
337
            $qb->setParameter('session', $session);
338
        } else {
339
            // Load only session resources.
340
            $qb->andWhere('links.session = :session');
341
            $qb->setParameter('session', $session);
342
        }
343
344
        if (null === $group) {
345
            $qb->andWhere(
346
                $qb->expr()->orX(
347
                    $qb->expr()->isNull('links.group'),
348
                    $qb->expr()->eq('links.group', 0)
349
                )
350
            );
351
        } else {
352
            $qb->andWhere('links.group = :group');
353
            $qb->setParameter('group', $group);
354
        }
355
356
        return $qb;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $qb could return the type null which is incompatible with the type-hinted return Doctrine\ORM\QueryBuilder. Consider adding an additional type-check to rule them out.
Loading history...
357
    }
358
359
    public function getResourceTypeName(): string
360
    {
361
        return $this->toolChain->getResourceTypeNameByEntity($this->getClassName());
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->toolChain-...($this->getClassName()) could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
362
    }
363
364
    public function getResources(?ResourceNode $parentNode = null): QueryBuilder
365
    {
366
        $resourceTypeName = $this->getResourceTypeName();
367
368
        $qb = $this->createQueryBuilder('resource')
369
            ->select('resource')
370
            ->innerJoin('resource.resourceNode', 'node')
371
            ->innerJoin('node.resourceLinks', 'links')
372
            ->innerJoin('node.resourceType', 'type')
373
            ->leftJoin('node.resourceFiles', 'file')
374
            ->where('type.title = :type')
375
            ->setParameter('type', $resourceTypeName, Types::STRING)
376
            ->addSelect('node')
377
            ->addSelect('links')
378
            ->addSelect('type')
379
            ->addSelect('file')
380
        ;
381
382
        if (null !== $parentNode) {
383
            $qb->andWhere('node.parent = :parentNode');
384
            $qb->setParameter('parentNode', $parentNode);
385
        }
386
387
        return $qb;
388
    }
389
390
    public function getResourcesByCourse(
391
        ?Course $course = null,
392
        ?Session $session = null,
393
        ?CGroup $group = null,
394
        ?ResourceNode $parentNode = null,
395
        bool $displayOnlyPublished = true,
396
        bool $displayOrder = false
397
    ): QueryBuilder {
398
        $qb = $this->getResources($parentNode);
399
        $this->addVisibilityQueryBuilder($qb, true, $displayOnlyPublished);
400
        $this->addCourseSessionGroupQueryBuilder($course, $session, $group, $qb);
401
402
        if ($displayOrder) {
403
            $qb->orderBy('links.displayOrder', 'ASC');
404
        }
405
406
        return $qb;
407
    }
408
409
    public function getResourcesBySession(
410
        ?Session $session = null,
411
        ?ResourceNode $parentNode = null,
412
        bool $displayOnlyPublished = true,
413
        bool $displayOrder = false
414
    ): QueryBuilder {
415
        $qb = $this->getResources($parentNode);
416
        $this->addVisibilityQueryBuilder($qb, true, $displayOnlyPublished);
417
        $this->addCourseSessionGroupQueryBuilder(null, $session, null, $qb);
418
419
        if ($displayOrder) {
420
            $qb->orderBy('links.displayOrder', 'ASC');
421
        }
422
423
        return $qb;
424
    }
425
426
    public function getResourcesByCourseIgnoreVisibility(Course $course, ?Session $session = null, ?CGroup $group = null, ?ResourceNode $parentNode = null): QueryBuilder
427
    {
428
        $qb = $this->getResources($parentNode);
429
        $this->addCourseSessionGroupQueryBuilder($course, $session, $group, $qb);
430
431
        return $qb;
432
    }
433
434
    /**
435
     * Get resources only from the base course.
436
     */
437
    public function getResourcesByCourseOnly(Course $course, ?ResourceNode $parentNode = null): QueryBuilder
438
    {
439
        $qb = $this->getResources($parentNode);
440
        $this->addCourseQueryBuilder($course, $qb);
441
        $this->addVisibilityQueryBuilder($qb);
442
443
        $qb->andWhere('links.session IS NULL');
444
445
        return $qb;
446
    }
447
448
    public function getResourceByCreatorFromTitle(
449
        string $title,
450
        User $user,
451
        ResourceNode $parentNode
452
    ): ?ResourceInterface {
453
        $qb = $this->getResourcesByCreator($user, $parentNode);
454
        $this->addTitleQueryBuilder($title, $qb);
455
        $qb->setMaxResults(1);
456
457
        return $qb->getQuery()->getOneOrNullResult();
458
    }
459
460
    public function getResourcesByCreator(User $user, ?ResourceNode $parentNode = null): QueryBuilder
461
    {
462
        $qb = $this->createQueryBuilder('resource')
463
            ->select('resource')
464
            ->innerJoin('resource.resourceNode', 'node')
465
        ;
466
467
        if (null !== $parentNode) {
468
            $qb->andWhere('node.parent = :parentNode');
469
            $qb->setParameter('parentNode', $parentNode);
470
        }
471
472
        $this->addCreatorQueryBuilder($user, $qb);
473
474
        return $qb;
475
    }
476
477
    public function getResourcesByCourseLinkedToUser(
478
        User $user,
479
        Course $course,
480
        ?Session $session = null,
481
        ?CGroup $group = null,
482
        ?ResourceNode $parentNode = null
483
    ): QueryBuilder {
484
        $qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
485
        $qb->andWhere('node.creator = :user OR (links.user = :user OR links.user IS NULL)');
486
        $qb->setParameter('user', $user);
487
488
        return $qb;
489
    }
490
491
    public function getResourcesByLinkedUser(User $user, ?ResourceNode $parentNode = null): QueryBuilder
492
    {
493
        $qb = $this->getResources($parentNode);
494
        $qb
495
            ->andWhere('links.user = :user')
496
            ->setParameter('user', $user)
497
        ;
498
499
        $this->addVisibilityQueryBuilder($qb);
500
501
        return $qb;
502
    }
503
504
    public function getResourceFromResourceNode(int $resourceNodeId): ?ResourceInterface
505
    {
506
        $qb = $this->createQueryBuilder('resource')
507
            ->select('resource')
508
            ->addSelect('node')
509
            ->addSelect('links')
510
            ->innerJoin('resource.resourceNode', 'node')
511
        //    ->innerJoin('node.creator', 'userCreator')
512
            ->leftJoin('node.resourceLinks', 'links')
513
            ->where('node.id = :id')
514
            ->setParameters([
515
                'id' => $resourceNodeId,
516
            ])
517
        ;
518
519
        return $qb->getQuery()->getOneOrNullResult();
520
    }
521
522
    public function delete(ResourceInterface $resource): void
523
    {
524
        $em = $this->getEntityManager();
525
        $children = $resource->getResourceNode()->getChildren();
526
        foreach ($children as $child) {
527
            foreach ($child->getResourceFiles() as $resourceFile) {
528
                $em->remove($resourceFile);
529
            }
530
            $resourceNode = $this->getResourceFromResourceNode($child->getId());
531
            if (null !== $resourceNode) {
532
                $this->delete($resourceNode);
533
            }
534
        }
535
536
        $em->remove($resource);
537
        $em->flush();
538
    }
539
540
    /**
541
     * Deletes several entities: AbstractResource (Ex: CDocument, CQuiz), ResourceNode,
542
     * ResourceLinks and ResourceFile (including files via Flysystem).
543
     */
544
    public function hardDelete(AbstractResource $resource): void
545
    {
546
        $em = $this->getEntityManager();
547
        $em->remove($resource);
548
        $em->flush();
549
    }
550
551
    public function getResourceFileContent(AbstractResource $resource): string
552
    {
553
        try {
554
            $resourceNode = $resource->getResourceNode();
555
556
            return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode);
0 ignored issues
show
Bug introduced by
The method getResourceNodeFileContent() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

556
            return $this->resourceNodeRepository->/** @scrutinizer ignore-call */ getResourceNodeFileContent($resourceNode);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
557
        } catch (Throwable $throwable) {
558
            throw new FileNotFoundException($resource->getResourceName());
559
        }
560
    }
561
562
    public function getResourceNodeFileContent(ResourceNode $resourceNode): string
563
    {
564
        return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode);
565
    }
566
567
    /**
568
     * @return false|resource
569
     */
570
    public function getResourceNodeFileStream(ResourceNode $resourceNode)
571
    {
572
        return $this->resourceNodeRepository->getResourceNodeFileStream($resourceNode);
573
    }
574
575
    public function getResourceFileDownloadUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
576
    {
577
        $extraParams['mode'] = 'download';
578
579
        return $this->getResourceFileUrl($resource, $extraParams, $referenceType);
580
    }
581
582
    public function getResourceFileUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
583
    {
584
        return $this->getResourceNodeRepository()->getResourceFileUrl(
585
            $resource->getResourceNode(),
586
            $extraParams,
587
            $referenceType
588
        );
589
    }
590
591
    public function updateResourceFileContent(AbstractResource $resource, string $content): bool
592
    {
593
        $resourceNode = $resource->getResourceNode();
594
        if ($resourceNode->hasResourceFile()) {
595
            $resourceNode->setContent($content);
596
            foreach ($resourceNode->getResourceFiles() as $resourceFile) {
597
                $resourceFile->setSize(\strlen($content));
598
            }
599
600
            return true;
601
        }
602
603
        return false;
604
    }
605
606
    public function setResourceName(AbstractResource $resource, $title): void
607
    {
608
        if (!empty($title)) {
609
            $resource->setResourceName($title);
610
            $resourceNode = $resource->getResourceNode();
611
            $resourceNode->setTitle($title);
612
        }
613
    }
614
615
    public function toggleVisibilityPublishedDraft(
616
        AbstractResource $resource,
617
        ?Course $course = null,
618
        ?Session $session = null
619
    ): void {
620
        $firstLink = $resource->getFirstResourceLink();
621
622
        if (ResourceLink::VISIBILITY_PUBLISHED === $firstLink->getVisibility()) {
623
            $this->setVisibilityDraft($resource, $course, $session);
624
625
            return;
626
        }
627
628
        if (ResourceLink::VISIBILITY_DRAFT === $firstLink->getVisibility()) {
629
            $this->setVisibilityPublished($resource, $course, $session);
630
        }
631
    }
632
633
    public function setVisibilityPublished(
634
        AbstractResource $resource,
635
        ?Course $course = null,
636
        ?Session $session = null,
637
    ): void {
638
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PUBLISHED, true, $course, $session);
639
    }
640
641
    public function setVisibilityDraft(
642
        AbstractResource $resource,
643
        ?Course $course = null,
644
        ?Session $session = null,
645
    ): void {
646
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DRAFT, true, $course, $session);
647
    }
648
649
    public function setVisibilityPending(
650
        AbstractResource $resource,
651
        ?Course $course = null,
652
        ?Session $session = null,
653
    ): void {
654
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PENDING, true, $course, $session);
655
    }
656
657
    public function addResourceNode(
658
        ResourceInterface $resource,
659
        User $creator,
660
        ResourceInterface $parentResource,
661
        ?ResourceType $resourceType = null,
662
    ): ResourceNode {
663
        $parentResourceNode = $parentResource->getResourceNode();
664
665
        return $this->createNodeForResource(
666
            $resource,
667
            $creator,
668
            $parentResourceNode,
669
            null,
670
            $resourceType,
671
        );
672
    }
673
674
    /**
675
     * @todo remove this function and merge it with addResourceNode()
676
     */
677
    public function createNodeForResource(
678
        ResourceInterface $resource,
679
        User $creator,
680
        ResourceNode $parentNode,
681
        ?UploadedFile $file = null,
682
        ?ResourceType $resourceType = null,
683
    ): ResourceNode {
684
        $em = $this->getEntityManager();
685
686
        $resourceType = $resourceType ?: $this->getResourceType();
687
        $resourceName = $resource->getResourceName();
688
        $extension = $this->slugify->slugify(pathinfo($resourceName, PATHINFO_EXTENSION));
0 ignored issues
show
Bug introduced by
The method slugify() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

688
        /** @scrutinizer ignore-call */ 
689
        $extension = $this->slugify->slugify(pathinfo($resourceName, PATHINFO_EXTENSION));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
689
690
        if (empty($extension)) {
691
            $slug = $this->slugify->slugify($resourceName);
692
        } else {
693
            $originalExtension = pathinfo($resourceName, PATHINFO_EXTENSION);
694
            $originalBasename = basename($resourceName, $originalExtension);
695
            $slug = \sprintf('%s.%s', $this->slugify->slugify($originalBasename), $originalExtension);
696
        }
697
698
        $resourceNode = new ResourceNode();
699
        $resourceNode
700
            ->setTitle($resourceName)
701
            ->setSlug($slug)
702
            ->setResourceType($resourceType)
703
        ;
704
705
        $creator->addResourceNode($resourceNode);
706
707
        $parentNode?->addChild($resourceNode);
708
709
        $resource->setResourceNode($resourceNode);
710
        $em->persist($resourceNode);
711
        $em->persist($resource);
712
713
        if (null !== $file) {
714
            $this->addFile($resource, $file);
715
        }
716
717
        return $resourceNode;
718
    }
719
720
    /**
721
     * This is only used during installation for the special nodes (admin and AccessUrl).
722
     */
723
    public function createNodeForResourceWithNoParent(ResourceInterface $resource, User $creator): ResourceNode
724
    {
725
        $em = $this->getEntityManager();
726
727
        $resourceType = $this->getResourceType();
728
        $resourceName = $resource->getResourceName();
729
        $slug = $this->slugify->slugify($resourceName);
730
        $resourceNode = new ResourceNode();
731
        $resourceNode
732
            ->setTitle($resourceName)
733
            ->setSlug($slug)
734
            ->setCreator($creator)
735
            ->setResourceType($resourceType)
736
        ;
737
        $resource->setResourceNode($resourceNode);
738
        $em->persist($resourceNode);
739
        $em->persist($resource);
740
741
        return $resourceNode;
742
    }
743
744
    public function getTotalSpaceByCourse(Course $course, ?CGroup $group = null, ?Session $session = null): int
745
    {
746
        $qb = $this->createQueryBuilder('resource');
747
        $qb
748
            ->select('SUM(file.size) as total')
749
            ->innerJoin('resource.resourceNode', 'node')
750
            ->innerJoin('node.resourceLinks', 'l')
751
            ->innerJoin('node.resourceFiles', 'file')
752
            ->where('l.course = :course')
753
            ->andWhere('file IS NOT NULL')
754
            ->setParameters(
755
                [
756
                    'course' => $course,
757
                ]
758
            )
759
        ;
760
761
        if (null === $group) {
762
            $qb->andWhere('l.group IS NULL');
763
        } else {
764
            $qb
765
                ->andWhere('l.group = :group')
766
                ->setParameter('group', $group)
767
            ;
768
        }
769
770
        if (null === $session) {
771
            $qb->andWhere('l.session IS NULL');
772
        } else {
773
            $qb
774
                ->andWhere('l.session = :session')
775
                ->setParameter('session', $session)
776
            ;
777
        }
778
779
        $query = $qb->getQuery();
780
781
        return (int) $query->getSingleScalarResult();
782
    }
783
784
    public function addTitleDecoration(AbstractResource $resource, Course $course, ?Session $session = null): string
785
    {
786
        if (null === $session) {
787
            return '';
788
        }
789
790
        $link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
791
        if (null === $link) {
792
            return '';
793
        }
794
795
        return '<img title="'.$session->getTitle().'" src="/img/icons/22/star.png" />';
796
    }
797
798
    public function isGranted(string $subject, AbstractResource $resource): bool
799
    {
800
        return $this->getAuthorizationChecker()->isGranted($subject, $resource->getResourceNode());
801
    }
802
803
    /**
804
     * Changes the visibility of the children that matches the exact same link.
805
     */
806
    public function copyVisibilityToChildren(ResourceNode $resourceNode, ResourceLink $link): bool
807
    {
808
        $children = $resourceNode->getChildren();
809
810
        if (0 === $children->count()) {
811
            return false;
812
        }
813
814
        $em = $this->getEntityManager();
815
816
        /** @var ResourceNode $child */
817
        foreach ($children as $child) {
818
            if ($child->getChildren()->count() > 0) {
819
                $this->copyVisibilityToChildren($child, $link);
820
            }
821
822
            $links = $child->getResourceLinks();
823
            foreach ($links as $linkItem) {
824
                if ($linkItem->getUser() === $link->getUser()
825
                    && $linkItem->getSession() === $link->getSession()
826
                    && $linkItem->getCourse() === $link->getCourse()
827
                    && $linkItem->getUserGroup() === $link->getUserGroup()
828
                ) {
829
                    $linkItem->setVisibility($link->getVisibility());
830
                    $em->persist($linkItem);
831
                }
832
            }
833
        }
834
835
        $em->flush();
836
837
        return true;
838
    }
839
840
    protected function addSlugQueryBuilder(?string $slug, ?QueryBuilder $qb = null): QueryBuilder
841
    {
842
        $qb = $this->getOrCreateQueryBuilder($qb);
843
        if (null === $slug) {
844
            return $qb;
845
        }
846
847
        $qb
848
            ->andWhere('node.slug = :slug OR node.slug LIKE :slug2')
849
            ->setParameter('slug', $slug) // normal slug = title
850
            ->setParameter('slug2', $slug.'%-%') // slug with a counter  = title-1
851
        ;
852
853
        return $qb;
854
    }
855
856
    protected function addTitleQueryBuilder(?string $title, ?QueryBuilder $qb = null): QueryBuilder
857
    {
858
        $qb = $this->getOrCreateQueryBuilder($qb);
859
        if (null === $title) {
860
            return $qb;
861
        }
862
863
        $qb
864
            ->andWhere('node.title = :title')
865
            ->setParameter('title', $title)
866
        ;
867
868
        return $qb;
869
    }
870
871
    protected function addCreatorQueryBuilder(?User $user, ?QueryBuilder $qb = null): QueryBuilder
872
    {
873
        $qb = $this->getOrCreateQueryBuilder($qb);
874
        if (null === $user) {
875
            return $qb;
876
        }
877
878
        $qb
879
            ->andWhere('node.creator = :creator')
880
            ->setParameter('creator', $user)
881
        ;
882
883
        return $qb;
884
    }
885
886
    private function setLinkVisibility(
887
        AbstractResource $resource,
888
        int $visibility,
889
        bool $recursive = true,
890
        ?Course $course = null,
891
        ?Session $session = null,
892
        ?CGroup $group = null,
893
        ?User $user = null,
894
    ): bool {
895
        $resourceNode = $resource->getResourceNode();
896
897
        if (null === $resourceNode) {
898
            return false;
899
        }
900
901
        $em = $this->getEntityManager();
902
        if ($recursive) {
903
            $children = $resourceNode->getChildren();
904
905
            /** @var ResourceNode $child */
906
            foreach ($children as $child) {
907
                $criteria = [
908
                    'resourceNode' => $child,
909
                ];
910
                $childDocument = $this->findOneBy($criteria);
911
                if ($childDocument) {
912
                    $this->setLinkVisibility($childDocument, $visibility);
913
                }
914
            }
915
        }
916
917
        if ($resource instanceof ResourceShowCourseResourcesInSessionInterface) {
918
            $link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
919
920
            if (!$link) {
921
                $resource->parentResource = $course;
922
                $resource->addCourseLink($course, $session);
923
            }
924
925
            $link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
926
            $links = [$link];
927
        } else {
928
            $links = $resourceNode->getResourceLinks();
929
        }
930
931
        /** @var ResourceLink $link */
932
        foreach ($links as $link) {
933
            $link->setVisibility($visibility);
934
            if (ResourceLink::VISIBILITY_DRAFT === $visibility) {
935
                $editorMask = ResourceNodeVoter::getEditorMask();
936
                $resourceRight = (new ResourceRight())
937
                    ->setMask($editorMask)
938
                    ->setRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER)
939
                    ->setResourceLink($link)
940
                ;
941
                $link->addResourceRight($resourceRight);
942
            } else {
943
                $link->setResourceRights(new ArrayCollection());
944
            }
945
            $em->persist($link);
946
        }
947
948
        $em->flush();
949
950
        return true;
951
    }
952
953
    public function findByTitleAndParentResourceNode(string $title, int $parentResourceNodeId): ?AbstractResource
954
    {
955
        return $this->createQueryBuilder('d')
956
            ->innerJoin('d.resourceNode', 'node')
957
            ->andWhere('d.title = :title')
958
            ->andWhere('node.parent = :parentResourceNodeId')
959
            ->setParameter('title', $title)
960
            ->setParameter('parentResourceNodeId', $parentResourceNodeId)
961
            ->setMaxResults(1)
962
            ->getQuery()
963
            ->getOneOrNullResult()
964
        ;
965
    }
966
967
    public function findResourceByTitleInCourse(
968
        string $title,
969
        Course $course,
970
        ?Session $session = null,
971
        ?CGroup $group = null
972
    ): ?ResourceInterface {
973
        $qb = $this->getResourcesByCourse($course, $session, $group);
974
975
        $this->addTitleQueryBuilder($title, $qb);
976
977
        $qb->setMaxResults(1);
978
979
        return $qb->getQuery()->getOneOrNullResult();
980
    }
981
}
982