Completed
Push — master ( 21ff8e...a4d357 )
by Chauncey
16:06
created

Attachment::linkLabel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Attachment\Object;
4
5
use Exception;
6
use ReflectionClass;
7
use RuntimeException;
8
use InvalidArgumentException;
9
10
// From PSR-7
11
use Psr\Http\Message\UriInterface;
12
13
// From Pimple
14
use Pimple\Container;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Charcoal\Attachment\Object\Container.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
15
16
// From 'charcoal-core'
17
use Charcoal\Loader\CollectionLoader;
18
19
// From 'charcoal-object'
20
use Charcoal\Object\Content;
21
22
// From 'charcoal-translator'
23
use Charcoal\Translator\Translation;
24
25
// From 'locomotivemtl/charcoal-attachments'
26
use Charcoal\Attachment\Interfaces\AttachableInterface;
27
use Charcoal\Attachment\Interfaces\AttachmentContainerInterface;
28
29
use Charcoal\Attachment\Object\File;
30
use Charcoal\Attachment\Object\Image;
31
use Charcoal\Attachment\Object\Text;
32
use Charcoal\Attachment\Object\Embed;
33
use Charcoal\Attachment\Object\Video;
34
use Charcoal\Attachment\Object\Gallery;
35
use Charcoal\Attachment\Object\Accordion;
36
use Charcoal\Attachment\Object\Link;
37
use Charcoal\Attachment\Object\Container as AttachmentContainer;
38
use Charcoal\Attachment\Object\Join;
39
40
/**
41
 *
42
 */
43
class Attachment extends Content implements AttachableInterface
0 ignored issues
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: hasProperty, p, properties, property
Loading history...
44
{
45
    /**
46
     * Default attachment types
47
     */
48
    const FILE_TYPE      = File::class;
49
    const LINK_TYPE      = Link::class;
50
    const IMAGE_TYPE     = Image::class;
51
    const EMBED_TYPE     = Embed::class;
52
    const VIDEO_TYPE     = Video::class;
53
    const TEXT_TYPE      = Text::class;
54
    const GALLERY_TYPE   = Gallery::class;
55
    const ACCORDION_TYPE = Accordion::class;
56
    const CONTAINER_TYPE = AttachmentContainer::class;
57
58
    /**
59
     * The attachment type.
60
     *
61
     * @var string
62
     */
63
    protected $type;
64
65
    /**
66
     * The attachment heading template.
67
     *
68
     * @var Translation|string|null
69
     */
70
    protected $heading;
71
72
    /**
73
     * The attachment preview template.
74
     *
75
     * @var Translation|string|null
76
     */
77
    protected $preview;
78
79
    /**
80
     * Whether to show the title on the front-end.
81
     *
82
     * @var boolean
83
     */
84
    protected $showTitle = true;
85
86
    /**
87
     * @var string|string[]
88
     */
89
    protected $categories;
90
91
    /**
92
     * Generic information about the attachment.
93
     *
94
     * @var Translation|string|null $title       The title of the attachment.
95
     * @var Translation|string|null $subtitle    The subtitle of the attachment.
96
     * @var Translation|string|null $description The content of the attachment.
97
     * @var Translation|string|null $keywords    Keywords finding the attachment.
98
     */
99
    protected $title;
100
    protected $subtitle;
101
    protected $description;
102
    protected $keywords;
103
104
    /**
105
     * File related attachments.
106
     *
107
     * @var string  $file      The path of an attached file.
108
     * @var string  $fileLabel The label for the attached file.
109
     * @var integer $fileSize  The size of the attached file in bytes.
110
     * @var string  $fileType  The content type of the attached file.
111
     */
112
    protected $file;
113
    protected $fileLabel;
114
    protected $fileSize;
115
    protected $fileType;
116
117
    /**
118
     * Link related attachments.
119
     *
120
     * @var string $link      The URL related to the attachment.
121
     * @var string $linkLabel The label for the attached link.
122
     */
123
    protected $link;
124
    protected $linkLabel;
125
126
    /**
127
     * Path to a thumbnail of the attached file.
128
     *
129
     * Auto-generated thumbnail if the attached file is an image.
130
     *
131
     * @var Translation|string|null
132
     */
133
    protected $thumbnail;
134
135
    /**
136
     * Embedded content.
137
     *
138
     * @var Translation|string|null
139
     */
140
    protected $embed;
141
142
    /**
143
     * The attachment's position amongst other attachments.
144
     *
145
     * @var integer
146
     */
147
    protected $position;
148
149
    /**
150
     * The base URI.
151
     *
152
     * @var UriInterface|null
153
     */
154
    private $baseUrl;
155
156
    /**
157
     * Whether the attachment acts like a presenter (TRUE) or data model (FALSE).
158
     *
159
     * @var boolean
160
     */
161
    private $presentable = false;
162
163
    /**
164
     * The attachment's parent container instance.
165
     *
166
     * @var AttachmentContainerInterface|null
167
     */
168
    protected $containerObj;
169
170
    /**
171
     * A store of resolved attachment types.
172
     *
173
     * @var array
174
     */
175
    protected static $resolvedType = [];
176
177
    /**
178
     * Store the collection loader for the current class.
179
     *
180
     * @var CollectionLoader
181
     */
182
    private $collectionLoader;
183
184
    /**
185
     * Return a new section object.
186
     *
187
     * @param array $data Dependencies.
188
     */
189
    public function __construct(array $data = null)
190
    {
191
        parent::__construct($data);
192
193
        if (is_callable([ $this, 'defaultData' ])) {
194
            $defaultData = $this->defaultData();
195
            if ($defaultData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $defaultData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
196
                $this->setData($defaultData);
197
            }
198
        }
199
    }
200
201
    /**
202
     * Inject dependencies from a DI Container.
203
     *
204
     * @param  Container $container A dependencies container instance.
205
     * @return void
206
     */
207
    protected function setDependencies(Container $container)
208
    {
209
        parent::setDependencies($container);
210
211
        $this->setBaseUrl($container['base-url']);
212
        $this->setCollectionLoader($container['model/collection/loader']);
213
    }
214
215
    /**
216
     * Determine if the model is for presentation or editing.
217
     *
218
     * @param  boolean $presenter The presenter flag.
219
     * @return boolean Returns TRUE if model is used for presentation; FALSE for editing.
220
     */
221
    public function isPresentable($presenter = null)
222
    {
223
        if (is_bool($presenter)) {
224
            $this->presentable = $presenter;
225
        }
226
227
        return $this->presentable;
228
    }
229
230
    /**
231
     * Retrieve the attachment's container ID (if any).
232
     *
233
     * Useful when templating a container of nested attachments.
234
     *
235
     * @return mixed|null
236
     */
237
    public function containerId()
238
    {
239
        $container = $this->containerObj();
240
        if ($container) {
241
            return $container->id();
242
        }
243
244
        return null;
245
    }
246
247
    /**
248
     * Determine if the attachment belongs to a container.
249
     *
250
     * @return boolean
251
     */
252
    public function hasContainerObj()
253
    {
254
        return boolval($this->containerObj);
255
    }
256
257
    /**
258
     * Retrieve the attachment's container instance.
259
     *
260
     * @return AttachmentContainerInterface|null
261
     */
262
    public function containerObj()
263
    {
264
        return $this->containerObj;
265
    }
266
267
    /**
268
     * Set the attachment's container instance.
269
     *
270
     * @param  AttachmentContainerInterface|null $obj The container object or NULL.
271
     * @throws InvalidArgumentException If the given object is invalid.
272
     * @return Attachment
273
     */
274
    public function setContainerObj($obj)
275
    {
276
        if ($obj === null) {
277
            $this->containerObj = null;
278
279
            return $this;
280
        }
281
282 View Code Duplication
        if (!$obj instanceof AttachmentContainerInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
            throw new InvalidArgumentException(sprintf(
284
                'Container object must be an instance of %s; received %s',
285
                AttachmentContainerInterface::class,
286
                (is_object($obj) ? get_class($obj) : gettype($obj))
287
            ));
288
        }
289
290 View Code Duplication
        if (!$obj->id()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
            throw new InvalidArgumentException(sprintf(
292
                'Container object must have an ID.',
293
                (is_object($obj) ? get_class($obj) : gettype($obj))
294
            ));
295
        }
296
297
        $this->containerObj = $obj;
298
299
        return $this;
300
    }
301
302
    /**
303
     * Retrieve the attachment type.
304
     *
305
     * @return string
306
     */
307
    public function type()
308
    {
309
        if (!$this->type) {
310
            $this->type = $this->objType();
311
        }
312
313
        return $this->type;
314
    }
315
316
    /**
317
     * Set the attachment type.
318
     *
319
     * @param  string $type The attachment type.
320
     * @throws InvalidArgumentException If provided argument is not of type 'string'.
321
     * @return string
322
     */
323
    public function setType($type)
324
    {
325
        if (!is_string($type)) {
326
            throw new InvalidArgumentException('Attachment type must be a string.');
327
        }
328
329
        $this->type = $type;
330
331
        return $this;
332
    }
333
334
    /**
335
     * Retrieve the unqualified class name.
336
     *
337
     * @return string Returns the short name of the model's class, the part without the namespace.
338
     */
339
    public function microType()
340
    {
341
        $classname = get_called_class();
342
343
        if (!isset(static::$resolvedType[$classname])) {
344
            $reflect = new ReflectionClass($this);
345
346
            static::$resolvedType[$classname] = strtolower($reflect->getShortName());
347
        }
348
349
        return static::$resolvedType[$classname];
350
    }
351
352
    /**
353
     * Retrieve the image attachment type.
354
     *
355
     * @return string
356
     */
357
    public function imageType()
358
    {
359
        return self::IMAGE_TYPE;
360
    }
361
362
    /**
363
     * Retrieve the attachment's heading template.
364
     *
365
     * @return Translation|string|null
366
     */
367
    public function heading()
368
    {
369
        $heading = $this->render((string)$this->heading);
370
371
        if (!$heading) {
372
            $heading = $this->translator()->translation('{{ objType }} #{{ id }}', [
373
                '{{ objType }}' => ucfirst($this->microType()),
374
                '{{ id }}'      => $this->id()
375
            ]);
376
        }
377
378
        return $heading;
379
    }
380
381
    /**
382
     * Retrieve the attachment's heading as a raw value.
383
     *
384
     * @return Translation|string|null
385
     */
386
    public function rawHeading()
387
    {
388
        return $this->heading;
389
    }
390
391
    /**
392
     * Set the attachment's heading template.
393
     *
394
     * @param  string $template The attachment heading.
395
     * @return Attachment Chainable
396
     */
397
    public function setHeading($template)
398
    {
399
        $this->heading = $this->translator()->translation($template);
400
401
        return $this;
402
    }
403
404
    /**
405
     * Retrieve the attachment's preview template.
406
     *
407
     * @return Translation|string|null
408
     */
409
    public function preview()
410
    {
411
        if ($this->preview) {
412
            return $this->render((string)$this->preview);
413
        }
414
415
        return '';
416
    }
417
418
    /**
419
     * Retrieve the attachment's preview as a raw value.
420
     *
421
     * @return Translation|string|null
422
     */
423
    public function rawPreview()
424
    {
425
        return $this->preview;
426
    }
427
428
    /**
429
     * Set the attachment's preview template.
430
     *
431
     * @param  string $template The attachment preview.
432
     * @return Attachment Chainable
433
     */
434
    public function setPreview($template)
435
    {
436
        $this->preview = $this->translator()->translation($template);
437
438
        return $this;
439
    }
440
441
    /**
442
     * Determine if the attachment type is an image.
443
     *
444
     * @return boolean
445
     */
446
    public function isImage()
447
    {
448
        return ($this->microType() === 'image');
449
    }
450
451
    /**
452
     * Determine if the attachment type is an embed object.
453
     *
454
     * @return boolean
455
     */
456
    public function isEmbed()
457
    {
458
        return ($this->microType() === 'embed');
459
    }
460
461
    /**
462
     * Determine if the attachment type is a video.
463
     *
464
     * @return boolean
465
     */
466
    public function isVideo()
467
    {
468
        return ($this->microType() === 'video');
469
    }
470
471
    /**
472
     * Determine if the attachment type is a file attachment.
473
     *
474
     * @return boolean
475
     */
476
    public function isFile()
477
    {
478
        return ($this->microType() === 'file');
479
    }
480
481
    /**
482
     * Determine if the attachment type is a text-area.
483
     *
484
     * @return boolean
485
     */
486
    public function isText()
487
    {
488
        return ($this->microType() === 'text');
489
    }
490
491
    /**
492
     * Determine if the attachment type is an image gallery.
493
     *
494
     * @return boolean
495
     */
496
    public function isGallery()
497
    {
498
        return ($this->microType() === 'gallery');
499
    }
500
501
    /**
502
     * Determine if the attachment type is an accordion.
503
     *
504
     * @return boolean
505
     */
506
    public function isAccordion()
507
    {
508
        return ($this->microType() === 'accordion');
509
    }
510
511
    /**
512
     * Determine if the attachment type is a link.
513
     *
514
     * @return boolean
515
     */
516
    public function isLink()
517
    {
518
        return ($this->microType() === 'link');
519
    }
520
521
    /**
522
     * Determine if this attachment is a container.
523
     *
524
     * @return boolean
525
     */
526
    public function isAttachmentContainer()
527
    {
528
        return ($this instanceof AttachmentContainerInterface);
529
    }
530
531
    // Setters
532
    // =============================================================================
533
534
    /**
535
     * Show/hide the attachment's title on the front-end.
536
     *
537
     * @param  boolean $show Show (TRUE) or hide (FALSE) the title.
538
     * @return UiItemInterface Chainable
539
     */
540
    public function setShowTitle($show)
541
    {
542
        $this->showTitle = !!$show;
543
544
        return $this;
545
    }
546
547
    /**
548
     * Set the attachment's title.
549
     *
550
     * @param  string $title The object title.
551
     * @return self
552
     */
553
    public function setTitle($title)
554
    {
555
        $this->title = $this->translator()->translation($title);
556
557
        return $this;
558
    }
559
560
    /**
561
     * Set the attachment's sub-title.
562
     *
563
     * @param  string $title The object title.
564
     * @return self
565
     */
566
    public function setSubtitle($title)
567
    {
568
        $this->subtitle = $this->translator()->translation($title);
569
570
        return $this;
571
    }
572
573
    /**
574
     * Set the attachment's description.
575
     *
576
     * @param  string $description The description of the object.
577
     * @return self
578
     */
579
    public function setDescription($description)
580
    {
581
        $this->description = $this->translator()->translation($description);
582
583
        if ($this->isPresentable()) {
584
            foreach ($this->description->data() as $lang => $trans) {
585
                $this->description[$lang] = $this->resolveUrls($trans);
586
            }
587
        }
588
589
        return $this;
590
    }
591
592
    /**
593
     * Set the attachment's keywords.
594
     *
595
     * @param  string|string[] $keywords One or more entries.
596
     * @return self
597
     */
598
    public function setKeywords($keywords)
599
    {
600
        $this->keywords = $keywords;
601
602
        return $this;
603
    }
604
605
    /**
606
     * Set the path to the thumbnail associated with the object.
607
     *
608
     * @param  string $path A path to an image.
609
     * @return self
610
     */
611
    public function setThumbnail($path)
612
    {
613
        $this->thumbnail = $this->translator()->translation($path);
614
615
        return $this;
616
    }
617
618
    /**
619
     * Set the path to the attached file.
620
     *
621
     * @param  string $path A path to a file.
622
     * @return self
623
     */
624
    public function setFile($path)
625
    {
626
        $this->file = $this->translator()->translation($path);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->translator()->translation($path) can also be of type object<Charcoal\Translator\Translation>. However, the property $file is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
627
628
        return $this;
629
    }
630
631
    /**
632
     * Set the URL.
633
     *
634
     * @param  string $link An external url.
635
     * @return self
636
     */
637
    public function setLink($link)
638
    {
639
        $this->link = $this->translator()->translation($link);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->translator()->translation($link) can also be of type object<Charcoal\Translator\Translation>. However, the property $link is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
640
641
        return $this;
642
    }
643
644
    /**
645
     * Set the file label.
646
     *
647
     * @param  string $label A descriptor.
648
     * @return self
649
     */
650
    public function setFileLabel($label)
651
    {
652
        $this->fileLabel = $this->translator()->translation($label);
653
654
        return $this;
655
    }
656
657
    /**
658
     * Set the link label.
659
     *
660
     * @param  string $label A descriptor.
661
     * @return self
662
     */
663
    public function setLinkLabel($label)
664
    {
665
        $this->linkLabel = $this->translator()->translation($label);
666
667
        return $this;
668
    }
669
670
    /**
671
     * Set the size of the attached file.
672
     *
673
     * @param  integer|float $size A file size in bytes; the one of the attached.
674
     * @throws InvalidArgumentException If provided argument is not of type 'integer' or 'float'.
675
     * @return self
676
     */
677 View Code Duplication
    public function setFileSize($size)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
678
    {
679
        if ($size === null) {
680
            $this->fileSize = null;
681
682
            return $this;
683
        }
684
685
        if (!is_numeric($size)) {
686
            throw new InvalidArgumentException('File size must be an integer or a float.');
687
        }
688
689
        $this->fileSize = $size;
690
691
        return $this;
692
    }
693
694
    /**
695
     * Set file extension.
696
     *
697
     * @param  string $type File extension.
698
     * @return self
699
     */
700
    public function setFileType($type)
701
    {
702
        $this->fileType = $type;
703
704
        return $this;
705
    }
706
707
    /**
708
     * Set the embed content.
709
     *
710
     * @param  string $embed A URI or an HTML media element.
711
     * @throws InvalidArgumentException If provided argument is not of type 'string'.
712
     * @return self
713
     */
714
    public function setEmbed($embed)
715
    {
716
        $this->embed = $this->translator()->translation($embed);
717
718
        return $this;
719
    }
720
721
    /**
722
     * @param string|\string[] $categories Category elements.
723
     * @return self
724
     */
725
    public function setCategories($categories)
726
    {
727
        $this->categories = $categories;
0 ignored issues
show
Documentation Bug introduced by
It seems like $categories can also be of type array<integer,object<string>>. However, the property $categories is declared as type string|array<integer,string>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
728
729
        return $this;
730
    }
731
732
    // Getters
733
    // =============================================================================
734
735
    /**
736
     * Determine if the title is to be displayed on the front-end.
737
     *
738
     * @return boolean
739
     */
740
    public function showTitle()
741
    {
742
        if (is_bool($this->showTitle)) {
743
            return $this->showTitle;
744
        } else {
745
            return !!$this->title();
746
        }
747
    }
748
749
    /**
750
     * Retrieve the attachment's title.
751
     *
752
     * @return Translation|string|null
753
     */
754
    public function title()
755
    {
756
        return $this->title;
757
    }
758
759
    /**
760
     * Retrieve the attachment's sub-title.
761
     *
762
     * @return Translation|string|null
763
     */
764
    public function subtitle()
765
    {
766
        return $this->subtitle;
767
    }
768
769
    /**
770
     * Retrieve attachment's description.
771
     *
772
     * @return Translation|string|null
773
     */
774
    public function description()
775
    {
776
        return $this->description;
777
    }
778
779
    /**
780
     * Retrieve the attachment's keywords.
781
     *
782
     * @return string[]
783
     */
784
    public function keywords()
785
    {
786
        return $this->keywords;
787
    }
788
789
    /**
790
     * Retrieve the path to the thumbnail associated with the object.
791
     *
792
     * @return string|null
793
     */
794
    public function thumbnail()
795
    {
796
        return $this->thumbnail;
797
    }
798
799
    /**
800
     * Retrieve the path to the attached file.
801
     *
802
     * @return Translation|string|null
803
     */
804
    public function file()
805
    {
806
        return $this->file;
807
    }
808
809
    /**
810
     * Retrieve the attached link.
811
     *
812
     * @return Translation|string|null
813
     */
814
    public function link()
815
    {
816
        return $this->link;
817
    }
818
819
    /**
820
     * Basename of the associated file.
821
     * @return string Basename of file.
822
     */
823
    public function basename()
824
    {
825
        if (!$this->file()) {
826
            return '';
827
        }
828
829
        return basename(strval($this->file()));
830
    }
831
832
    /**
833
     * Retrieve the file label.
834
     *
835
     * @return string|null
836
     */
837
    public function fileLabel()
838
    {
839
        return $this->fileLabel;
840
    }
841
842
    /**
843
     * Retrieve the link label.
844
     *
845
     * @return string|null
846
     */
847
    public function linkLabel()
848
    {
849
        return $this->linkLabel;
850
    }
851
852
    /**
853
     * Retrieve the attached file's size.
854
     *
855
     * @return integer Returns the size of the file in bytes, or FALSE in case of an error.
856
     */
857
    public function fileSize()
858
    {
859
        return $this->fileSize;
860
    }
861
862
    /**
863
     * File type / extension
864
     * @return string File extension.
865
     */
866
    public function fileType()
867
    {
868
        return $this->fileType;
869
    }
870
871
    /**
872
     * Retrieve the embed content.
873
     *
874
     * @return string
875
     */
876
    public function embed()
877
    {
878
        return $this->embed;
879
    }
880
881
    /**
882
     * @return string|\string[]
883
     */
884
    public function categories()
885
    {
886
        return $this->categories;
887
    }
888
889
    // Events
890
    // =============================================================================
891
892
    /**
893
     * Event called before _deleting_ the attachment.
894
     *
895
     * @see    Charcoal\Source\StorableTrait::preDelete() For the "create" Event.
896
     * @see    Charcoal\Attachment\Traits\AttachmentAwareTrait::removeJoins
897
     * @return boolean
898
     */
899
    public function preDelete()
900
    {
901
        $attId = $this->id();
902
        $joinProto = $this->modelFactory()->get(Join::class);
903
        $loader = $this->collectionLoader();
904
        $loader->setModel($joinProto);
905
906
        $collection = $loader->addFilter('attachment_id', $attId)->load();
907
908
        foreach ($collection as $obj) {
909
            $obj->delete();
910
        }
911
912
        return parent::preDelete();
913
    }
914
915
    // Utilities
916
    // =============================================================================
917
918
    /**
919
     * Set the base URI of the project.
920
     *
921
     * @see    \Charcoal\Admin\Support\setBaseUrl::baseUrl()
922
     * @param  UriInterface $uri The base URI.
923
     * @return self
924
     */
925
    protected function setBaseUrl(UriInterface $uri)
926
    {
927
        $this->baseUrl = $uri;
928
929
        return $this;
930
    }
931
932
    /**
933
     * Retrieve the base URI of the project.
934
     *
935
     * @throws RuntimeException If the base URI is missing.
936
     * @return UriInterface|null
937
     */
938
    public function baseUrl()
939
    {
940
        if (!isset($this->baseUrl)) {
941
            throw new RuntimeException(sprintf(
942
                'The base URI is not defined for [%s]',
943
                get_class($this)
944
            ));
945
        }
946
947
        return $this->baseUrl;
948
    }
949
950
    /**
951
     * Prepend the base URI to the given path.
952
     *
953
     * @param  string $uri A URI path to wrap.
954
     * @return UriInterface|null
955
     */
956
    protected function createAbsoluteUrl($uri)
957
    {
958
        if (!isset($uri)) {
959
            return null;
960
        }
961
962
        $uri = strval($uri);
963
        if ($this->isRelativeUri($uri)) {
964
            $parts = parse_url($uri);
965
            $path  = isset($parts['path']) ? $parts['path'] : '';
966
            $query = isset($parts['query']) ? $parts['query'] : '';
967
            $hash  = isset($parts['fragment']) ? $parts['fragment'] : '';
968
969
            return $this->baseUrl()->withPath($path)->withQuery($query)->withFragment($hash);
970
        }
971
972
        return $uri;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $uri; (string) is incompatible with the return type documented by Charcoal\Attachment\Obje...ment::createAbsoluteUrl of type Psr\Http\Message\UriInterface|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
973
    }
974
975
    /**
976
     * Prepend the base URI to the given path.
977
     *
978
     * @param  string $text A string to parse relative URIs.
979
     * @return UriInterface|null
980
     */
981
    protected function resolveUrls($text)
982
    {
983
        static $search;
984
985
        if ($search === null) {
986
            $attr   = [ 'href', 'link', 'url', 'src' ];
987
            $scheme = [ '../', './', '/', 'data', 'mailto', 'http' ];
988
989
            $search = sprintf(
990
                '(?<=%1$s=")(?!%2$s)(\S+)(?=")',
991
                implode('="|', array_map('preg_quote', $attr, [ '~' ])),
992
                implode('|', array_map('preg_quote', $scheme, [ '~' ]))
993
            );
994
        }
995
996
        $text = preg_replace_callback(
997
            '~'.$search.'~i',
998
            function ($matches) {
999
                return $this->createAbsoluteUrl($matches[1]);
1000
            },
1001
            $text
1002
        );
1003
1004
        return $text;
1005
    }
1006
1007
    /**
1008
     * Determine if the given URI is relative.
1009
     *
1010
     * @see    \Charcoal\Admin\Support\BaseUrlTrait::isRelativeUri()
1011
     * @param  string $uri A URI path to test.
1012
     * @return boolean
1013
     */
1014
    protected function isRelativeUri($uri)
1015
    {
1016
        if ($uri && !parse_url($uri, PHP_URL_SCHEME)) {
1017
            if (!in_array($uri[0], [ '/', '#', '?' ])) {
1018
                return true;
1019
            }
1020
        }
1021
1022
        return false;
1023
    }
1024
1025
    /**
1026
     * Set a model collection loader.
1027
     *
1028
     * @param  CollectionLoader $loader The collection loader.
1029
     * @return self
1030
     */
1031
    protected function setCollectionLoader(CollectionLoader $loader)
1032
    {
1033
        $this->collectionLoader = $loader;
1034
1035
        return $this;
1036
    }
1037
1038
    /**
1039
     * Retrieve the model collection loader.
1040
     *
1041
     * @throws Exception If the collection loader was not previously set.
1042
     * @return CollectionLoader
1043
     */
1044
    public function collectionLoader()
1045
    {
1046
        if (!isset($this->collectionLoader)) {
1047
            throw new Exception(sprintf(
1048
                'Collection Loader is not defined for "%s"',
1049
                get_class($this)
1050
            ));
1051
        }
1052
1053
        return $this->collectionLoader;
1054
    }
1055
}
1056