ViewableDataObject::RelativeLink()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 16.9615

Importance

Changes 0
Metric Value
cc 9
eloc 13
nc 8
nop 1
dl 0
loc 27
rs 8.0555
c 0
b 0
f 0
ccs 7
cts 13
cp 0.5385
crap 16.9615
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