Completed
Push — master ( d8c92c...e99919 )
by
unknown
03:03
created

AbstractSection::loadChildren()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 3 Features 0
Metric Value
c 4
b 3
f 0
dl 0
loc 22
rs 9.2
cc 1
eloc 15
nc 1
nop 0
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
use \Charcoal\Loader\CollectionLoader;
14
15
// Module `charcoal-base` dependencies
16
use \Charcoal\Object\Content;
17
use \Charcoal\Object\HierarchicalInterface;
18
use \Charcoal\Object\HierarchicalTrait;
19
use \Charcoal\Object\RoutableInterface;
20
use \Charcoal\Object\RoutableTrait;
21
22
// Intra-module (`charcoal-cms`) dependencies
23
use \Charcoal\Cms\MetatagInterface;
24
use \Charcoal\Cms\SearchableInterface;
25
use \Charcoal\Cms\SectionInterface;
26
27
/**
28
 * A Section is a unique, reachable page.
29
 *
30
 * ## Types of sections
31
 * There can be different types of ection. 4 exists in the CMS module:
32
 * - `blocks`
33
 * - `content`
34
 * - `empty`
35
 * - `external`
36
 *
37
 * ## External implementations
38
 * Sections imlpement the following _Interface_ / _Trait_:
39
 * - From the `Charcoal\Object` namespace (in `charcoal-base`)
40
 *   - `Hierarchical`
41
 *   - `Routable`
42
 * - From the local `Charcoal\Cms` namespace
43
 *   - `Metatag`
44
 *   - `Searchable`
45
 *
46
 */
47
abstract class AbstractSection extends Content implements
48
    HierarchicalInterface,
49
    MetatagInterface,
50
    RoutableInterface,
51
    SearchableInterface,
52
    SectionInterface
53
{
54
    use HierarchicalTrait;
55
    use MetatagTrait;
56
    use RoutableTrait;
57
    use SearchableTrait;
58
59
    const TYPE_BLOCKS = 'charcoal/cms/section/blocks';
60
    const TYPE_CONTENT = 'charcoal/cms/section/content';
61
    const TYPE_EMPTY = 'charcoal/cms/section/empty';
62
    const TYPE_EXTERNAL = 'charcoal/cms/section/external';
63
    const DEFAULT_TYPE = self::TYPE_CONTENT;
64
65
    /**
66
     * @var string $sectionType
67
     */
68
    private $sectionType = self::DEFAULT_TYPE;
69
70
    /**
71
     * @var TranslationString $title
72
     */
73
    private $title;
74
    /**
75
     * @var TranslationString $subtitle
76
     */
77
    private $subtitle;
78
    /**
79
     * @var TranslationString $content
80
     */
81
    private $content;
82
83
    /**
84
     * @var TranslationString $image
85
     */
86
    private $image;
87
88
    /**
89
     * @var mixed $templateIdent
90
     */
91
    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...
92
    /**
93
     * @var array $templateOptions
94
     */
95
    private $templateOptions = [];
96
97
    /**
98
     * @var array $attachments
99
     */
100
    private $attachments;
101
102
    /**
103
     * @param string $sectionType The section type.
104
     * @throws InvalidArgumentException If the section type is not a string or not a valid section type.
105
     * @return SectionInterface Chainable
106
     */
107
    public function setSectionType($sectionType)
108
    {
109
        if (!is_string($sectionType)) {
110
            throw new InvalidArgumentException(
111
                'Section type must be a string'
112
            );
113
        }
114
        $validTypes = [
115
            self::TYPE_CONTENT,
116
            self::TYPE_EMPTY,
117
            self::TYPE_EXTERNAL
118
        ];
119
        if (!in_array($sectionType, $validTypes)) {
120
            throw new InvalidArgumentException(
121
                'Section type is not valid'
122
            );
123
        }
124
125
        $this->sectionType = $sectionType;
126
        return $this;
127
    }
128
129
    /**
130
     * @return string
131
     */
132
    public function sectionType()
133
    {
134
        return $this->sectionType;
135
    }
136
137
    /**
138
     * @param mixed $title The section title (localized).
139
     * @return TranslationString
140
     */
141
    public function setTitle($title)
142
    {
143
        $this->title = new TranslationString($title);
144
        return $this;
145
    }
146
147
    /**
148
     * @return TranslationString
149
     */
150
    public function title()
151
    {
152
        return $this->title;
153
    }
154
155
    /**
156
     * @param mixed $subtitle The section subtitle (localized).
157
     * @return Section Chainable
158
     */
159
    public function setSubtitle($subtitle)
160
    {
161
        $this->subtitle = new TranslationString($subtitle);
162
        return $this;
163
    }
164
165
    /**
166
     * @return TranslationString
167
     */
168
    public function subtitle()
169
    {
170
        return $this->subtitle;
171
    }
172
173
    /**
174
     * @param mixed $content The section content (localized).
175
     * @return Section Chainable
176
     */
177
    public function setContent($content)
178
    {
179
        $this->content = new TranslationString($content);
180
        return $this;
181
    }
182
183
    /**
184
     * @return TranslationString
185
     */
186
    public function content()
187
    {
188
        return $this->content;
189
    }
190
191
    /**
192
     * @param mixed $image The section main image (localized).
193
     * @return Section Chainable
194
     */
195
    public function setImage($image)
196
    {
197
        $this->image = new TranslationString($image);
198
        return $this;
199
    }
200
201
    /**
202
     * @return TranslationString
203
     */
204
    public function image()
205
    {
206
        return $this->image;
207
    }
208
209
    /**
210
     * @param mixed $template The section template (ident).
211
     * @return SectionInterface Chainable
212
     */
213
    public function setTemplateIdent($template)
214
    {
215
        $this->templateIdent = $template;
216
        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...
217
    }
218
219
    /**
220
     * @return mixed
221
     */
222
    public function templateIdent()
223
    {
224
        if (!$this->templateIdent) {
225
            $metadata = $this->metadata();
226
            return $metadata['template_ident'];
227
        }
228
        return $this->templateIdent;
229
    }
230
231
    /**
232
     * @param array|string $templateOptions Extra template options, if any.
233
     * @return SectionInterface Chainable
234
     */
235
    public function setTemplateOptions($templateOptions)
236
    {
237
        $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...
238
        return $this;
239
    }
240
241
    /**
242
     * @return array
243
     */
244
    public function templateOptions()
245
    {
246
        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...
247
            $metadata = $this->metadata();
248
            return $metadata['template_options'];
249
        }
250
        return $this->templateOptions;
251
    }
252
253
    /**
254
     * @param string $type Optional type.
255
     * @return array
256
     */
257
    public function attachments($type = null)
258
    {
259
        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...
260
            $this->attachments = $this->loadAttachments();
261
        }
262
        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...
263
            // Return only the attachments of a certain type.
264
            return $this->attachments[$type];
265
        } else {
266
            // Return all attachments, grouped by types.
267
            return $this->attachments;
268
        }
269
    }
270
271
    /**
272
     * @return array
273
     */
274
    public function loadAttachments()
275
    {
276
        return [];
277
    }
278
279
    /**
280
     * HierarchicalTrait > loadChildren
281
     *
282
     * @return Co
283
     */
284
    public function loadChildren()
285
    {
286
        $loader = new CollectionLoader([
287
            'logger' => $this->logger,
288
            'factory' => $this->modelFactory()
289
        ]);
290
        $loader->setModel($this);
291
        $loader->addFilter([
292
            'property'=>'master',
293
            'val'=>$this->id()
294
        ]);
295
        $loader->addFilter([
296
            'property'=>'active',
297
            'val'=>true
298
        ]);
299
300
        $loader->addOrder([
301
            'property'=>'position',
302
            'mode'=>'asc'
303
        ]);
304
        return $loader->load();
305
    }
306
307
    /**
308
     * MetatagTrait > canonicalUrl
309
     *
310
     * @return string
311
     * @todo
312
     */
313
    public function canonicalUrl()
314
    {
315
        return '';
316
    }
317
318
    /**
319
     * @return TranslationString
320
     */
321
    public function defaultMetaTitle()
322
    {
323
        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...
324
    }
325
326
    /**
327
     * @return TranslationString
328
     */
329
    public function defaultMetaDescription()
330
    {
331
        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...
332
    }
333
334
    /**
335
     * @return TranslationString
336
     */
337
    public function defaultMetaImage()
338
    {
339
        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...
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     *
345
     * @return boolean
346
     */
347
    public function preSave()
348
    {
349
        $this->setSlug($this->generateSlug());
350
        return parent::preSave();
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     *
356
     * @param array $properties Optional properties to update.
357
     * @return boolean
358
     */
359
    public function preUpdate(array $properties = null)
360
    {
361
        // if (!$this->slug) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
362
            $this->setSlug($this->generateSlug());
363
        // }
364
        return parent::preUpdate($properties);
365
    }
366
367
}
368