Passed
Push — master ( 0c16ea...e96de5 )
by Julito
08:43
created

ResourceNode::getThumbnail()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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