Passed
Push — dependabot/github_actions/code... ( 154cc6...3e3012 )
by
unknown
26:36 queued 15:59
created

findByTitleAndParentResourceNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

537
            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...
538
        } catch (Throwable $throwable) {
539
            throw new FileNotFoundException($resource->getResourceName());
540
        }
541
    }
542
543
    public function getResourceNodeFileContent(ResourceNode $resourceNode): string
544
    {
545
        return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode);
546
    }
547
548
    /**
549
     * @return false|resource
550
     */
551
    public function getResourceNodeFileStream(ResourceNode $resourceNode)
552
    {
553
        return $this->resourceNodeRepository->getResourceNodeFileStream($resourceNode);
554
    }
555
556
    public function getResourceFileDownloadUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
557
    {
558
        $extraParams['mode'] = 'download';
559
560
        return $this->getResourceFileUrl($resource, $extraParams, $referenceType);
561
    }
562
563
    public function getResourceFileUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
564
    {
565
        return $this->getResourceNodeRepository()->getResourceFileUrl(
566
            $resource->getResourceNode(),
567
            $extraParams,
568
            $referenceType
569
        );
570
    }
571
572
    public function updateResourceFileContent(AbstractResource $resource, string $content): bool
573
    {
574
        $resourceNode = $resource->getResourceNode();
575
        if ($resourceNode->hasResourceFile()) {
576
            $resourceNode->setContent($content);
577
            $resourceNode->getResourceFile()->setSize(\strlen($content));
578
579
            return true;
580
        }
581
582
        return false;
583
    }
584
585
    public function setResourceName(AbstractResource $resource, $title): void
586
    {
587
        if (!empty($title)) {
588
            $resource->setResourceName($title);
589
            $resourceNode = $resource->getResourceNode();
590
            $resourceNode->setTitle($title);
591
        }
592
593
        // if ($resourceNode->hasResourceFile()) {
594
        // $resourceNode->getResourceFile()->getFile()->
595
        // $resourceNode->getResourceFile()->setTitle($title);
596
        // $resourceFile->setTitle($title);
597
598
        /*$fileName = $this->getResourceNodeRepository()->getFilename($resourceFile);
599
        error_log('$fileName');
600
        error_log($fileName);
601
        error_log($title);
602
        $this->getResourceNodeRepository()->getFileSystem()->rename($fileName, $title);
603
        $resourceFile->setTitle($title);
604
        $resourceFile->setOriginalName($title);*/
605
        // }
606
    }
607
608
    /**
609
     * Change all links visibility to DELETED.
610
     */
611
    public function softDelete(AbstractResource $resource): void
612
    {
613
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DELETED);
614
    }
615
616
    public function toggleVisibilityPublishedDraft(AbstractResource $resource): void
617
    {
618
        $firstLink = $resource->getFirstResourceLink();
619
620
        if (ResourceLink::VISIBILITY_PUBLISHED === $firstLink->getVisibility()) {
621
            $this->setVisibilityDraft($resource);
622
623
            return;
624
        }
625
626
        if (ResourceLink::VISIBILITY_DRAFT === $firstLink->getVisibility()) {
627
            $this->setVisibilityPublished($resource);
628
        }
629
    }
630
631
    public function setVisibilityPublished(AbstractResource $resource): void
632
    {
633
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PUBLISHED);
634
    }
635
636
    public function setVisibilityDraft(AbstractResource $resource): void
637
    {
638
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DRAFT);
639
    }
640
641
    public function setVisibilityDeleted(AbstractResource $resource): void
642
    {
643
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DELETED);
644
    }
645
646
    public function setVisibilityPending(AbstractResource $resource): void
647
    {
648
        $this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PENDING);
649
    }
650
651
    public function addResourceNode(ResourceInterface $resource, User $creator, ResourceInterface $parentResource): ResourceNode
652
    {
653
        $parentResourceNode = $parentResource->getResourceNode();
654
655
        return $this->createNodeForResource($resource, $creator, $parentResourceNode);
656
    }
657
658
    /**
659
     * @todo remove this function and merge it with addResourceNode()
660
     */
661
    public function createNodeForResource(ResourceInterface $resource, User $creator, ResourceNode $parentNode, ?UploadedFile $file = null): ResourceNode
662
    {
663
        $em = $this->getEntityManager();
664
665
        $resourceType = $this->getResourceType();
666
        $resourceName = $resource->getResourceName();
667
        $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

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