Completed
Push — master ( 4f82af...913b4d )
by
unknown
03:04
created

AbstractSection::preUpdate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Charcoal\Cms;
4
5
use \InvalidArgumentException;
6
7
// Module `charcoal-core` dependencies
8
use \Charcoal\Model\Collection;
9
10
// Module `charcoal-translation` dependencies
11
use \Charcoal\Translation\TranslationString;
12
13
// Module `charcoal-base` dependencies
14
use \Charcoal\Object\Content;
15
use \Charcoal\Object\HierarchicalInterface;
16
use \Charcoal\Object\HierarchicalTrait;
17
use \Charcoal\Object\RoutableInterface;
18
use \Charcoal\Object\RoutableTrait;
19
20
// Intra-module (`charcoal-cms`) dependencies
21
use \Charcoal\Cms\MetatagInterface;
22
use \Charcoal\Cms\SearchableInterface;
23
use \Charcoal\Cms\SectionInterface;
24
25
/**
26
 * A Section is a unique, reachable page.
27
 *
28
 * ## Types of sections
29
 * There can be different types of ection. 4 exists in the CMS module:
30
 * - `blocks`
31
 * - `content`
32
 * - `empty`
33
 * - `external`
34
 *
35
 * ## External implementations
36
 * Sections imlpement the following _Interface_ / _Trait_:
37
 * - From the `Charcoal\Object` namespace (in `charcoal-base`)
38
 *   - `Hierarchical`
39
 *   - `Routable`
40
 * - From the local `Charcoal\Cms` namespace
41
 *   - `Metatag`
42
 *   - `Searchable`
43
 *
44
 */
45
abstract class AbstractSection extends Content implements
46
    HierarchicalInterface,
47
    MetatagInterface,
48
    RoutableInterface,
49
    SearchableInterface,
50
    SectionInterface
51
{
52
    use HierarchicalTrait;
53
    use MetatagTrait;
54
    use RoutableTrait;
55
    use SearchableTrait;
56
57
    const TYPE_BLOCKS = 'charcoal/cms/section/blocks';
58
    const TYPE_CONTENT = 'charcoal/cms/section/content';
59
    const TYPE_EMPTY = 'charcoal/cms/section/empty';
60
    const TYPE_EXTERNAL = 'charcoal/cms/section/external';
61
    const DEFAULT_TYPE = self::TYPE_CONTENT;
62
63
    /**
64
     * @var string $sectionType
65
     */
66
    private $sectionType = self::DEFAULT_TYPE;
67
68
    /**
69
     * @var TranslationString $title
70
     */
71
    private $title;
72
    /**
73
     * @var TranslationString $subtitle
74
     */
75
    private $subtitle;
76
    /**
77
     * @var TranslationString $content
78
     */
79
    private $content;
80
81
    /**
82
     * @var TranslationString $image
83
     */
84
    private $image;
85
86
    /**
87
     * @var mixed $templateIdent
88
     */
89
    private $templateIdent;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
90
    /**
91
     * @var array $templateOptions
92
     */
93
    private $templateOptions = [];
94
95
    /**
96
     * @var array $attachments
97
     */
98
    private $attachments;
99
100
    /**
101
     * Generate a slug on preSave
102
     * @see RoutableTrait
103
     * @return parent::preSave
0 ignored issues
show
Documentation introduced by
The doc-type parent::preSave could not be parsed: Unknown type name "parent::preSave" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
104
     */
105
    public function preSave()
106
    {
107
        $this->setSlug($this->generateSlug());
108
109
        return parent::preSave();
110
    }
111
112
    /**
113
     * Generate a slug on preUpdate
114
     * @see RoutableTrait
115
     * @param array $properties Properties to update.
116
     * @return parent::preUpdate
0 ignored issues
show
Documentation introduced by
The doc-type parent::preUpdate could not be parsed: Unknown type name "parent::preUpdate" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
117
     */
118
    public function preUpdate(array $properties = null)
119
    {
120
        $this->setSlug($this->generateSlug());
121
122
        return parent::preUpdate($properties);
123
    }
124
125
    /**
126
     * @param string $sectionType The section type.
127
     * @throws InvalidArgumentException If the section type is not a string or not a valid section type.
128
     * @return SectionInterface Chainable
129
     */
130
    public function setSectionType($sectionType)
131
    {
132
        if (!is_string($sectionType)) {
133
            throw new InvalidArgumentException(
134
                'Section type must be a string'
135
            );
136
        }
137
        $validTypes = [
138
            self::TYPE_CONTENT,
139
            self::TYPE_EMPTY,
140
            self::TYPE_EXTERNAL
141
        ];
142
        if (!in_array($sectionType, $validTypes)) {
143
            throw new InvalidArgumentException(
144
                'Section type is not valid'
145
            );
146
        }
147
148
        $this->sectionType = $sectionType;
149
        return $this;
150
    }
151
152
    /**
153
     * @return string
154
     */
155
    public function sectionType()
156
    {
157
        return $this->sectionType;
158
    }
159
160
    /**
161
     * @param mixed $title The section title (localized).
162
     * @return TranslationString
163
     */
164
    public function setTitle($title)
165
    {
166
        $this->title = new TranslationString($title);
167
        return $this;
168
    }
169
170
    /**
171
     * @return TranslationString
172
     */
173
    public function title()
174
    {
175
        return $this->title;
176
    }
177
178
    /**
179
     * @param mixed $subtitle The section subtitle (localized).
180
     * @return Section Chainable
181
     */
182
    public function setSubtitle($subtitle)
183
    {
184
        $this->subtitle = new TranslationString($subtitle);
185
        return $this;
186
    }
187
188
    /**
189
     * @return TranslationString
190
     */
191
    public function subtitle()
192
    {
193
        return $this->subtitle;
194
    }
195
196
    /**
197
     * @param mixed $content The section content (localized).
198
     * @return Section Chainable
199
     */
200
    public function setContent($content)
201
    {
202
        $this->content = new TranslationString($content);
203
        return $this;
204
    }
205
206
    /**
207
     * @return TranslationString
208
     */
209
    public function content()
210
    {
211
        return $this->content;
212
    }
213
214
    /**
215
     * @param mixed $image The section main image (localized).
216
     * @return Section Chainable
217
     */
218
    public function setImage($image)
219
    {
220
        $this->image = new TranslationString($image);
221
        return $this;
222
    }
223
224
    /**
225
     * @return TranslationString
226
     */
227
    public function image()
228
    {
229
        return $this->image;
230
    }
231
232
    /**
233
     * @param mixed $template The section template (ident).
234
     * @return SectionInterface Chainable
235
     */
236
    public function setTemplateIdent($template)
237
    {
238
        $this->templateIdent = $template;
239
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Charcoal\Cms\AbstractSection) is incompatible with the return type of the parent method Charcoal\Model\AbstractModel::setTemplateIdent of type Charcoal\View\ViewableTrait.

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...
240
    }
241
242
    /**
243
     * @return mixed
244
     */
245
    public function templateIdent()
246
    {
247
        if (!$this->templateIdent) {
248
            $metadata = $this->metadata();
249
            return $metadata['template_ident'];
250
        }
251
        return $this->templateIdent;
252
    }
253
254
    /**
255
     * @param array|string $templateOptions Extra template options, if any.
256
     * @return SectionInterface Chainable
257
     */
258
    public function setTemplateOptions($templateOptions)
259
    {
260
        $this->templateOptions = $templateOptions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $templateOptions can also be of type string. However, the property $templateOptions is declared as type array. 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...
261
        return $this;
262
    }
263
264
    /**
265
     * @return array
266
     */
267
    public function templateOptions()
268
    {
269
        if (!$this->templateOptions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->templateOptions 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...
270
            $metadata = $this->metadata();
271
            return $metadata['template_options'];
272
        }
273
        return $this->templateOptions;
274
    }
275
276
    /**
277
     * @param string $type Optional type.
278
     * @return array
279
     */
280
    public function attachments($type = null)
281
    {
282
        if (!$this->attachments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->attachments 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...
283
            $this->attachments = $this->loadAttachments();
284
        }
285
        if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
286
            // Return only the attachments of a certain type.
287
            return $this->attachments[$type];
288
        } else {
289
            // Return all attachments, grouped by types.
290
            return $this->attachments;
291
        }
292
    }
293
294
    /**
295
     * @return array
296
     */
297
    public function loadAttachments()
298
    {
299
        return [];
300
    }
301
302
    /**
303
     * HierarchicalTrait > loadChildren
304
     *
305
     * @return array
306
     */
307
    public function loadChildren()
308
    {
309
        $source = clone($this->source());
310
        $source->reset();
311
        $source->setFilters([
312
            [
313
                'property'=>'master',
314
                'val'=>$this->id()
315
            ],
316
            [
317
                'property'=>'active',
318
                'val'=>1
319
            ]
320
        ]);
321
        $source->setOrders([
322
            [
323
                'property'=>'position',
324
                'mode'=>'asc'
325
            ]
326
        ]);
327
        $children = $source->loadItems();
328
        return $children;
329
    }
330
331
    /**
332
     * MetatagTrait > canonicalUrl
333
     *
334
     * @return string
335
     * @todo
336
     */
337
    public function canonicalUrl()
338
    {
339
        return '';
340
    }
341
342
    /**
343
     * @return TranslationString
344
     */
345
    public function defaultMetaTitle()
346
    {
347
        return $this->title();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->title(); (Charcoal\Translation\TranslationString) is incompatible with the return type declared by the interface Charcoal\Cms\MetatagInterface::defaultMetaTitle of type Charcoal\Cms\TranslationString.

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...
348
    }
349
350
    /**
351
     * @return TranslationString
352
     */
353
    public function defaultMetaDescription()
354
    {
355
        return $this->content();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->content(); (Charcoal\Translation\TranslationString) is incompatible with the return type declared by the interface Charcoal\Cms\MetatagInte...:defaultMetaDescription of type Charcoal\Cms\TranslationString.

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...
356
    }
357
358
    /**
359
     * @return TranslationString
360
     */
361
    public function defaultMetaImage()
362
    {
363
        return $this->image();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->image(); (Charcoal\Translation\TranslationString) is incompatible with the return type declared by the interface Charcoal\Cms\MetatagInterface::defaultMetaImage of type Charcoal\Cms\TranslationString.

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...
364
    }
365
}
366