Passed
Pull Request — master (#288)
by
unknown
02:06
created

BaseElement::getPage()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 0
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DNADesign\Elemental\Models;
4
5
use DNADesign\Elemental\ORM\FieldType\DBObjectType;
6
use DNADesign\Elemental\Controllers\ElementController;
7
use DNADesign\Elemental\Forms\TextCheckboxGroupField;
8
use Exception;
9
use SilverStripe\CMS\Controllers\CMSPageEditController;
10
use SilverStripe\CMS\Model\SiteTree;
11
use SilverStripe\Control\Controller;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Core\ClassInfo;
14
use SilverStripe\Core\Injector\Injector;
15
use SilverStripe\Forms\CheckboxField;
16
use SilverStripe\Forms\DropdownField;
17
use SilverStripe\Forms\FieldList;
18
use SilverStripe\Forms\HiddenField;
19
use SilverStripe\Forms\ListboxField;
20
use SilverStripe\Forms\NumericField;
21
use SilverStripe\Forms\OptionsetField;
22
use SilverStripe\Forms\TextField;
23
use SilverStripe\Forms\TreeMultiselectField;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\FieldType\DBField;
26
use SilverStripe\ORM\FieldType\DBHTMLText;
27
use SilverStripe\Security\Security;
28
use SilverStripe\Security\Group;
29
use SilverStripe\Security\InheritedPermissions;
30
use SilverStripe\Security\InheritedPermissionsExtension;
31
use SilverStripe\Security\Member;
32
use SilverStripe\Security\Permission;
33
use SilverStripe\Versioned\Versioned;
34
use SilverStripe\VersionedAdmin\Forms\HistoryViewerField;
35
use SilverStripe\View\ArrayData;
36
use SilverStripe\View\Parsers\URLSegmentFilter;
37
use SilverStripe\View\Requirements;
38
39
/**
40
 * Class BaseElement
41
 * @package DNADesign\Elemental\Models
42
 *
43
 * @property string $Title
44
 * @property bool $ShowTitle
45
 * @property int $Sort
46
 * @property string $ExtraClass
47
 * @property string $Style
48
 *
49
 * @method ElementalArea Parent()
50
 */
51
class BaseElement extends DataObject
52
{
53
    /**
54
     * Override this on your custom elements to specify a CSS icon class
55
     *
56
     * @var string
57
     */
58
    private static $icon = 'font-icon-block-layout';
0 ignored issues
show
introduced by
The private property $icon is not used, and could be removed.
Loading history...
59
60
    /**
61
     * Describe the purpose of this element
62
     *
63
     * @config
64
     * @var string
65
     */
66
    private static $description = 'Base element class';
0 ignored issues
show
introduced by
The private property $description is not used, and could be removed.
Loading history...
67
68
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
69
        'Title' => 'Varchar(255)',
70
        'ShowTitle' => 'Boolean',
71
        'Sort' => 'Int',
72
        'ExtraClass' => 'Varchar(255)',
73
        'Style' => 'Varchar(255)'
74
    ];
75
76
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
77
        'Parent' => ElementalArea::class
78
    ];
79
80
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
81
        'ViewerGroups' => Group::class,
82
    ];
83
84
    private static $extensions = [
0 ignored issues
show
introduced by
The private property $extensions is not used, and could be removed.
Loading history...
85
        Versioned::class,
86
        InheritedPermissionsExtension::class
87
    ];
88
89
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
90
        'BlockSchema' => DBObjectType::class,
91
    ];
92
93
    private static $versioned_gridfield_extensions = true;
0 ignored issues
show
introduced by
The private property $versioned_gridfield_extensions is not used, and could be removed.
Loading history...
94
95
    private static $table_name = 'Element';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
96
97
    /**
98
     * @var string
99
     */
100
    private static $controller_class = ElementController::class;
101
102
    /**
103
     * @var string
104
     */
105
    private static $controller_template = 'ElementHolder';
0 ignored issues
show
introduced by
The private property $controller_template is not used, and could be removed.
Loading history...
106
107
    /**
108
     * @var ElementController
109
     */
110
    protected $controller;
111
112
    private static $default_sort = 'Sort';
0 ignored issues
show
introduced by
The private property $default_sort is not used, and could be removed.
Loading history...
113
114
    private static $singular_name = 'block';
0 ignored issues
show
introduced by
The private property $singular_name is not used, and could be removed.
Loading history...
115
116
    private static $plural_name = 'blocks';
0 ignored issues
show
introduced by
The private property $plural_name is not used, and could be removed.
Loading history...
117
118
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
119
        'EditorPreview' => 'Summary'
120
    ];
121
122
    /**
123
     * @config
124
     * @var array
125
     */
126
    private static $styles = [];
0 ignored issues
show
introduced by
The private property $styles is not used, and could be removed.
Loading history...
127
128
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
129
        'ID' => [
130
            'field' => NumericField::class,
131
        ],
132
        'Title',
133
        'LastEdited'
134
    ];
135
136
    /**
137
     * Enable for backwards compatibility
138
     *
139
     * @var boolean
140
     */
141
    private static $disable_pretty_anchor_name = false;
142
143
    /**
144
     * Store used anchor names, this is to avoid title clashes
145
     * when calling 'getAnchor'
146
     *
147
     * @var array
148
     */
149
    protected static $used_anchors = [];
150
151
    /**
152
     * For caching 'getAnchor'
153
     *
154
     * @var string
155
     */
156
    protected $anchor = null;
157
158
    /**
159
     * This function should return true if the current user can view this element.
160
     *
161
     * Denies permission if any of the following conditions is true:
162
     * - canView() on any extension returns false
163
     * - "CanViewType" directive is set to "LoggedInUsers" and no user is logged in
164
     * - "CanViewType" directive is set to "OnlyTheseUsers" and user is not in the given groups
165
     *
166
     * @uses DataExtension->canView()
167
     * @uses ViewerGroups()
168
     *
169
     * @param Member $member
170
     * @return bool True if the current user can view this page
171
     */
172
    public function canView($member = null)
173
    {
174
        if (!$member) {
175
            $member = Security::getCurrentUser();
176
        }
177
178
        // Standard mechanism for accepting permission changes from extensions
179
        $extended = $this->extendedCan('canView', $member);
180
        if ($extended !== null) {
181
            return $extended;
182
        }
183
184
        // admin override
185
        if ($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) {
186
            return true;
187
        }
188
189
        // Note: getInheritedPermissions() is disused in this instance
190
        // to allow parent canView extensions to influence canView()
191
192
        // check for empty spec
193
        if (!$this->CanViewType || $this->CanViewType === InheritedPermissions::ANYONE) {
0 ignored issues
show
Bug Best Practice introduced by
The property CanViewType does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
194
            return true;
195
        }
196
197
        // check for any logged-in users
198
        if ($this->CanViewType === InheritedPermissions::LOGGED_IN_USERS && $member && $member->ID) {
199
            return true;
200
        }
201
202
        // check for specific groups
203
        if ($this->CanViewType === InheritedPermissions::ONLY_THESE_USERS
204
            && $member
205
            && $member->inGroups($this->ViewerGroups())
0 ignored issues
show
Bug introduced by
The method ViewerGroups() 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

205
            && $member->inGroups($this->/** @scrutinizer ignore-call */ ViewerGroups())
Loading history...
206
        ) {
207
            return true;
208
        }
209
210
        return false;
211
    }
212
213
    /**
214
     * Basic permissions, defaults to page perms where possible.
215
     *
216
     * @param Member $member
217
     *
218
     * @return boolean
219
     */
220
    public function canEdit($member = null)
221
    {
222
        $extended = $this->extendedCan(__FUNCTION__, $member);
223
        if ($extended !== null) {
224
            return $extended;
225
        }
226
227
        if ($this->hasMethod('getPage')) {
228
            if ($page = $this->getPage()) {
229
                return $page->canEdit($member);
230
            }
231
        }
232
233
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
234
    }
235
236
    /**
237
     * Basic permissions, defaults to page perms where possible.
238
     *
239
     * Uses archive not delete so that current stage is respected i.e if a
240
     * element is not published, then it can be deleted by someone who doesn't
241
     * have publishing permissions.
242
     *
243
     * @param Member $member
244
     *
245
     * @return boolean
246
     */
247
    public function canDelete($member = null)
248
    {
249
        $extended = $this->extendedCan(__FUNCTION__, $member);
250
        if ($extended !== null) {
251
            return $extended;
252
        }
253
254
        if ($this->hasMethod('getPage')) {
255
            if ($page = $this->getPage()) {
256
                return $page->canArchive($member);
257
            }
258
        }
259
260
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
261
    }
262
263
    /**
264
     * Basic permissions, defaults to page perms where possible.
265
     *
266
     * @param Member $member
267
     * @param array $context
268
     *
269
     * @return boolean
270
     */
271
    public function canCreate($member = null, $context = array())
272
    {
273
        $extended = $this->extendedCan(__FUNCTION__, $member);
274
        if ($extended !== null) {
275
            return $extended;
276
        }
277
278
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
279
    }
280
281
    /**
282
     * Increment the sort order if one hasn't been already defined. This ensures that new elements are created
283
     * at the end of the list by default.
284
     *
285
     * {@inheritDoc}
286
     */
287
    public function onBeforeWrite()
288
    {
289
        parent::onBeforeWrite();
290
291
        if (!$this->Sort) {
292
            $this->Sort = static::get()->max('Sort') + 1;
293
        }
294
    }
295
296
    public function getCMSFields()
297
    {
298
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
299
            // Remove relationship fields
300
            $fields->removeByName('ParentID');
301
            $fields->removeByName('Sort');
302
303
            $fields->addFieldToTab(
304
                'Root.Settings',
305
                TextField::create('ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
306
                    ->setAttribute(
307
                        'placeholder',
308
                        _t(__CLASS__ . '.ExtraCssClassesPlaceholder', 'my_class another_class')
309
                    )
310
            );
311
312
            // Add a combined field for "Title" and "Displayed" checkbox in a Bootstrap input group
313
            $fields->removeByName('ShowTitle');
314
            $fields->replaceField(
315
                'Title',
316
                TextCheckboxGroupField::create(
317
                    TextField::create('Title', _t(__CLASS__ . '.TitleLabel', 'Title (displayed if checked)')),
318
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
319
                )
320
                    ->setName('TitleAndDisplayed')
321
            );
322
323
            // Rename the "Main" tab
324
            $fields->fieldByName('Root.Main')
325
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
326
327
            $fields->addFieldsToTab('Root.Main', [
328
                HiddenField::create('AbsoluteLink', false, Director::absoluteURL($this->PreviewLink())),
329
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
330
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
331
            ]);
332
333
            $styles = $this->config()->get('styles');
334
335
            if ($styles && count($styles) > 0) {
336
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
337
338
                $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

338
                $fields->insertBefore($styleDropdown, /** @scrutinizer ignore-type */ 'ExtraClass');
Loading history...
339
340
                $styleDropdown->setEmptyString(_t(__CLASS__.'.CUSTOM_STYLES', 'Select a style..'));
341
            } else {
342
                $fields->removeByName('Style');
343
            }
344
345
            // Support for new history viewer in SS 4.2+
346
            if (class_exists(HistoryViewerField::class)) {
347
                Requirements::javascript('dnadesign/silverstripe-elemental:client/dist/js/bundle.js');
348
349
                $historyViewer = HistoryViewerField::create('ElementHistory');
350
                $fields->addFieldToTab('Root.History', $historyViewer);
351
352
                $fields->fieldByName('Root.History')
353
                    ->addExtraClass('elemental-block__history-tab tab--history-viewer');
354
            }
355
356
            // Viewer groups (remove tabs from silverstripe/asset-admin)
357
            $fields->removeByName('ViewerGroups');
358
            $fields->removeByName('EditorGroups');
359
360
            //group options field
361
            $viewersOptionsField = OptionsetField::create(
362
                "CanViewType",
363
                _t(__CLASS__.'.ACCESSHEADER', "Who can view this page?")
364
            );
365
366
            //group options set
367
            $viewersOptionsSource = [
368
                InheritedPermissions::ANYONE => _t(__CLASS__.'.ACCESSANYONE', "Anyone"),
369
                InheritedPermissions::LOGGED_IN_USERS => _t(__CLASS__.'.ACCESSLOGGEDIN', "Logged-in users"),
370
                InheritedPermissions::ONLY_THESE_USERS => _t(
371
                    __CLASS__.'.ACCESSONLYTHESE',
372
                    "Only these groups (choose from list)"
373
                ),
374
            ];
375
376
            //attach set to field and default to 'Anyone'
377
            $viewersOptionsField->setSource($viewersOptionsSource)->setValue('Anyone');
378
379
            //group drop down for 'Only these groups' in options set 
380
            $viewerGroupsField = TreeMultiselectField::create(
381
                "ViewerGroups",
382
                _t(__class__ . '.VIEWERGROUPS', "Viewer Groups"),
383
                Group::class
384
            );
385
386
            //attach group options and group dropdown
387
            $fields->addFieldsToTab('Root.ViewerGroups', [
388
                $viewersOptionsField,
389
                $viewerGroupsField,
390
            ]);
391
392
        });
393
394
        return parent::getCMSFields();
395
    }
396
397
    /**
398
     * Get the type of the current block, for use in GridField summaries, block
399
     * type dropdowns etc. Examples are "Content", "File", "Media", etc.
400
     *
401
     * @return string
402
     */
403
    public function getType()
404
    {
405
        return _t(__CLASS__ . '.BlockType', 'Block');
406
    }
407
408
    /**
409
     * @param ElementController $controller
410
     *
411
     * @return $this
412
     */
413
    public function setController($controller)
414
    {
415
        $this->controller = $controller;
416
417
        return $this;
418
    }
419
420
    /**
421
     * @throws Exception If the specified controller class doesn't exist
422
     *
423
     * @return ElementController
424
     */
425
    public function getController()
426
    {
427
        if ($this->controller) {
428
            return $this->controller;
429
        }
430
431
        $controllerClass = self::config()->controller_class;
432
433
        if (!class_exists($controllerClass)) {
434
            throw new Exception(
435
                'Could not find controller class ' . $controllerClass . ' as defined in ' . static::class
436
            );
437
        }
438
439
        $this->controller = Injector::inst()->create($controllerClass, $this);
440
        $this->controller->doInit();
441
442
        return $this->controller;
443
    }
444
445
    /**
446
     * @return Controller
447
     */
448
    public function Top()
449
    {
450
        return (Controller::has_curr()) ? Controller::curr() : null;
451
    }
452
453
    /**
454
     * Default way to render element in templates. Note that all blocks should
455
     * be rendered through their {@link ElementController} class as this
456
     * contains the holder styles.
457
     *
458
     * @return string|null HTML
459
     */
460
    public function forTemplate($holder = true)
461
    {
462
        $templates = $this->getRenderTemplates();
463
464
        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...
465
            return $this->renderWith($templates);
466
        }
467
468
        return null;
469
    }
470
471
    /**
472
     * @param string $suffix
473
     *
474
     * @return array
475
     */
476
    public function getRenderTemplates($suffix = '')
477
    {
478
        $classes = ClassInfo::ancestry($this->ClassName);
479
        $classes[static::class] = static::class;
480
        $classes = array_reverse($classes);
481
        $templates = [];
482
483
        foreach ($classes as $key => $class) {
484
            if ($class == BaseElement::class) {
485
                continue;
486
            }
487
488
            if ($class == DataObject::class) {
489
                break;
490
            }
491
492
            if ($style = $this->Style) {
493
                $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName() . '_' . $style;
494
                $templates[$class][] = $class . $suffix . '_' . $style;
495
            }
496
            $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName();
497
            $templates[$class][] = $class . $suffix;
498
        }
499
500
        $this->extend('updateRenderTemplates', $templates, $suffix);
501
502
        $templateFlat = [];
503
        foreach ($templates as $class => $variations) {
504
            $templateFlat = array_merge($templateFlat, $variations);
505
        }
506
507
        return $templateFlat;
508
    }
509
510
    /**
511
     * Strip all namespaces from class namespace.
512
     *
513
     * @param string $classname e.g. "\Fully\Namespaced\Class"
514
     *
515
     * @return string following the param example, "Class"
516
     */
517
    protected function stripNamespacing($classname)
518
    {
519
        $classParts = explode('\\', $classname);
520
        return array_pop($classParts);
521
    }
522
523
    /**
524
     * @return string
525
     */
526
    public function getSimpleClassName()
527
    {
528
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
529
    }
530
531
    /**
532
     * @return null|DataObject
533
     * @throws \Psr\Container\NotFoundExceptionInterface
534
     * @throws \SilverStripe\ORM\ValidationException
535
     */
536
    public function getPage()
537
    {
538
        $area = $this->Parent();
539
540
        if ($area instanceof ElementalArea && $area->exists()) {
541
            return $area->getOwnerPage();
542
        }
543
544
        return null;
545
    }
546
547
    /**
548
     * Get a unique anchor name
549
     *
550
     * @return string
551
     */
552
    public function getAnchor()
553
    {
554
        if ($this->anchor !== null) {
555
            return $this->anchor;
556
        }
557
558
        $anchorTitle = '';
559
560
        if (!$this->config()->disable_pretty_anchor_name) {
561
            if ($this->hasMethod('getAnchorTitle')) {
562
                $anchorTitle = $this->getAnchorTitle();
0 ignored issues
show
Bug introduced by
The method getAnchorTitle() 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

562
                /** @scrutinizer ignore-call */ 
563
                $anchorTitle = $this->getAnchorTitle();
Loading history...
563
            } elseif ($this->config()->enable_title_in_template) {
564
                $anchorTitle = $this->getField('Title');
565
            }
566
        }
567
568
        if (!$anchorTitle) {
569
            $anchorTitle = 'e'.$this->ID;
570
        }
571
572
        $filter = URLSegmentFilter::create();
573
        $titleAsURL = $filter->filter($anchorTitle);
574
575
        // Ensure that this anchor name isn't already in use
576
        // ie. If two elemental blocks have the same title, it'll append '-2', '-3'
577
        $result = $titleAsURL;
578
        $count = 1;
579
        while (isset(self::$used_anchors[$result]) && self::$used_anchors[$result] !== $this->ID) {
580
            ++$count;
581
            $result = $titleAsURL . '-' . $count;
582
        }
583
        self::$used_anchors[$result] = $this->ID;
584
        return $this->anchor = $result;
585
    }
586
587
    /**
588
     * @param string|null $action
589
     * @return string|null
590
     * @throws \Psr\Container\NotFoundExceptionInterface
591
     * @throws \SilverStripe\ORM\ValidationException
592
     */
593
    public function AbsoluteLink($action = null)
594
    {
595
        if ($page = $this->getPage()) {
596
            $link = $page->AbsoluteLink($action) . '#' . $this->getAnchor();
597
598
            return $link;
599
        }
600
601
        return null;
602
    }
603
604
    /**
605
     * @param string|null $action
606
     * @return string
607
     * @throws \Psr\Container\NotFoundExceptionInterface
608
     * @throws \SilverStripe\ORM\ValidationException
609
     */
610
    public function Link($action = null)
611
    {
612
        if ($page = $this->getPage()) {
613
            $link = $page->Link($action) . '#' . $this->getAnchor();
614
615
            $this->extend('updateLink', $link);
616
617
            return $link;
618
        }
619
620
        return null;
621
    }
622
623
    /**
624
     * @param string|null $action
625
     * @return string
626
     * @throws \Psr\Container\NotFoundExceptionInterface
627
     * @throws \SilverStripe\ORM\ValidationException
628
     */
629
    public function PreviewLink($action = null)
630
    {
631
        $action = $action . '?ElementalPreview=' . mt_rand();
632
        $link = $this->Link($action);
633
        $this->extend('updatePreviewLink', $link);
634
635
        return $link;
636
    }
637
638
    /**
639
     * @return boolean
640
     */
641
    public function isCMSPreview()
642
    {
643
        if (Controller::has_curr()) {
644
            $controller = Controller::curr();
645
646
            if ($controller->getRequest()->requestVar('CMSPreview')) {
647
                return true;
648
            }
649
        }
650
651
        return false;
652
    }
653
654
    /**
655
     * @return null|string
656
     * @throws \Psr\Container\NotFoundExceptionInterface
657
     * @throws \SilverStripe\ORM\ValidationException
658
     */
659
    public function CMSEditLink()
660
    {
661
        $relationName = $this->getAreaRelationName();
662
        $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

662
        /** @scrutinizer ignore-call */ 
663
        $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...
663
664
        if (!$page) {
665
            return null;
666
        }
667
668
        $editLinkPrefix = '';
669
        if (!$page instanceof SiteTree && method_exists($page, 'CMSEditLink')) {
670
            $editLinkPrefix = Controller::join_links($page->CMSEditLink(), 'ItemEditForm');
671
        } else {
672
            $editLinkPrefix = Controller::join_links(
673
                singleton(CMSPageEditController::class)->Link('EditForm'),
674
                $page->ID
675
            );
676
        }
677
678
        $link = Controller::join_links(
679
            $editLinkPrefix,
680
            'field/' . $relationName . '/item/',
681
            $this->ID
682
        );
683
684
        $link = Controller::join_links(
685
            $link,
686
            'edit'
687
        );
688
689
        $this->extend('updateCMSEditLink', $link);
690
691
        return $link;
692
    }
693
694
    /**
695
     * Retrieve a elemental area relation for creating cms links
696
     *
697
     * @return int|string The name of a valid elemental area relation
698
     * @throws \Psr\Container\NotFoundExceptionInterface
699
     * @throws \SilverStripe\ORM\ValidationException
700
     */
701
    public function getAreaRelationName()
702
    {
703
        $page = $this->getPage();
704
705
        if ($page) {
706
            $has_one = $page->config()->get('has_one');
707
            $area = $this->Parent();
708
709
            foreach ($has_one as $relationName => $relationClass) {
710
                if ($page instanceof BaseElement && $relationName === 'Parent') {
711
                    continue;
712
                }
713
                if ($relationClass === $area->ClassName && $page->{$relationName}()->ID === $area->ID) {
714
                    return $relationName;
715
                }
716
            }
717
        }
718
719
        return 'ElementalArea';
720
    }
721
722
    /**
723
     * Sanitise a model class' name for inclusion in a link.
724
     *
725
     * @return string
726
     */
727
    public function sanitiseClassName($class, $delimiter = '-')
728
    {
729
        return str_replace('\\', $delimiter, $class);
730
    }
731
732
    public function unsanitiseClassName($class, $delimiter = '-')
733
    {
734
        return str_replace($delimiter, '\\', $class);
735
    }
736
737
    /**
738
     * @return null|string
739
     * @throws \Psr\Container\NotFoundExceptionInterface
740
     * @throws \SilverStripe\ORM\ValidationException
741
     */
742
    public function getEditLink()
743
    {
744
        return $this->CMSEditLink();
745
    }
746
747
    /**
748
     * @return DBField|null
749
     * @throws \Psr\Container\NotFoundExceptionInterface
750
     * @throws \SilverStripe\ORM\ValidationException
751
     */
752
    public function PageCMSEditLink()
753
    {
754
        if ($page = $this->getPage()) {
755
            return DBField::create_field('HTMLText', sprintf(
756
                '<a href="%s">%s</a>',
757
                $page->CMSEditLink(),
758
                $page->Title
759
            ));
760
        }
761
762
        return null;
763
    }
764
765
    /**
766
     * @return string
767
     */
768
    public function getMimeType()
769
    {
770
        return 'text/html';
771
    }
772
773
    /**
774
     * This can be overridden on child elements to create a summary for display
775
     * in GridFields.
776
     *
777
     * @return string
778
     */
779
    public function getSummary()
780
    {
781
        return '';
782
    }
783
784
785
    /**
786
     * @return array
787
     */
788
    public function getBlockSchema()
789
    {
790
        $blockSchema = [
791
            'iconClass' => $this->config()->get('icon'),
792
            'type' => $this->getType(),
793
        ];
794
795
        return $blockSchema;
796
    }
797
798
    /**
799
     * Generate markup for element type icons suitable for use in GridFields.
800
     *
801
     * @return null|DBHTMLText
802
     */
803
    public function getIcon()
804
    {
805
        $data = ArrayData::create([]);
806
807
        $iconClass = $this->config()->get('icon');
808
        if ($iconClass) {
809
            $data->IconClass = $iconClass;
810
811
            // Add versioned states (rendered as a circle over the icon)
812
            if ($this->hasExtension(Versioned::class)) {
813
                $data->IsVersioned = true;
814
                if ($this->isOnDraftOnly()) {
0 ignored issues
show
Bug introduced by
The method isOnDraftOnly() 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

814
                if ($this->/** @scrutinizer ignore-call */ isOnDraftOnly()) {
Loading history...
815
                    $data->VersionState = 'draft';
816
                    $data->VersionStateTitle = _t(
817
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.ADDEDTODRAFTHELP',
818
                        'Item has not been published yet'
819
                    );
820
                } elseif ($this->isModifiedOnDraft()) {
0 ignored issues
show
Bug introduced by
The method isModifiedOnDraft() 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

820
                } elseif ($this->/** @scrutinizer ignore-call */ isModifiedOnDraft()) {
Loading history...
821
                    $data->VersionState = 'modified';
822
                    $data->VersionStateTitle = $data->VersionStateTitle = _t(
823
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.MODIFIEDONDRAFTHELP',
824
                        'Item has unpublished changes'
825
                    );
826
                }
827
            }
828
829
            return $data->renderWith(__CLASS__ . '/PreviewIcon');
830
        }
831
832
        return null;
833
    }
834
835
    /**
836
     * Get a description for this content element, if available
837
     *
838
     * @return string
839
     */
840
    public function getDescription()
841
    {
842
        $description = $this->config()->uninherited('description');
843
        if ($description) {
844
            return _t(__CLASS__ . '.Description', $description);
845
        }
846
        return '';
847
    }
848
849
    /**
850
     * Generate markup for element type, with description suitable for use in
851
     * GridFields.
852
     *
853
     * @return DBField
854
     */
855
    public function getTypeNice()
856
    {
857
        $description = $this->getDescription();
858
        $desc = ($description) ? ' <span class="element__note"> &mdash; ' . $description . '</span>' : '';
859
860
        return DBField::create_field(
861
            'HTMLVarchar',
862
            $this->getType() . $desc
863
        );
864
    }
865
866
    /**
867
     * @return \SilverStripe\ORM\FieldType\DBHTMLText
868
     */
869
    public function getEditorPreview()
870
    {
871
        $templates = $this->getRenderTemplates('_EditorPreview');
872
        $templates[] = BaseElement::class . '_EditorPreview';
873
874
        return $this->renderWith($templates);
875
    }
876
877
    /**
878
     * @return Member
879
     */
880
    public function getAuthor()
881
    {
882
        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...
883
            return Member::get()->byId($this->AuthorID);
884
        }
885
886
        return null;
887
    }
888
889
    /**
890
     * Get a user defined style variant for this element, if available
891
     *
892
     * @return string
893
     */
894
    public function getStyleVariant()
895
    {
896
        $style = $this->Style;
897
        $styles = $this->config()->get('styles');
898
899
        if (isset($styles[$style])) {
900
            $style = strtolower($style);
901
        } else {
902
            $style = '';
903
        }
904
905
        $this->extend('updateStyleVariant', $style);
906
907
        return $style;
908
    }
909
910
    /**
911
     * @return mixed|null
912
     * @throws \Psr\Container\NotFoundExceptionInterface
913
     * @throws \SilverStripe\ORM\ValidationException
914
     */
915
    public function getPageTitle()
916
    {
917
        $page = $this->getPage();
918
919
        if ($page) {
920
            return $page->Title;
921
        }
922
923
        return null;
924
    }
925
}
926