Passed
Pull Request — master (#123)
by Robbie
01:32
created

BaseElement::PreviewLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace DNADesign\Elemental\Models;
4
5
use Exception;
6
use DNADesign\Elemental\Forms\ElementalGridFieldHistoryButton;
7
use DNADesign\Elemental\Forms\HistoricalVersionedGridFieldItemRequest;
8
use DNADesign\Elemental\Controllers\ElementController;
9
use SilverStripe\CMS\Controllers\CMSPageEditController;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Core\ClassInfo;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Core\Injector\Injector;
15
use SilverStripe\Core\Manifest\ModuleResourceLoader;
16
use SilverStripe\Forms\CheckboxField;
17
use SilverStripe\Forms\DropdownField;
18
use SilverStripe\Forms\FieldGroup;
19
use SilverStripe\Forms\FieldList;
20
use SilverStripe\Forms\HiddenField;
21
use SilverStripe\Forms\NumericField;
22
use SilverStripe\Forms\ReadonlyField;
23
use SilverStripe\Forms\TextField;
24
use SilverStripe\Forms\GridField\GridField;
25
use SilverStripe\Forms\GridField\GridFieldConfig_RecordViewer;
26
use SilverStripe\Forms\GridField\GridFieldDataColumns;
27
use SilverStripe\Forms\GridField\GridFieldDetailForm;
28
use SilverStripe\Forms\GridField\GridFieldPageCount;
29
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
30
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
31
use SilverStripe\Forms\GridField\GridFieldVersionedState;
32
use SilverStripe\Forms\GridField\GridFieldViewButton;
33
use SilverStripe\ORM\ArrayList;
34
use SilverStripe\ORM\CMSPreviewable;
35
use SilverStripe\ORM\DataObject;
36
use SilverStripe\ORM\DB;
37
use SilverStripe\ORM\FieldType\DBField;
38
use SilverStripe\ORM\Search\SearchContext;
39
use SilverStripe\Security\Permission;
40
use SilverStripe\Security\Member;
41
use SilverStripe\SiteConfig\SiteConfig;
42
use SilverStripe\Versioned\Versioned;
43
use SilverStripe\View\Parsers\URLSegmentFilter;
44
use SilverStripe\View\Requirements;
45
use SilverStripe\View\SSViewer;
46
use Symbiote\GridFieldExtensions\GridFieldTitleHeader;
47
48
class BaseElement extends DataObject implements CMSPreviewable
49
{
50
    /**
51
     * Override this on your custom elements to specify a cms icon
52
     *
53
     * @var string
54
     */
55
    private static $icon = 'dnadesign/silverstripe-elemental:images/base.svg';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
56
57
    private static $db = [
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
58
        'Title' => 'Varchar(255)',
59
        'ShowTitle' => 'Boolean',
60
        'Sort' => 'Int',
61
        'ExtraClass' => 'Varchar(255)',
62
        'Style' => 'Varchar(255)'
63
    ];
64
65
    private static $has_one = [
0 ignored issues
show
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
66
        'Parent' => ElementalArea::class
67
    ];
68
69
    private static $extensions = [
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
70
        Versioned::class
71
    ];
72
73
    private static $versioned_gridfield_extensions = true;
0 ignored issues
show
Unused Code introduced by
The property $versioned_gridfield_extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
74
75
    private static $table_name = 'Element';
0 ignored issues
show
Unused Code introduced by
The property $table_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
76
77
    /**
78
     * @var string
79
     */
80
    private static $controller_class = ElementController::class;
0 ignored issues
show
Unused Code introduced by
The property $controller_class is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
81
82
    /**
83
     * @var string
84
     */
85
    private static $controller_template = 'ElementHolder';
0 ignored issues
show
Unused Code introduced by
The property $controller_template is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
86
87
    /**
88
     * @var ElementController
89
     */
90
    protected $controller;
91
92
    private static $default_sort = 'Sort';
0 ignored issues
show
Unused Code introduced by
The property $default_sort is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
93
94
    private static $singular_name = 'block';
0 ignored issues
show
Unused Code introduced by
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
95
96
    private static $plural_name = 'blocks';
0 ignored issues
show
Unused Code introduced by
The property $plural_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
97
98
    private static $summary_fields = [
0 ignored issues
show
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
99
        'EditorPreview' => 'Summary'
100
    ];
101
102
    /**
103
     * @var array
104
     */
105
    private static $styles = [];
0 ignored issues
show
Unused Code introduced by
The property $styles is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
106
107
    private static $searchable_fields = [
0 ignored issues
show
Unused Code introduced by
The property $searchable_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
108
        'ID' => [
109
            'field' => NumericField::class,
110
        ],
111
        'Title',
112
        'LastEdited'
113
    ];
114
115
    /**
116
     * Enable for backwards compatibility
117
     *
118
     * @var boolean
119
     */
120
    private static $disable_pretty_anchor_name = false;
0 ignored issues
show
Unused Code introduced by
The property $disable_pretty_anchor_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
121
122
    /**
123
     * Store used anchor names, this is to avoid title clashes
124
     * when calling 'getAnchor'
125
     *
126
     * @var array
127
     */
128
    protected static $_used_anchors = [];
129
130
    /**
131
     * For caching 'getAnchor'
132
     *
133
     * @var string
134
     */
135
    protected $_anchor = null;
136
137
    /**
138
     * Basic permissions, defaults to page perms where possible.
139
     *
140
     * @param Member $member
141
     *
142
     * @return boolean
143
     */
144 View Code Duplication
    public function canView($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
    {
146
        if ($this->hasMethod('getPage')) {
147
            if ($page = $this->getPage()) {
148
                return $page->canView($member);
149
            }
150
        }
151
152
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
153
    }
154
155
    /**
156
     * Basic permissions, defaults to page perms where possible.
157
     *
158
     * @param Member $member
159
     *
160
     * @return boolean
161
     */
162 View Code Duplication
    public function canEdit($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
    {
164
        if ($this->hasMethod('getPage')) {
165
            if ($page = $this->getPage()) {
166
                return $page->canEdit($member);
167
            }
168
        }
169
170
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
171
    }
172
173
    /**
174
     * Basic permissions, defaults to page perms where possible.
175
     *
176
     * Uses archive not delete so that current stage is respected i.e if a
177
     * element is not published, then it can be deleted by someone who doesn't
178
     * have publishing permissions.
179
     *
180
     * @param Member $member
181
     *
182
     * @return boolean
183
     */
184 View Code Duplication
    public function canDelete($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
    {
186
        if ($this->hasMethod('getPage')) {
187
            if ($page = $this->getPage()) {
188
                return $page->canArchive($member);
189
            }
190
        }
191
192
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
193
    }
194
195
    /**
196
     * Basic permissions, defaults to page perms where possible.
197
     *
198
     * @param Member $member
199
     * @param array $context
200
     *
201
     * @return boolean
202
     */
203
    public function canCreate($member = null, $context = array())
204
    {
205
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
206
    }
207
208
    /**
209
     *
210
     */
211
    public function onBeforeWrite()
212
    {
213
        parent::onBeforeWrite();
214
215
        if ($areaID = $this->ParentID) {
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
216
            if ($elementalArea = ElementalArea::get()->byID($areaID)) {
217
                $elementalArea->write();
218
            }
219
        }
220
221
        if (!$this->Sort) {
222
            $parentID = ($this->ParentID) ? $this->ParentID : 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $parentID is dead and can be removed.
Loading history...
223
224
            $this->Sort = static::get()->max('Sort') + 1;
0 ignored issues
show
Bug Best Practice introduced by
The property Sort does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
225
        }
226
    }
227
228
    public function getCMSFields()
229
    {
230
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
231
            // Remove relationship fields
232
            $fields->removeByName('ParentID');
233
            $fields->removeByName('Sort');
234
235
            $fields->addFieldToTab(
236
                'Root.Settings',
237
                TextField::create('ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
0 ignored issues
show
Bug introduced by
'ExtraClass' 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

237
                TextField::create(/** @scrutinizer ignore-type */ 'ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
Loading history...
238
                    ->setAttribute(
239
                        'placeholder',
240
                        _t(__CLASS__ . '.ExtraCssClassesPlaceholder', 'my_class another_class')
241
                    )
242
            );
243
244
            // Add a combined field for "Title" and "Displayed" checkbox in a Bootstrap input group
245
            $fields->removeByName('ShowTitle');
246
            $fields->replaceField(
247
                'Title',
248
                FieldGroup::create(
249
                    TextField::create('Title', ''),
250
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
251
                )
252
                    ->setTemplate(__CLASS__ . '\\FieldGroup')
253
                    ->setTitle(_t(__CLASS__ . '.TitleLabel', 'Title (not displayed unless specified)'))
254
            );
255
256
            // Rename the "Main" tab
257
            $fields->fieldByName('Root.Main')
258
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
259
260
            // Remove divider lines on all block forms
261
            $fields->fieldByName('Root')->addExtraClass('form--no-dividers');
262
263
            $fields->addFieldsToTab('Root.Main', [
264
                HiddenField::create('AbsoluteLink', false, Director::absoluteURL($this->PreviewLink())),
0 ignored issues
show
Bug introduced by
false of type false 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

264
                HiddenField::create('AbsoluteLink', /** @scrutinizer ignore-type */ false, Director::absoluteURL($this->PreviewLink())),
Loading history...
265
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
266
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
267
            ]);
268
269
            $styles = $this->config()->get('styles');
270
271
            if ($styles && count($styles) > 0) {
272
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
273
274
                $fields->insertBefore($styleDropdown, 'ExtraClass');
0 ignored issues
show
Bug introduced by
'ExtraClass' of type string is incompatible with the type SilverStripe\Forms\FormField expected by parameter $item of SilverStripe\Forms\FieldList::insertBefore(). ( Ignorable by Annotation )

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

274
                $fields->insertBefore($styleDropdown, /** @scrutinizer ignore-type */ 'ExtraClass');
Loading history...
275
276
                $styleDropdown->setEmptyString(_t(__CLASS__.'.CUSTOM_STYLES', 'Select a style..'));
277
            } else {
278
                $fields->removeByName('Style');
279
            }
280
281
            $history = $this->getHistoryFields();
282
283
            if ($history) {
284
                $fields->addFieldsToTab('Root.History', $history);
0 ignored issues
show
Bug introduced by
$history of type SilverStripe\Forms\FieldList is incompatible with the type array expected by parameter $fields of SilverStripe\Forms\FieldList::addFieldsToTab(). ( Ignorable by Annotation )

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

284
                $fields->addFieldsToTab('Root.History', /** @scrutinizer ignore-type */ $history);
Loading history...
285
            }
286
        });
287
288
        return parent::getCMSFields();
289
    }
290
291
    /**
292
     * Returns the history fields for this element.
293
     *
294
     * @param  bool $checkLatestVersion Whether to check if this is the latest version. Prevents recursion, but can be
295
     *                                  overridden to get the history GridField if required.
296
     * @return FieldList
297
     */
298
    public function getHistoryFields($checkLatestVersion = true)
299
    {
300
        if ($checkLatestVersion && !$this->isLatestVersion()) {
0 ignored issues
show
Bug introduced by
The method isLatestVersion() does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

300
        if ($checkLatestVersion && !$this->/** @scrutinizer ignore-call */ isLatestVersion()) {
Loading history...
301
            // if viewing the history of the of page then don't show the history
302
            // fields as then we have recursion.
303
            return null;
304
        }
305
306
        Requirements::javascript('dnadesign/silverstripe-elemental:javascript/block-history.js');
307
308
        $config = GridFieldConfig_RecordViewer::create();
309
        $config->removeComponentsByType(GridFieldPageCount::class);
310
        $config->removeComponentsByType(GridFieldToolbarHeader::class);
311
        // Replace the sortable ID column with a static header component
312
        $config->removeComponentsByType(GridFieldSortableHeader::class);
313
        $config->addComponent(new GridFieldTitleHeader);
314
315
        $config
316
            ->getComponentByType(GridFieldDetailForm::class)
317
            ->setItemRequestClass(HistoricalVersionedGridFieldItemRequest::class);
318
319
        $config->getComponentByType(GridFieldDataColumns::class)
320
            ->setDisplayFields([
321
                'Version' => '#',
322
                'RecordStatus' => _t(__CLASS__ . '.Record', 'Record'),
323
                'getAuthor.Name' => _t(__CLASS__ . '.Author', 'Author')
324
            ])
325
            ->setFieldFormatting([
326
                'RecordStatus' => '$VersionedStateNice <span class=\"element-history__date--small\">on $LastEditedNice</span>',
327
            ]);
328
329
        $config->removeComponentsByType(GridFieldViewButton::class);
330
        $config->addComponent(new ElementalGridFieldHistoryButton());
331
332
        $history = Versioned::get_all_versions(__CLASS__, $this->ID)
333
            ->sort('Version', 'DESC');
334
335
        return FieldList::create(
336
            GridField::create('History', '', $history, $config)
0 ignored issues
show
Bug introduced by
$history of type SilverStripe\ORM\DataList 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

336
            GridField::create('History', '', /** @scrutinizer ignore-type */ $history, $config)
Loading history...
Bug introduced by
'History' 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

336
            GridField::create(/** @scrutinizer ignore-type */ 'History', '', $history, $config)
Loading history...
337
                ->addExtraClass('elemental-block__history')
338
        );
339
    }
340
341
    /**
342
     * Get the type of the current block, for use in GridField summaries, block
343
     * type dropdowns etc. Examples are "Content", "File", "Media", etc.
344
     *
345
     * @return string
346
     */
347
    public function getType()
348
    {
349
        return _t(__CLASS__ . '.BlockType', 'Block');
350
    }
351
352
    /**
353
     * @param ElementController
354
     *
355
     * @return $this
356
     */
357
    public function setController($controller)
358
    {
359
        $this->controller = $controller;
360
361
        return $this;
362
    }
363
364
    /**
365
     * @throws Exception
366
     *
367
     * @return ElementController
368
     */
369
    public function getController()
370
    {
371
        if ($this->controller) {
372
            return $this->controller;
373
        }
374
375
        $controllerClass = self::config()->controller_class;
376
377
        if (!class_exists($controllerClass)) {
378
            throw new Exception('Could not find controller class ' . $controllerClass . ' as defined in ' . static::class);
379
        }
380
381
        $this->controller = Injector::inst()->create($controllerClass, $this);
382
        $this->controller->doInit();
383
384
        return $this->controller;
385
    }
386
387
    /**
388
     * @return Controller
389
     */
390
    public function Top()
391
    {
392
        return (Controller::has_curr()) ? Controller::curr() : null;
393
    }
394
395
    /**
396
     * Default way to render element in templates. Note that all blocks should
397
     * be rendered through their {@link ElementController} class as this
398
     * contains the holder styles.
399
     *
400
     * @return string HTML
401
     */
402
    public function forTemplate($holder = true)
0 ignored issues
show
Unused Code introduced by
The parameter $holder 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

402
    public function forTemplate(/** @scrutinizer ignore-unused */ $holder = true)

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...
403
    {
404
        $templates = $this->getRenderTemplates();
405
406
        if ($templates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $templates 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...
407
            return $this->renderWith($templates);
408
        }
409
    }
410
411
    /**
412
     * @param string $suffix
413
     *
414
     * @return array
415
     */
416
    public function getRenderTemplates($suffix = '')
417
    {
418
        $classes = ClassInfo::ancestry($this->ClassName);
419
        $classes[static::class] = static::class;
420
        $classes = array_reverse($classes);
421
        $templates = array();
422
423
        foreach ($classes as $key => $value) {
424
            if ($value == BaseElement::class) {
425
                continue;
426
            }
427
428
            if ($value == DataObject::class) {
429
                break;
430
            }
431
432
            $templates[] = $value . $suffix;
433
        }
434
435
        return $templates;
436
    }
437
438
    /**
439
     * Strip all namespaces from class namespace.
440
     *
441
     * @param string $classname e.g. "\Fully\Namespaced\Class"
442
     *
443
     * @return string following the param example, "Class"
444
     */
445
    protected function stripNamespacing($classname)
446
    {
447
        $classParts = explode('\\', $classname);
448
        return array_pop($classParts);
449
    }
450
451
    /**
452
     * @return string
453
     */
454
    public function getSimpleClassName()
455
    {
456
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
457
    }
458
459
    /**
460
     * @return SiteTree
0 ignored issues
show
Bug introduced by
The type DNADesign\Elemental\Models\SiteTree was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
461
     */
462
    public function getPage()
463
    {
464
        $area = $this->Parent();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

464
        /** @scrutinizer ignore-call */ 
465
        $area = $this->Parent();
Loading history...
465
466
        if ($area instanceof ElementalArea && $area->exists()) {
467
            return $area->getOwnerPage();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $area->getOwnerPage() could also return false which is incompatible with the documented return type DNADesign\Elemental\Models\SiteTree. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
468
        }
469
470
        return null;
471
    }
472
473
    /**
474
     * Get a unique anchor name
475
     *
476
     * @return string
477
     */
478
    public function getAnchor()
479
    {
480
        if ($this->_anchor !== null) {
481
            return $this->_anchor;
482
        }
483
484
        $anchorTitle = '';
485
486
        if (!$this->config()->disable_pretty_anchor_name) {
487
            if ($this->hasMethod('getAnchorTitle')) {
488
                $anchorTitle = $this->getAnchorTitle();
489
            } elseif ($this->config()->enable_title_in_template) {
490
                $anchorTitle = $this->getField('Title');
491
            }
492
        }
493
494
        if (!$anchorTitle) {
495
            $anchorTitle = 'e'.$this->ID;
496
        }
497
498
        $filter = URLSegmentFilter::create();
499
        $titleAsURL = $filter->filter($anchorTitle);
500
501
        // Ensure that this anchor name isn't already in use
502
        // ie. If two elemental blocks have the same title, it'll append '-2', '-3'
503
        $result = $titleAsURL;
504
        $count = 1;
505
        while (isset(self::$_used_anchors[$result]) && self::$_used_anchors[$result] !== $this->ID) {
506
            ++$count;
507
            $result = $titleAsURL.'-'.$count;
508
        }
509
        self::$_used_anchors[$result] = $this->ID;
510
        return $this->_anchor = $result;
511
    }
512
513
    /**
514
     * @param string $action
515
     *
516
     * @return string
517
     */
518
    public function AbsoluteLink($action = null)
519
    {
520
        if ($page = $this->getPage()) {
521
            $link = $page->AbsoluteLink($action) . '#' . $this->getAnchor();
522
523
            return $link;
524
        }
525
    }
526
527
    /**
528
     * @param string $action
529
     *
530
     * @return string
531
     */
532
    public function Link($action = null)
533
    {
534
        if ($page = $this->getPage()) {
535
            $link = $page->Link($action) . '#' . $this->getAnchor();
536
537
            $this->extend('updateLink', $link);
538
539
            return $link;
540
        }
541
    }
542
543
    /**
544
     * @param string $action
545
     *
546
     * @return string
547
     */
548
    public function PreviewLink($action = null)
549
    {
550
        $action = $action . '?ElementalPreview=' . mt_rand();
551
        $link = $this->Link($action);
552
        $this->extend('updatePreviewLink', $link);
553
554
        return $link;
555
    }
556
557
    /**
558
     * @return boolean
559
     */
560
    public function isCMSPreview()
561
    {
562
        if (Controller::has_curr()) {
563
            $c = Controller::curr();
564
565
            if ($c->getRequest()->requestVar('CMSPreview')) {
566
                return true;
567
            }
568
        }
569
570
        return false;
571
    }
572
573
    /**
574
     * @return string
575
     */
576
    public function CMSEditLink()
577
    {
578
        $relationName = $this->getAreaRelationName();
579
        $page = $this->getPage(true);
0 ignored issues
show
Unused Code introduced by
The call to DNADesign\Elemental\Models\BaseElement::getPage() has too many arguments starting with true. ( Ignorable by Annotation )

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

579
        /** @scrutinizer ignore-call */ 
580
        $page = $this->getPage(true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
580
581
        if (!$page) {
582
            return null;
583
        }
584
585
        $link = Controller::join_links(
586
            singleton(CMSPageEditController::class)->Link('EditForm'),
587
            $page->ID,
588
            'field/' . $relationName . '/item/',
589
            $this->ID
590
        );
591
592
        return Controller::join_links(
593
            $link,
594
            'edit'
595
        );
596
    }
597
598
    /**
599
     * Retrieve a elemental area relation for creating cms links
600
     *
601
     * @return string - the name of a valid elemental area relation
602
     */
603
    public function getAreaRelationName()
604
    {
605
        $page = $this->getPage(true);
0 ignored issues
show
Unused Code introduced by
The call to DNADesign\Elemental\Models\BaseElement::getPage() has too many arguments starting with true. ( Ignorable by Annotation )

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

605
        /** @scrutinizer ignore-call */ 
606
        $page = $this->getPage(true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
606
607
        if ($page) {
608
            $has_one = $page->config()->get('has_one');
609
            $area = $this->Parent();
610
611
            foreach ($has_one as $relationName => $relationClass) {
612
                if ($relationClass === $area->ClassName) {
613
                    return $relationName;
614
                }
615
            }
616
        }
617
618
        return 'ElementalArea';
619
    }
620
621
    /**
622
     * Sanitise a model class' name for inclusion in a link.
623
     *
624
     * @return string
625
     */
626
    public function sanitiseClassName($class, $delimiter = '-')
627
    {
628
        return str_replace('\\', $delimiter, $class);
629
    }
630
631
    public function unsanitiseClassName($class, $delimiter = '-')
632
    {
633
        return str_replace($delimiter, '\\', $class);
634
    }
635
636
    /**
637
     * @return string
638
     */
639
    public function getEditLink()
640
    {
641
        return $this->CMSEditLink();
642
    }
643
644
    /**
645
     * @return HTMLText
0 ignored issues
show
Bug introduced by
The type DNADesign\Elemental\Models\HTMLText was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
646
     */
647
    public function PageCMSEditLink()
648
    {
649
        if ($page = $this->getPage()) {
650
            return DBField::create_field('HTMLText', sprintf(
0 ignored issues
show
Bug Best Practice introduced by
The expression return SilverStripe\ORM\...tLink(), $page->Title)) returns the type SilverStripe\ORM\FieldType\DBField which is incompatible with the documented return type DNADesign\Elemental\Models\HTMLText.
Loading history...
651
                '<a href="%s">%s</a>',
652
                $page->CMSEditLink(),
653
                $page->Title
654
            ));
655
        }
656
    }
657
658
    /**
659
     * @return string
660
     */
661
    public function getMimeType()
662
    {
663
        return 'text/html';
664
    }
665
666
    /**
667
     * This can be overridden on child elements to create a summary for display
668
     * in GridFields.
669
     *
670
     * @return string
671
     */
672
    public function getSummary()
673
    {
674
        return '';
675
    }
676
677
678
    /**
679
     * Generate markup for element type icons suitable for use in GridFields.
680
     *
681
     * @return DBField
682
     */
683
    public function getIcon()
684
    {
685
        $icon = $this->config()->get('icon');
686
687
        if ($icon) {
688
            $icon = ModuleResourceLoader::resourceURL($icon);
689
690
            return DBField::create_field('HTMLVarchar', '<img width="16px" src="' . Director::absoluteBaseURL() . $icon . '" alt="" />');
691
        }
692
    }
693
694
    /**
695
     * Generate markup for element type, with description suitable for use in
696
     * GridFields.
697
     *
698
     * @return DBField
699
     */
700
    public function getTypeNice()
701
    {
702
        $description = $this->config()->get('description');
703
        $desc = ($description) ? ' <span class="el-description"> &mdash; ' . $description . '</span>' : '';
704
705
        return DBField::create_field(
706
            'HTMLVarchar',
707
            $this->getType() . $desc
708
        );
709
    }
710
711
    /**
712
     * @return HTMLText
713
     */
714
    public function getEditorPreview()
715
    {
716
        $templates = $this->getRenderTemplates('_EditorPreview');
717
        $templates[] = BaseElement::class . '_EditorPreview';
718
719
        return $this->renderWith($templates);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->renderWith($templates) returns the type SilverStripe\ORM\FieldType\DBHTMLText which is incompatible with the documented return type DNADesign\Elemental\Models\HTMLText.
Loading history...
720
    }
721
722
    /**
723
     * @return Member
724
     */
725
    public function getAuthor()
726
    {
727
        if ($this->AuthorID) {
0 ignored issues
show
Bug Best Practice introduced by
The property AuthorID does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
728
            return Member::get()->byId($this->AuthorID);
729
        }
730
    }
731
732
    /**
733
     * Get a user defined style variant for this element, if available
734
     *
735
     * @return string
736
     */
737
    public function getStyleVariant()
738
    {
739
        $style = $this->Style;
0 ignored issues
show
Bug Best Practice introduced by
The property Style does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
740
        $styles = $this->config()->get('styles');
741
742
        if (isset($styles[$style])) {
743
            $style = strtolower($style);
744
        } else {
745
            $style = '';
746
        }
747
748
        $this->extend('updateStyleVariant', $style);
749
750
        return $style;
751
    }
752
753
    /**
754
     *
755
     */
756
    public function getPageTitle()
757
    {
758
        $page = $this->getPage();
759
760
        if ($page) {
761
            return $page->Title;
762
        }
763
764
        return null;
765
    }
766
767
    /**
768
     * Get a "nice" label for use in the block history GridField
769
     *
770
     * @return string
771
     */
772
    public function getVersionedStateNice()
773
    {
774
        if ($this->WasPublished) {
0 ignored issues
show
Bug Best Practice introduced by
The property WasPublished does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
775
            return _t(__CLASS__ . '.Published', 'Published');
776
        }
777
778
        return _t(__CLASS__ . '.Modified', 'Modified');
779
    }
780
781
    /**
782
     * Return a formatted date for use in the block history GridField
783
     *
784
     * @return string
785
     */
786
    public function getLastEditedNice()
787
    {
788
        return $this->dbObject('LastEdited')->Nice();
789
    }
790
}
791