Passed
Push — ofaj2 ( 70178f )
by Yannick
11:43
created

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