Completed
Push — master ( 4c93a4...288b18 )
by Jason
13s
created

ViewableDataObject::Breadcrumbs()   B

Complexity

Conditions 9
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 9.0368

Importance

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

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

82
            TextField::create(/** @scrutinizer ignore-type */ 'MenuTitle'),
Loading history...
83 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

83
            /** @scrutinizer ignore-type */ 'Title'
Loading history...
84
        );
85
86 1
        if ($page = $this->hasParentPage()) {
87 1
            $fields->insertAfter(
88 1
                SiteTreeURLSegmentField::create('URLSegment')
89 1
                    ->setURLPrefix($page->Link().$this->hasViewAction().'/'),
90 1
                'MenuTitle'
91
            );
92
        }
93
94 1
        $fields->addFieldToTab(
95 1
            'Root.Main',
96 1
            ToggleCompositeField::create(
97 1
                'Metadata',
98 1
                'Metadata',
99
                array(
100 1
                    new TextField('MetaTitle', $this->owner->fieldLabel('MetaTitle')),
101 1
                    new TextareaField('MetaDescription', $this->owner->fieldLabel('MetaDescription')),
102
                )
103
            )
104
        );
105
    }
106
107
    /**
108
     * @return bool
109
     */
110 3
    public function hasParentPage()
111
    {
112 3
        if ($this->owner->hasMethod('getParentPage')) {
113 3
            return $this->owner->getParentPage();
114
        }
115
116
        return false;
117
    }
118
119
    /**
120
     * @return string
121
     */
122 4
    public function hasViewAction()
123
    {
124 4
        if ($this->owner->hasMethod('getViewAction')) {
125 4
            return $this->owner->getViewAction();
126
        }
127
128
        return 'view';
129
    }
130
131
    /**
132
     * @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...
133
     *
134
     * @return bool|string
135
     */
136 2
    public function Link($action = null)
137
    {
138 2
        if ($this->hasParentPage()) {
139 2
            return Controller::join_links(
140 2
                $this->hasParentPage()->Link(),
141 2
                $this->hasViewAction(),
142 2
                $this->owner->RelativeLink($action)
143
            );
144
        }
145
146
        return false;
147
    }
148
149
    /**
150
     * @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...
151
     *
152
     * @return string
153
     */
154 1
    public function AbsoluteLink($action = null)
155
    {
156 1
        if ($this->owner->hasMethod('alternateAbsoluteLink')) {
157
            return $this->owner->alternateAbsoluteLink($action);
158
        } else {
159 1
            return Director::absoluteURL($this->owner->Link($action));
160
        }
161
    }
162
163
    /**
164
     * @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...
165
     *
166
     * @return string
167
     */
168 2
    public function RelativeLink($action = null)
169
    {
170 2
        if ($this->owner->ParentID && SiteConfig::current_site_config()->nested_urls) {
171
            $parent = $this->owner->Parent();
172
            // If page is removed select parent from version history (for archive page view)
173
            if ((!$parent || !$parent->exists()) && !$this->owner->isOnDraft()) {
174
                $parent = Versioned::get_latest_version($this->owner->ClassName, $this->owner->ParentID);
175
            }
176
            $base = $parent->RelativeLink($this->owner->URLSegment);
177 2
        } elseif (!$action && $this->owner->URLSegment == RootURLController::get_homepage_link()) {
178
            // Unset base for root-level homepages.
179
            // Note: Homepages with action parameters (or $action === true)
180
            // need to retain their URLSegment.
181
            $base = null;
182
        } else {
183 2
            $base = $this->owner->URLSegment;
184
        }
185
186 2
        $this->owner->extend('updateRelativeLink', $base, $action);
187
188
        // Legacy support: If $action === true, retain URLSegment for homepages,
189
        // but don't append any action
190 2
        if ($action === true) {
191
            $action = null;
192
        }
193
194 2
        return Controller::join_links($base, '/', $action);
195
    }
196
197
    /**
198
     * @return bool|mixed
199
     */
200 7
    public function validURLSegment()
201
    {
202 7
        if (SiteConfig::current_site_config()->nested_urls && $parent = $this->owner->Parent()) {
203
            if ($controller = ModelAsController::controller_for($parent)) {
204
                if ($controller instanceof Controller && $controller->hasAction($this->owner->URLSegment)) {
205
                    return false;
206
                }
207
            }
208
        }
209
210 7
        if (!SiteConfig::current_site_config()->nested_urls || !$this->owner->ParentID) {
211 7
            if (class_exists($this->owner->URLSegment) &&
212 7
                is_subclass_of($this->owner->URLSegment, RequestHandler::class)) {
213
                return false;
214
            }
215
        }
216
217
        // Filters by url, id, and parent
218 7
        $table = DataObject::getSchema()->tableForField($this->owner->ClassName, 'URLSegment');
219 7
        $filter = array('"'.$table.'"."URLSegment"' => $this->owner->URLSegment);
220 7
        if ($this->owner->ID) {
221 7
            $filter['"'.$table.'"."ID" <> ?'] = $this->owner->ID;
222
        }
223 7
        if (SiteConfig::current_site_config()->nested_urls) {
224
            $filter['"'.$table.'"."ParentID"'] = $this->owner->ParentID ? $this->owner->ParentID : 0;
225
        }
226
227
        // If any of the extensions return `0` consider the segment invalid
228 7
        $extensionResponses = array_filter(
229 7
            (array) $this->owner->extend('augmentValidURLSegment'),
230 7
            function ($response) {
231
                return !is_null($response);
232 7
            }
233
        );
234 7
        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...
235
            return min($extensionResponses);
236
        }
237
238
        // Check existence
239 7
        return !DataObject::get($this->owner->ClassName, $filter)->exists();
240
    }
241
242
    /**
243
     * @param $title
244
     *
245
     * @return string
246
     */
247
    public function generateURLSegment($title)
248
    {
249
        $filter = URLSegmentFilter::create();
250
        $t = $filter->filter($title);
251
        // Fallback to generic page name if path is empty (= no valid, convertable characters)
252
        if (!$t || $t == '-' || $t == '-1') {
253
            $t = "page-$this->owner->ID";
254
        }
255
        // Hook for extensions
256
        $this->owner->extend('updateURLSegment', $t, $title);
257
258
        return $t;
259
    }
260
261
    /**
262
     * Generate custom meta tags to display on the DataObject view page.
263
     *
264
     * @param bool $includeTitle
265
     *
266
     * @return string
267
     */
268
    public function MetaTags($includeTitle = true)
269
    {
270
        $tags = '';
271
272
        if ($includeTitle === true || $includeTitle == 'true') {
273
            $tags .= '<title>'.Convert::raw2xml(($this->owner->MetaTitle)
0 ignored issues
show
Bug introduced by
Are you sure SilverStripe\Core\Conver... : $this->owner->Title) of type string|array can be used in concatenation? ( Ignorable by Annotation )

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

273
            $tags .= '<title>'./** @scrutinizer ignore-type */ Convert::raw2xml(($this->owner->MetaTitle)
Loading history...
274
                    ? $this->owner->MetaTitle
275
                    : $this->owner->Title)
276
                    ."</title>\n";
277
        }
278
        $tags .= "<meta name=\"generator\" content=\"SilverStripe - http://silverstripe.org\" />\n";
279
        $charset = Config::inst()->get('ContentNegotiator', 'encoding');
280
        $tags .= "<meta http-equiv=\"Content-type\" content=\"text/html; charset=$charset\" />\n";
281
        if ($this->owner->MetaDescription) {
282
            $tags .= '<meta name="description" content="'.Convert::raw2att($this->owner->MetaDescription)."\" />\n";
0 ignored issues
show
Bug introduced by
Are you sure SilverStripe\Core\Conver...owner->MetaDescription) of type string|array can be used in concatenation? ( Ignorable by Annotation )

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

282
            $tags .= '<meta name="description" content="'./** @scrutinizer ignore-type */ Convert::raw2att($this->owner->MetaDescription)."\" />\n";
Loading history...
283
        }
284
        $this->owner->extend('updateMetaTags', $tags);
285
286
        return $tags;
287
    }
288
289
    /**
290
     * Produce the correct breadcrumb trail for use on the DataObject Item Page.
291
     *
292
     * @param int  $maxDepth
293
     * @param bool $unlinked
294
     * @param bool $stopAtPageType
295
     * @param bool $showHidden
296
     *
297
     * @return DBHTMLText
298
     */
299 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

299
    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...
300
    {
301 1
        $page = Controller::curr();
302 1
        $pages = array();
303 1
        $pages[] = $this->owner;
304 1
        while ($page
305 1
            && (!$maxDepth || count($pages) < $maxDepth)
306 1
            && (!$stopAtPageType || $page->ClassName != $stopAtPageType)
307
        ) {
308 1
            if ($showHidden || $page->ShowInMenus || ($page->ID == $this->owner->ID)) {
309
                $pages[] = $page;
310
            }
311 1
            $page = $page->Parent;
312
        }
313 1
        $template = new SSViewer('BreadcrumbsTemplate');
314
315 1
        return $template->process($this->owner->customise(new ArrayData(array(
316 1
            'Pages' => new ArrayList(array_reverse($pages)),
317
        ))));
318
    }
319
320
    /**
321
     *
322
     */
323 7
    public function onBeforeWrite()
324
    {
325 7
        if (!$this->owner->URLSegment) {
326
            $siteTree = singleton(SiteTree::class);
327
            $this->owner->URLSegment = $siteTree->generateURLSegment($this->owner->Title);
328
        }
329
330
        // Ensure that this object has a non-conflicting URLSegment value.
331 7
        $count = 2;
332 7
        while (!$this->owner->validURLSegment()) {
333 7
            $this->owner->URLSegment = preg_replace('/-[0-9]+$/', null, $this->owner->URLSegment).'-'.$count;
334 7
            ++$count;
335
        }
336
337 7
        parent::onBeforeWrite();
338
    }
339
}
340