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

BaseElement::canEdit()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 9
Code Lines 4

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.2
c 0
b 0
f 0
cc 4
eloc 4
nc 5
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\Forms\TextCheckboxGroupField;
9
use DNADesign\Elemental\Controllers\ElementController;
10
use SilverStripe\CMS\Controllers\CMSPageEditController;
11
use SilverStripe\Control\Controller;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Core\ClassInfo;
14
use SilverStripe\Core\Config\Config;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Core\Manifest\ModuleResourceLoader;
17
use SilverStripe\Forms\CheckboxField;
18
use SilverStripe\Forms\DropdownField;
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
                TextCheckboxGroupField::create(
249
                    TextField::create('Title', _t(__CLASS__ . '.TitleLabel', 'Title (not displayed unless specified)')),
250
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
251
                )
252
                    ->setName('TitleAndDisplayed')
253
            );
254
255
            // Rename the "Main" tab
256
            $fields->fieldByName('Root.Main')
257
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
258
259
            // Remove divider lines on all block forms
260
            $fields->fieldByName('Root')->addExtraClass('form--no-dividers');
261
262
            $fields->addFieldsToTab('Root.Main', [
263
                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

263
                HiddenField::create('AbsoluteLink', /** @scrutinizer ignore-type */ false, Director::absoluteURL($this->PreviewLink())),
Loading history...
264
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
265
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
266
            ]);
267
268
            $styles = $this->config()->get('styles');
269
270
            if ($styles && count($styles) > 0) {
271
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
272
273
                $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

273
                $fields->insertBefore($styleDropdown, /** @scrutinizer ignore-type */ 'ExtraClass');
Loading history...
274
275
                $styleDropdown->setEmptyString(_t(__CLASS__.'.CUSTOM_STYLES', 'Select a style..'));
276
            } else {
277
                $fields->removeByName('Style');
278
            }
279
280
            $history = $this->getHistoryFields();
281
282
            if ($history) {
283
                $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

283
                $fields->addFieldsToTab('Root.History', /** @scrutinizer ignore-type */ $history);
Loading history...
284
            }
285
        });
286
287
        return parent::getCMSFields();
288
    }
289
290
    /**
291
     * Returns the history fields for this element.
292
     *
293
     * @param  bool $checkLatestVersion Whether to check if this is the latest version. Prevents recursion, but can be
294
     *                                  overridden to get the history GridField if required.
295
     * @return FieldList
296
     */
297
    public function getHistoryFields($checkLatestVersion = true)
298
    {
299
        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

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

335
            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

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

401
    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...
402
    {
403
        $templates = $this->getRenderTemplates();
404
405
        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...
406
            return $this->renderWith($templates);
407
        }
408
    }
409
410
    /**
411
     * @param string $suffix
412
     *
413
     * @return array
414
     */
415
    public function getRenderTemplates($suffix = '')
416
    {
417
        $classes = ClassInfo::ancestry($this->ClassName);
418
        $classes[static::class] = static::class;
419
        $classes = array_reverse($classes);
420
        $templates = array();
421
422
        foreach ($classes as $key => $value) {
423
            if ($value == BaseElement::class) {
424
                continue;
425
            }
426
427
            if ($value == DataObject::class) {
428
                break;
429
            }
430
431
            $templates[] = $value . $suffix;
432
        }
433
434
        return $templates;
435
    }
436
437
    /**
438
     * Strip all namespaces from class namespace.
439
     *
440
     * @param string $classname e.g. "\Fully\Namespaced\Class"
441
     *
442
     * @return string following the param example, "Class"
443
     */
444
    protected function stripNamespacing($classname)
445
    {
446
        $classParts = explode('\\', $classname);
447
        return array_pop($classParts);
448
    }
449
450
    /**
451
     * @return string
452
     */
453
    public function getSimpleClassName()
454
    {
455
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
456
    }
457
458
    /**
459
     * @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...
460
     */
461
    public function getPage()
462
    {
463
        $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

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

578
        /** @scrutinizer ignore-call */ 
579
        $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...
579
580
        if (!$page) {
581
            return null;
582
        }
583
584
        $link = Controller::join_links(
585
            singleton(CMSPageEditController::class)->Link('EditForm'),
586
            $page->ID,
587
            'field/' . $relationName . '/item/',
588
            $this->ID
589
        );
590
591
        return Controller::join_links(
592
            $link,
593
            'edit'
594
        );
595
    }
596
597
    /**
598
     * Retrieve a elemental area relation for creating cms links
599
     *
600
     * @return string - the name of a valid elemental area relation
601
     */
602
    public function getAreaRelationName()
603
    {
604
        $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

604
        /** @scrutinizer ignore-call */ 
605
        $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...
605
606
        if ($page) {
607
            $has_one = $page->config()->get('has_one');
608
            $area = $this->Parent();
609
610
            foreach ($has_one as $relationName => $relationClass) {
611
                if ($relationClass === $area->ClassName) {
612
                    return $relationName;
613
                }
614
            }
615
        }
616
617
        return 'ElementalArea';
618
    }
619
620
    /**
621
     * Sanitise a model class' name for inclusion in a link.
622
     *
623
     * @return string
624
     */
625
    public function sanitiseClassName($class, $delimiter = '-')
626
    {
627
        return str_replace('\\', $delimiter, $class);
628
    }
629
630
    public function unsanitiseClassName($class, $delimiter = '-')
631
    {
632
        return str_replace($delimiter, '\\', $class);
633
    }
634
635
    /**
636
     * @return string
637
     */
638
    public function getEditLink()
639
    {
640
        return $this->CMSEditLink();
641
    }
642
643
    /**
644
     * @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...
645
     */
646
    public function PageCMSEditLink()
647
    {
648
        if ($page = $this->getPage()) {
649
            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...
650
                '<a href="%s">%s</a>',
651
                $page->CMSEditLink(),
652
                $page->Title
653
            ));
654
        }
655
    }
656
657
    /**
658
     * @return string
659
     */
660
    public function getMimeType()
661
    {
662
        return 'text/html';
663
    }
664
665
    /**
666
     * This can be overridden on child elements to create a summary for display
667
     * in GridFields.
668
     *
669
     * @return string
670
     */
671
    public function getSummary()
672
    {
673
        return '';
674
    }
675
676
677
    /**
678
     * Generate markup for element type icons suitable for use in GridFields.
679
     *
680
     * @return DBField
681
     */
682
    public function getIcon()
683
    {
684
        $icon = $this->config()->get('icon');
685
686
        if ($icon) {
687
            $icon = ModuleResourceLoader::resourceURL($icon);
688
689
            return DBField::create_field('HTMLVarchar', '<img width="16px" src="' . Director::absoluteBaseURL() . $icon . '" alt="" />');
690
        }
691
    }
692
693
    /**
694
     * Generate markup for element type, with description suitable for use in
695
     * GridFields.
696
     *
697
     * @return DBField
698
     */
699
    public function getTypeNice()
700
    {
701
        $description = $this->config()->get('description');
702
        $desc = ($description) ? ' <span class="el-description"> &mdash; ' . $description . '</span>' : '';
703
704
        return DBField::create_field(
705
            'HTMLVarchar',
706
            $this->getType() . $desc
707
        );
708
    }
709
710
    /**
711
     * @return HTMLText
712
     */
713
    public function getEditorPreview()
714
    {
715
        $templates = $this->getRenderTemplates('_EditorPreview');
716
        $templates[] = BaseElement::class . '_EditorPreview';
717
718
        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...
719
    }
720
721
    /**
722
     * @return Member
723
     */
724
    public function getAuthor()
725
    {
726
        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...
727
            return Member::get()->byId($this->AuthorID);
728
        }
729
    }
730
731
    /**
732
     * Get a user defined style variant for this element, if available
733
     *
734
     * @return string
735
     */
736
    public function getStyleVariant()
737
    {
738
        $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...
739
        $styles = $this->config()->get('styles');
740
741
        if (isset($styles[$style])) {
742
            $style = strtolower($style);
743
        } else {
744
            $style = '';
745
        }
746
747
        $this->extend('updateStyleVariant', $style);
748
749
        return $style;
750
    }
751
752
    /**
753
     *
754
     */
755
    public function getPageTitle()
756
    {
757
        $page = $this->getPage();
758
759
        if ($page) {
760
            return $page->Title;
761
        }
762
763
        return null;
764
    }
765
766
    /**
767
     * Get a "nice" label for use in the block history GridField
768
     *
769
     * @return string
770
     */
771
    public function getVersionedStateNice()
772
    {
773
        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...
774
            return _t(__CLASS__ . '.Published', 'Published');
775
        }
776
777
        return _t(__CLASS__ . '.Modified', 'Modified');
778
    }
779
780
    /**
781
     * Return a formatted date for use in the block history GridField
782
     *
783
     * @return string
784
     */
785
    public function getLastEditedNice()
786
    {
787
        return $this->dbObject('LastEdited')->Nice();
788
    }
789
}
790