ViewableDataObject::validURLSegment()   C
last analyzed

Complexity

Conditions 14
Paths 76

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 19.5228

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 21
c 2
b 0
f 0
nc 76
nop 0
dl 0
loc 40
ccs 16
cts 23
cp 0.6957
crap 19.5228
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Dynamic\ViewableDataObject\Extensions;
4
5
use SilverStripe\CMS\Controllers\ModelAsController;
6
use SilverStripe\CMS\Controllers\RootURLController;
7
use SilverStripe\CMS\Forms\SiteTreeURLSegmentField;
8
use SilverStripe\CMS\Model\SiteTree;
9
use SilverStripe\Control\ContentNegotiator;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Control\RequestHandler;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Forms\FieldList;
15
use SilverStripe\Forms\TextareaField;
16
use SilverStripe\Forms\TextField;
17
use SilverStripe\Forms\ToggleCompositeField;
18
use SilverStripe\ORM\ArrayList;
19
use SilverStripe\ORM\DataExtension;
20
use SilverStripe\ORM\DataObject;
21
use SilverStripe\ORM\FieldType\DBField;
22
use SilverStripe\ORM\FieldType\DBHTMLText;
23
use SilverStripe\SiteConfig\SiteConfig;
24
use SilverStripe\Versioned\Versioned;
25
use SilverStripe\View\ArrayData;
26
use SilverStripe\View\HTML;
27
use SilverStripe\View\Parsers\URLSegmentFilter;
28
use SilverStripe\View\SSViewer;
29
30
/**
31
 * Class ViewableDataObject
32
 * @package Dynamic\ViewableDataObject\Extensions
33
 *
34
 * @property string $Title
35
 * @property string $MenuTitle
36
 * @property string $URLSegment
37
 * @property string $MetaTitle
38
 * @property string $MetaDescription
39
 */
40
class ViewableDataObject extends DataExtension
41
{
42
    /**
43
     * @var array
44
     */
45
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
46
        'Title' => 'Varchar(255)',
47
        'MenuTitle' => 'Varchar(255)',
48
        'URLSegment' => 'Varchar(255)',
49
        'MetaTitle' => 'Varchar(255)',
50
        'MetaDescription' => 'Varchar(255)',
51
    ];
52
53
    /**
54
     * @var array
55
     */
56
    private static $defaults = array(
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
57
        'Title' => 'New Item',
58
        'URLSegment' => 'new-item',
59
    );
60
61
    /**
62
     * @var array
63
     */
64
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
65
        'URLSegment' => true,
66
    ];
67
68
    /**
69
     * @var array
70
     */
71
    private static $casting = array(
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
72
        'Breadcrumbs' => 'HTMLFragment',
73
        'Link' => 'Text',
74
        'RelativeLink' => 'Text',
75
        'AbsoluteLink' => 'Text',
76
        'MetaTags' => 'HTMLFragment',
77
    );
78
79
    /**
80
     * @param FieldList $fields
81
     */
82 1
    public function updateCMSFields(FieldList $fields)
83
    {
84 1
        $fields->removeByName(array(
85 1
            'MenuTitle',
86
            'URLSegment',
87
            'MetaTitle',
88
            'MetaDescription',
89
        ));
90
91 1
        $fields->insertAfter(
92 1
            TextField::create('MenuTitle'),
93 1
            'Title'
0 ignored issues
show
Bug introduced by
'Title' of type string is incompatible with the type SilverStripe\Forms\FormField expected by parameter $item of SilverStripe\Forms\FieldList::insertAfter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
            /** @scrutinizer ignore-type */ 'Title'
Loading history...
94
        );
95
96 1
        if ($page = $this->hasParentPage()) {
97 1
            $fields->insertAfter(
98 1
                SiteTreeURLSegmentField::create('URLSegment')
99 1
                    ->setURLPrefix($page->Link() . $this->hasViewAction() . '/'),
100 1
                'MenuTitle'
101
            );
102
        }
103
104 1
        $fields->addFieldToTab(
105 1
            'Root.Main',
106 1
            ToggleCompositeField::create(
107 1
                'Metadata',
108 1
                'Metadata',
109
                array(
110 1
                    new TextField('MetaTitle', $this->owner->fieldLabel('MetaTitle')),
111 1
                    new TextareaField('MetaDescription', $this->owner->fieldLabel('MetaDescription')),
112
                )
113
            )
114
        );
115
    }
116
117
    /**
118
     * @return bool
119
     */
120 3
    public function hasParentPage()
121
    {
122 3
        if ($this->owner->hasMethod('getParentPage')) {
123 3
            return $this->owner->getParentPage();
124
        }
125
126
        return false;
127
    }
128
129
    /**
130
     * @return string
131
     */
132 4
    public function hasViewAction()
133
    {
134 4
        if ($this->owner->hasMethod('getViewAction')) {
135 4
            return $this->owner->getViewAction();
136
        }
137
138
        return 'view';
139
    }
140
141
    /**
142
     * @param null $action
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $action is correct as it would always require null to be passed?
Loading history...
143
     *
144
     * @return bool|string
145
     */
146 2
    public function Link($action = null)
147
    {
148 2
        if ($this->hasParentPage()) {
149 2
            return Controller::join_links(
150 2
                $this->hasParentPage()->Link(),
151 2
                $this->hasViewAction(),
152 2
                $this->owner->RelativeLink($action)
153
            );
154
        }
155
156
        return false;
157
    }
158
159
    /**
160
     * @param null $action
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $action is correct as it would always require null to be passed?
Loading history...
161
     *
162
     * @return string
163
     */
164 1
    public function AbsoluteLink($action = null)
165
    {
166 1
        if ($this->owner->hasMethod('alternateAbsoluteLink')) {
167
            return $this->owner->alternateAbsoluteLink($action);
168
        } else {
169 1
            return Director::absoluteURL($this->owner->Link($action));
170
        }
171
    }
172
173
    /**
174
     * @param null $action
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $action is correct as it would always require null to be passed?
Loading history...
175
     *
176
     * @return string
177
     */
178 2
    public function RelativeLink($action = null)
179
    {
180 2
        if ($this->owner->ParentID && SiteConfig::current_site_config()->nested_urls) {
181
            $parent = $this->owner->Parent();
182
            // If page is removed select parent from version history (for archive page view)
183
            if ((!$parent || !$parent->exists()) && !$this->owner->isOnDraft()) {
184
                $parent = Versioned::get_latest_version($this->owner->ClassName, $this->owner->ParentID);
185
            }
186
            $base = $parent->RelativeLink($this->owner->URLSegment);
187 2
        } elseif (!$action && $this->owner->URLSegment == RootURLController::get_homepage_link()) {
0 ignored issues
show
introduced by
$action is of type null, thus it always evaluated to false.
Loading history...
188
            // Unset base for root-level homepages.
189
            // Note: Homepages with action parameters (or $action === true)
190
            // need to retain their URLSegment.
191
            $base = null;
192
        } else {
193 2
            $base = $this->owner->URLSegment;
194
        }
195
196 2
        $this->owner->extend('updateRelativeLink', $base, $action);
197
198
        // Legacy support: If $action === true, retain URLSegment for homepages,
199
        // but don't append any action
200 2
        if ($action === true) {
201
            $action = null;
202
        }
203
204 2
        return Controller::join_links($base, '/', $action);
205
    }
206
207
    /**
208
     * @return bool|mixed
209
     */
210 2
    public function validURLSegment()
211
    {
212 2
        if (SiteConfig::current_site_config()->nested_urls && $parent = $this->owner->Parent()) {
213
            if ($controller = ModelAsController::controller_for($parent)) {
214
                if ($controller instanceof Controller && $controller->hasAction($this->owner->URLSegment)) {
215
                    return false;
216
                }
217
            }
218
        }
219
220 2
        if (!SiteConfig::current_site_config()->nested_urls || !$this->owner->ParentID) {
221 2
            if (class_exists($this->owner->URLSegment) &&
0 ignored issues
show
Bug introduced by
It seems like $this->owner->URLSegment can also be of type null; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
            if (class_exists(/** @scrutinizer ignore-type */ $this->owner->URLSegment) &&
Loading history...
222 2
                is_subclass_of($this->owner->URLSegment, RequestHandler::class)) {
223
                return false;
224
            }
225
        }
226
227
        // Filters by url, id, and parent
228 2
        $table = DataObject::getSchema()->tableForField($this->owner->ClassName, 'URLSegment');
229 2
        $filter = array('"' . $table . '"."URLSegment"' => $this->owner->URLSegment);
230 2
        if ($this->owner->ID) {
231 2
            $filter['"' . $table . '"."ID" <> ?'] = $this->owner->ID;
232
        }
233 2
        if (SiteConfig::current_site_config()->nested_urls) {
234
            $filter['"' . $table . '"."ParentID"'] = $this->owner->ParentID ? $this->owner->ParentID : 0;
235
        }
236
237
        // If any of the extensions return `0` consider the segment invalid
238 2
        $extensionResponses = array_filter(
239 2
            (array)$this->owner->extend('augmentValidURLSegment'),
240 2
            function ($response) {
241
                return !is_null($response);
242 2
            }
243
        );
244 2
        if ($extensionResponses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $extensionResponses 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...
245
            return min($extensionResponses);
246
        }
247
248
        // Check existence
249 2
        return !DataObject::get($this->owner->ClassName, $filter)->exists();
250
    }
251
252
    /**
253
     * @param $title
254
     *
255
     * @return string
256
     */
257
    public function generateURLSegment($title)
258
    {
259
        $filter = URLSegmentFilter::create();
260
        $t = $filter->filter($title);
261
        // Fallback to generic page name if path is empty (= no valid, convertable characters)
262
        if (!$t || $t == '-' || $t == '-1') {
263
            $t = "page-$this->owner->ID";
264
        }
265
        // Hook for extensions
266
        $this->owner->extend('updateURLSegment', $t, $title);
267
268
        return $t;
269
    }
270
271
    /**
272
     * Generate custom meta tags to display on the DataObject view page.
273
     *
274
     * @param bool $includeTitle
275
     *
276
     * @return DBField
277
     */
278
    public function MetaTags($includeTitle = true)
279
    {
280
        $tags = array();
281
282
        if ($includeTitle && strtolower($includeTitle) != 'false') {
0 ignored issues
show
Bug introduced by
$includeTitle of type true is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

282
        if ($includeTitle && strtolower(/** @scrutinizer ignore-type */ $includeTitle) != 'false') {
Loading history...
283
            $title = $this->owner->MetaTitle
284
                ? $this->owner->MetaTitle
285
                : $this->owner->Title;
286
            $tags[] = HTML::createTag('title', array(), $title);
287
        }
288
        $generator = trim(Config::inst()->get(SiteTree::class, 'meta_generator'));
289
        if (!empty($generator)) {
290
            $tags[] = HTML::createTag('meta', array(
291
                'name' => 'generator',
292
                'content' => $generator,
293
            ));
294
        }
295
296
        $charset = ContentNegotiator::config()->uninherited('encoding');
297
        $tags[] = HTML::createTag('meta', array(
298
            'http-equiv' => 'Content-Type',
299
            'content' => 'text/html; charset=' . $charset,
300
        ));
301
302
        if ($this->owner->MetaDescription) {
303
            $tags[] = HTML::createTag('meta', array(
304
                'name' => 'description',
305
                'content' => $this->owner->MetaDescription,
306
            ));
307
        }
308
309
        $this->owner->extend('updateMetaTagsArray', $tags);
310
        $tagString = implode("\n", $tags);
311
        $this->owner->extend('updateMetaTags', $tagString);
312
313
        return DBField::create_field('HTMLFragment', $tagString);
314
    }
315
316
    /**
317
     * Produce the correct breadcrumb trail for use on the DataObject Item Page.
318
     *
319
     * @param int $maxDepth
320
     * @param bool $unlinked
321
     * @param bool $stopAtPageType
322
     * @param bool $showHidden
323
     *
324
     * @return DBHTMLText
325
     */
326 1
    public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false)
0 ignored issues
show
Unused Code introduced by
The parameter $unlinked is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

326
    public function Breadcrumbs($maxDepth = 20, /** @scrutinizer ignore-unused */ $unlinked = false, $stopAtPageType = false, $showHidden = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
327
    {
328 1
        $page = Controller::curr();
329 1
        $pages = array();
330 1
        $pages[] = $this->owner;
331 1
        while ($page
332 1
            && (!$maxDepth || count($pages) < $maxDepth)
333 1
            && (!$stopAtPageType || $page->ClassName != $stopAtPageType)
334
        ) {
335 1
            if ($showHidden || $page->ShowInMenus || ($page->ID == $this->owner->ID)) {
336
                $pages[] = $page;
337
            }
338 1
            $page = $page->Parent;
339
        }
340 1
        $template = new SSViewer('BreadcrumbsTemplate');
341
342 1
        return $template->process($this->owner->customise(new ArrayData(array(
343 1
            'Pages' => new ArrayList(array_reverse($pages)),
344
        ))));
345
    }
346
347
    /**
348
     *
349
     */
350 1
    public function onBeforeWrite()
351
    {
352 1
        if (!$this->owner->URLSegment) {
353
            $siteTree = singleton(SiteTree::class);
354
            $this->owner->URLSegment = $siteTree->generateURLSegment($this->owner->Title);
355
        }
356
357
        // Ensure that this object has a non-conflicting URLSegment value.
358 1
        $count = 2;
359 1
        while (!$this->owner->validURLSegment()) {
360 1
            $this->owner->URLSegment = preg_replace('/-[0-9]+$/', null, $this->owner->URLSegment) . '-' . $count;
361 1
            ++$count;
362
        }
363
364 1
        parent::onBeforeWrite();
365
    }
366
}
367