Passed
Pull Request — master (#5625)
by Angel Fernando Quiroz
08:26
created

ResourceNode::getResourceFile()   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
nc 1
nop 0
dl 0
loc 3
rs 10
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\Entity;
8
9
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
10
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
11
use ApiPlatform\Metadata\ApiFilter;
12
use ApiPlatform\Metadata\ApiResource;
13
use ApiPlatform\Metadata\Delete;
14
use ApiPlatform\Metadata\Get;
15
use ApiPlatform\Metadata\GetCollection;
16
use ApiPlatform\Metadata\Patch;
17
use ApiPlatform\Metadata\Put;
18
use ApiPlatform\Serializer\Filter\PropertyFilter;
19
use Chamilo\CoreBundle\Entity\Listener\ResourceNodeListener;
20
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
21
use Chamilo\CoreBundle\Traits\TimestampableAgoTrait;
22
use Chamilo\CoreBundle\Traits\TimestampableTypedEntity;
23
use Chamilo\CourseBundle\Entity\CGroup;
24
use Chamilo\CourseBundle\Entity\CShortcut;
25
use DateTime;
26
use Doctrine\Common\Collections\ArrayCollection;
27
use Doctrine\Common\Collections\Collection;
28
use Doctrine\Common\Collections\Criteria;
29
use Doctrine\ORM\Mapping as ORM;
30
use Gedmo\Mapping\Annotation as Gedmo;
31
use InvalidArgumentException;
32
use Stringable;
33
use Symfony\Component\Routing\RouterInterface;
34
use Symfony\Component\Serializer\Annotation\Groups;
35
use Symfony\Component\Serializer\Annotation\MaxDepth;
36
use Symfony\Component\Uid\Uuid;
37
use Symfony\Component\Uid\UuidV4;
38
use Symfony\Component\Validator\Constraints as Assert;
39
40
// *     attributes={"security"="is_granted('ROLE_ADMIN')"},
41
42
/**
43
 * Base entity for all resources.
44
 */
45
#[ORM\Table(name: 'resource_node')]
46
#[ORM\Entity(repositoryClass: ResourceNodeRepository::class)]
47
#[ORM\HasLifecycleCallbacks]
48
#[ORM\EntityListeners([ResourceNodeListener::class])]
49
#[Gedmo\Tree(type: 'materializedPath')]
50
#[ApiResource(
51
    operations: [
52
        new Get(),
53
        new Put(),
54
        new Patch(),
55
        new Delete(),
56
        new GetCollection(),
57
    ],
58
    normalizationContext: [
59
        'groups' => [
60
            'resource_node:read',
61
            'document:read',
62
        ],
63
    ],
64
    denormalizationContext: [
65
        'groups' => [
66
            'resource_node:write',
67
            'document:write',
68
        ],
69
    ]
70
)]
71
#[ApiFilter(filterClass: OrderFilter::class, properties: ['id', 'title', 'resourceFile', 'createdAt', 'updatedAt'])]
72
#[ApiFilter(filterClass: PropertyFilter::class)]
73
#[ApiFilter(filterClass: SearchFilter::class, properties: ['title' => 'partial'])]
74
class ResourceNode implements Stringable
75
{
76
    use TimestampableAgoTrait;
77
    use TimestampableTypedEntity;
78
79
    public const PATH_SEPARATOR = '/';
80
81
    #[Groups(['resource_node:read', 'document:read', 'ctool:read', 'user_json:read', 'course:read'])]
82
    #[ORM\Id]
83
    #[ORM\Column(type: 'integer')]
84
    #[ORM\GeneratedValue(strategy: 'AUTO')]
85
    protected ?int $id = null;
86
87
    #[Groups(['resource_node:read', 'resource_node:write', 'document:read', 'document:write'])]
88
    #[Assert\NotBlank]
89
    #[Gedmo\TreePathSource]
90
    #[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
91
    protected string $title;
92
93
    #[Assert\NotBlank]
94
    #[Gedmo\Slug(fields: ['title'])]
95
    #[ORM\Column(name: 'slug', type: 'string', length: 255, nullable: false)]
96
    protected string $slug;
97
98
    #[Groups(['resource_node:read'])]
99
    #[Assert\NotNull]
100
    #[ORM\ManyToOne(targetEntity: ResourceType::class, inversedBy: 'resourceNodes')]
101
    #[ORM\JoinColumn(name: 'resource_type_id', referencedColumnName: 'id', nullable: false)]
102
    protected ResourceType $resourceType;
103
104
    #[ORM\ManyToOne(targetEntity: ResourceFormat::class, inversedBy: 'resourceNodes')]
105
    #[ORM\JoinColumn(name: 'resource_format_id', referencedColumnName: 'id')]
106
    protected ?ResourceFormat $resourceFormat = null;
107
108
    /**
109
     * @var Collection<int, ResourceLink>
110
     */
111
    #[Groups(['ctool:read', 'c_tool_intro:read'])]
112
    #[ORM\OneToMany(mappedBy: 'resourceNode', targetEntity: ResourceLink::class, cascade: ['persist', 'remove'])]
113
    protected Collection $resourceLinks;
114
115
    //#[Groups(['resource_node:read', 'resource_node:write', 'document:read', 'document:write', 'message:read'])]
116
    //#[ORM\OneToOne(inversedBy: 'resourceNode', targetEntity: ResourceFile::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
117
    //#[ORM\JoinColumn(name: 'resource_file_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
118
    #protected ?ResourceFile $resourceFile = null;
119
120
    #[Assert\NotNull]
121
    #[Groups(['resource_node:read', 'resource_node:write', 'document:write'])]
122
    #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'], inversedBy: 'resourceNodes')]
123
    #[ORM\JoinColumn(name: 'creator_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
124
    protected ?User $creator;
125
126
    #[Groups(['resource_node:read'])]
127
    #[MaxDepth(1)]
128
    #[ORM\JoinColumn(name: 'parent_id', onDelete: 'CASCADE')]
129
    #[ORM\ManyToOne(targetEntity: self::class, cascade: ['persist'], inversedBy: 'children')]
130
    #[Gedmo\TreeParent]
131
    protected ?ResourceNode $parent = null;
132
133
    /**
134
     * @var Collection<int, ResourceNode>
135
     */
136
    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])]
137
    #[ORM\OrderBy(['id' => 'ASC'])]
138
    protected Collection $children;
139
140
    #[Gedmo\TreeLevel]
141
    #[ORM\Column(name: 'level', type: 'integer', nullable: true)]
142
    protected ?int $level = null;
143
144
    #[Groups(['resource_node:read', 'document:read'])]
145
    #[Gedmo\TreePath(separator: '/', appendId: true)]
146
    #[ORM\Column(name: 'path', type: 'text', nullable: true)]
147
    protected ?string $path = null;
148
149
    /**
150
     * Shortcut to access Course resource from ResourceNode.
151
     * Groups({"resource_node:read", "course:read"}).
152
     *
153
     * ORM\OneToOne(targetEntity="Chamilo\CoreBundle\Entity\Illustration", mappedBy="resourceNode")
154
     */
155
    // protected $illustration;
156
157
    /**
158
     * @var Collection<int, ResourceComment>
159
     */
160
    #[ORM\OneToMany(mappedBy: 'resourceNode', targetEntity: ResourceComment::class, cascade: ['persist', 'remove'])]
161
    protected Collection $comments;
162
163
    #[Groups(['resource_node:read', 'document:read'])]
164
    #[Gedmo\Timestampable(on: 'create')]
165
    #[ORM\Column(type: 'datetime')]
166
    protected DateTime $createdAt;
167
168
    #[Groups(['resource_node:read', 'document:read'])]
169
    #[Gedmo\Timestampable(on: 'update')]
170
    #[ORM\Column(type: 'datetime')]
171
    protected DateTime $updatedAt;
172
173
    #[Groups(['resource_node:read', 'document:read'])]
174
    protected bool $fileEditableText;
175
176
    #[Groups(['resource_node:read', 'document:read'])]
177
    #[ORM\Column(type: 'boolean')]
178
    protected bool $public;
179
180
    protected ?string $content = null;
181
182
    #[ORM\OneToOne(mappedBy: 'shortCutNode', targetEntity: CShortcut::class, cascade: ['persist', 'remove'])]
183
    protected ?CShortcut $shortCut = null;
184
185
    #[Groups(['resource_node:read', 'document:read'])]
186
    #[ORM\Column(type: 'uuid', unique: true)]
187
    protected ?UuidV4 $uuid = null;
188
189
    /**
190
     * ResourceFile available file for this node.
191
     *
192
     * @var Collection<int, ResourceFile>
193
     */
194
    #[ORM\OneToMany(mappedBy: 'resourceNode', targetEntity: ResourceFile::class, cascade: ['persist'])]
195
    private Collection $resourceFiles;
196
197
    public function __construct()
198
    {
199
        $this->public = false;
200
        $this->uuid = Uuid::v4();
201
        $this->children = new ArrayCollection();
202
        $this->resourceLinks = new ArrayCollection();
203
        $this->comments = new ArrayCollection();
204
        $this->createdAt = new DateTime();
205
        $this->fileEditableText = false;
206
        $this->resourceFiles = new ArrayCollection();
207
    }
208
209
    public function __toString(): string
210
    {
211
        return $this->getPathForDisplay();
212
    }
213
214
    /**
215
     * Returns the path cleaned from its ids.
216
     * Eg.: "Root/subdir/file.txt".
217
     */
218
    public function getPathForDisplay(): string
219
    {
220
        return $this->path;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->path 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...
221
        // return $this->convertPathForDisplay($this->path);
222
    }
223
224
    public function getUuid(): ?UuidV4
225
    {
226
        return $this->uuid;
227
    }
228
229
    public function hasCreator(): bool
230
    {
231
        return null !== $this->creator;
232
    }
233
234
    public function getCreator(): ?User
235
    {
236
        return $this->creator;
237
    }
238
239
    public function setCreator(?User $creator): self
240
    {
241
        $this->creator = $creator;
242
243
        return $this;
244
    }
245
246
    /**
247
     * Returns the children resource instances.
248
     *
249
     * @return Collection<int, ResourceNode>
250
     */
251
    public function getChildren(): Collection
252
    {
253
        return $this->children;
254
    }
255
256
    public function addChild(self $resourceNode): static
257
    {
258
        if (!$this->children->contains($resourceNode)) {
259
            $this->children->add($resourceNode);
260
261
            $resourceNode->setParent($this);
262
        }
263
264
        return $this;
265
    }
266
267
    /**
268
     * Returns the parent resource.
269
     */
270
    public function getParent(): ?self
271
    {
272
        return $this->parent;
273
    }
274
275
    /**
276
     * Sets the parent resource.
277
     */
278
    public function setParent(?self $parent = null): self
279
    {
280
        $this->parent = $parent;
281
282
        return $this;
283
    }
284
285
    /**
286
     * Return the lvl value of the resource in the tree.
287
     */
288
    public function getLevel(): int
289
    {
290
        return $this->level;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->level could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
291
    }
292
293
    /**
294
     * Returns the "raw" path of the resource
295
     * (the path merge names and ids of all items).
296
     * Eg.: "Root-1/subdir-2/file.txt-3/".
297
     */
298
    public function getPath(): ?string
299
    {
300
        return $this->path;
301
    }
302
303
    /**
304
     * @return Collection|ResourceComment[]
305
     */
306
    public function getComments(): array|Collection
307
    {
308
        return $this->comments;
309
    }
310
311
    public function addComment(ResourceComment $comment): self
312
    {
313
        $comment->setResourceNode($this);
314
        $this->comments->add($comment);
315
316
        return $this;
317
    }
318
319
    public function getPathForDisplayToArray(?int $baseRoot = null): array
320
    {
321
        $parts = explode(self::PATH_SEPARATOR, $this->path);
322
        $list = [];
323
        foreach ($parts as $part) {
324
            $parts = explode('-', $part);
325
            if (empty($parts[1])) {
326
                continue;
327
            }
328
            $value = $parts[0];
329
            $id = $parts[1];
330
            if (!empty($baseRoot) && $id < $baseRoot) {
331
                continue;
332
            }
333
            $list[$id] = $value;
334
        }
335
336
        return $list;
337
    }
338
339
    public function getPathForDisplayRemoveBase(string $base): string
340
    {
341
        $path = str_replace($base, '', $this->path);
342
343
        return $this->convertPathForDisplay($path);
344
    }
345
346
    /**
347
     * Convert a path for display: remove ids.
348
     */
349
    public function convertPathForDisplay(string $path): string
350
    {
351
        /*$pathForDisplay = preg_replace(
352
              '/-\d+'.self::PATH_SEPARATOR.'/',
353
              ' / ',
354
              $path
355
          );
356
          if ($pathForDisplay !== null && strlen($pathForDisplay) > 0) {
357
              $pathForDisplay = substr_replace($pathForDisplay, '', -3);
358
          }
359
          */
360
        $pathForDisplay = preg_replace('/-\\d+\\'.self::PATH_SEPARATOR.'/', '/', $path);
361
        if (null !== $pathForDisplay && '' !== $pathForDisplay) {
362
            $pathForDisplay = substr_replace($pathForDisplay, '', -1);
363
        }
364
365
        return $pathForDisplay;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $pathForDisplay could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
366
    }
367
368
    public function getSlug(): string
369
    {
370
        return $this->slug;
371
    }
372
373
    public function setSlug(string $slug): self
374
    {
375
        if (str_contains(self::PATH_SEPARATOR, $slug)) {
376
            $message = 'Invalid character "'.self::PATH_SEPARATOR.'" in resource name';
377
378
            throw new InvalidArgumentException($message);
379
        }
380
        $this->slug = $slug;
381
382
        return $this;
383
    }
384
385
    public function getTitle(): string
386
    {
387
        return $this->title;
388
    }
389
390
    public function setTitle(string $title): self
391
    {
392
        $title = str_replace('/', '-', $title);
393
        $this->title = $title;
394
395
        return $this;
396
    }
397
398
    public function getResourceFormat(): ?ResourceFormat
399
    {
400
        return $this->resourceFormat;
401
    }
402
403
    public function setResourceFormat(?ResourceFormat $resourceFormat): self
404
    {
405
        $this->resourceFormat = $resourceFormat;
406
407
        return $this;
408
    }
409
410
    /**
411
     * @return Collection<int, ResourceLink>
412
     */
413
    public function getResourceLinks(): Collection
414
    {
415
        return $this->resourceLinks;
416
    }
417
418
    public function getResourceLinkByContext(
419
        ?Course $course = null,
420
        ?Session $session = null,
421
        ?CGroup $group = null,
422
        ?Usergroup $usergroup = null,
423
        ?User $user = null,
424
    ): ?ResourceLink {
425
        $criteria = Criteria::create();
426
        $criteria->where(
427
            Criteria::expr()->eq('resourceTypeGroup', $this->resourceType->getId())
428
        );
429
430
        if ($course) {
431
            $criteria->andWhere(
432
                Criteria::expr()->eq('course', $course)
433
            );
434
        }
435
436
        if ($session) {
437
            $criteria->andWhere(
438
                Criteria::expr()->eq('session', $session)
439
            );
440
        }
441
442
        if ($usergroup) {
443
            $criteria->andWhere(
444
                Criteria::expr()->eq('userGroup', $usergroup)
445
            );
446
        }
447
448
        if ($group) {
449
            $criteria->andWhere(
450
                Criteria::expr()->eq('group', $group)
451
            );
452
        }
453
454
        if ($user) {
455
            $criteria->andWhere(
456
                Criteria::expr()->eq('user', $user)
457
            );
458
        }
459
460
        $first = $this
461
            ->resourceLinks
462
            ->matching($criteria)
463
            ->first()
464
        ;
465
466
        return $first ?: null;
467
    }
468
469
    public function setResourceLinks(Collection $resourceLinks): self
470
    {
471
        $this->resourceLinks = $resourceLinks;
472
473
        return $this;
474
    }
475
476
    public function addResourceLink(ResourceLink $link): self
477
    {
478
        $link->setResourceNode($this);
479
480
        $this->resourceLinks->add($link);
481
482
        return $this;
483
    }
484
485
    public function hasEditableTextContent(): bool
486
    {
487
        if ($resourceFile = $this->resourceFiles->first()) {
488
            $mimeType = $resourceFile->getMimeType();
489
490
            if (str_contains($mimeType, 'text')) {
491
                return true;
492
            }
493
        }
494
495
        return false;
496
    }
497
498
    public function getIcon(?string $additionalClass = null): string
499
    {
500
        $class = 'fa fa-folder';
501
        if ($this->hasResourceFile()) {
502
            $class = 'far fa-file';
503
            if ($this->isResourceFileAnImage()) {
504
                $class = 'far fa-file-image';
505
            }
506
            if ($this->isResourceFileAVideo()) {
507
                $class = 'far fa-file-video';
508
            }
509
        }
510
511
        if ($additionalClass) {
512
            $class .= " $additionalClass";
513
        }
514
515
        return '<i class="'.$class.'"></i>';
516
    }
517
518
    public function isResourceFileAnImage(): bool
519
    {
520
        if ($resourceFile = $this->resourceFiles->first()) {
521
            $mimeType = $resourceFile->getMimeType();
522
            if (str_contains($mimeType, 'image')) {
523
                return true;
524
            }
525
        }
526
527
        return false;
528
    }
529
530
    public function isResourceFileAVideo(): bool
531
    {
532
        if ($resourceFile = $this->resourceFiles->first()) {
533
            $mimeType = $resourceFile->getMimeType();
534
            if (str_contains($mimeType, 'video')) {
535
                return true;
536
            }
537
        }
538
539
        return false;
540
    }
541
542
    public function getThumbnail(RouterInterface $router): string
543
    {
544
        if ($this->isResourceFileAnImage()) {
545
            $params = [
546
                'id' => $this->getId(),
547
                'tool' => $this->getResourceType()->getTool(),
548
                'type' => $this->getResourceType()->getTitle(),
549
                'filter' => 'editor_thumbnail',
550
            ];
551
            $url = $router->generate('chamilo_core_resource_view', $params);
552
553
            return sprintf("<img src='%s'/>", $url);
554
        }
555
556
        return $this->getIcon('fa-3x');
557
    }
558
559
    /**
560
     * Returns the resource id.
561
     */
562
    public function getId(): ?int
563
    {
564
        return $this->id;
565
    }
566
567
    public function getResourceType(): ResourceType
568
    {
569
        return $this->resourceType;
570
    }
571
572
    public function setResourceType(ResourceType $resourceType): self
573
    {
574
        $this->resourceType = $resourceType;
575
576
        return $this;
577
    }
578
579
    public function getContent(): ?string
580
    {
581
        return $this->content;
582
    }
583
584
    public function setContent(string $content): self
585
    {
586
        $this->content = $content;
587
588
        return $this;
589
    }
590
591
    public function getShortCut(): ?CShortcut
592
    {
593
        return $this->shortCut;
594
    }
595
596
    public function setShortCut(?CShortcut $shortCut): self
597
    {
598
        $this->shortCut = $shortCut;
599
600
        return $this;
601
    }
602
603
    public function isPublic(): bool
604
    {
605
        return $this->public;
606
    }
607
608
    public function setPublic(bool $public): self
609
    {
610
        $this->public = $public;
611
612
        return $this;
613
    }
614
615
    public function hasResourceFile(): bool
616
    {
617
        return $this->resourceFiles->count() > 0;
618
    }
619
620
    /**
621
     * @return Collection<int, ResourceFile>
622
     */
623
    public function getResourceFiles(): Collection
624
    {
625
        return $this->resourceFiles;
626
    }
627
628
    public function addResourceFile(ResourceFile $resourceFile): static
629
    {
630
        if (!$this->resourceFiles->contains($resourceFile)) {
631
            $this->resourceFiles->add($resourceFile);
632
            $resourceFile->setResourceNode($this);
633
        }
634
635
        return $this;
636
    }
637
638
    public function removeResourceFile(ResourceFile $resourceFile): static
639
    {
640
        if ($this->resourceFiles->removeElement($resourceFile)) {
641
            // set the owning side to null (unless already changed)
642
            if ($resourceFile->getResourceNode() === $this) {
643
                $resourceFile->setResourceNode(null);
644
            }
645
        }
646
647
        return $this;
648
    }
649
}
650