Passed
Push — master ( eb27fd...f091a3 )
by
unknown
02:36
created

BaseElement::setAreaRelationNameCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DNADesign\Elemental\Models;
4
5
use DNADesign\Elemental\Controllers\ElementController;
6
use DNADesign\Elemental\Forms\TextCheckboxGroupField;
7
use DNADesign\Elemental\ORM\FieldType\DBObjectType;
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\NumericField;
20
use SilverStripe\Forms\TextField;
21
use SilverStripe\ORM\DataObject;
22
use SilverStripe\ORM\FieldType\DBBoolean;
23
use SilverStripe\ORM\FieldType\DBField;
24
use SilverStripe\ORM\FieldType\DBHTMLText;
25
use SilverStripe\Security\Member;
26
use SilverStripe\Security\Permission;
27
use SilverStripe\Versioned\Versioned;
28
use SilverStripe\View\ArrayData;
29
use SilverStripe\View\Parsers\URLSegmentFilter;
30
31
/**
32
 * Class BaseElement
33
 * @package DNADesign\Elemental\Models
34
 *
35
 * @property string $Title
36
 * @property bool $ShowTitle
37
 * @property int $Sort
38
 * @property string $ExtraClass
39
 * @property string $Style
40
 *
41
 * @method ElementalArea Parent()
42
 */
43
class BaseElement extends DataObject
44
{
45
    /**
46
     * Override this on your custom elements to specify a CSS icon class
47
     *
48
     * @var string
49
     */
50
    private static $icon = 'font-icon-block-layout';
0 ignored issues
show
introduced by Robbie Averill
The private property $icon is not used, and could be removed.
Loading history...
51
52
    /**
53
     * Describe the purpose of this element
54
     *
55
     * @config
56
     * @var string
57
     */
58
    private static $description = 'Base element class';
0 ignored issues
show
introduced by Robbie Averill
The private property $description is not used, and could be removed.
Loading history...
59
60
    private static $db = [
0 ignored issues
show
introduced by Will Rossiter
The private property $db is not used, and could be removed.
Loading history...
61
        'Title' => 'Varchar(255)',
62
        'ShowTitle' => 'Boolean',
63
        'Sort' => 'Int',
64
        'ExtraClass' => 'Varchar(255)',
65
        'Style' => 'Varchar(255)'
66
    ];
67
68
    private static $has_one = [
0 ignored issues
show
introduced by Will Rossiter
The private property $has_one is not used, and could be removed.
Loading history...
69
        'Parent' => ElementalArea::class
70
    ];
71
72
    private static $extensions = [
0 ignored issues
show
introduced by Will Rossiter
The private property $extensions is not used, and could be removed.
Loading history...
73
        Versioned::class
74
    ];
75
76
    private static $casting = [
0 ignored issues
show
introduced by Guy Marriott
The private property $casting is not used, and could be removed.
Loading history...
77
        'BlockSchema' => DBObjectType::class,
78
        'InlineEditable' => DBBoolean::class,
79
        'IsLiveVersion' => DBBoolean::class,
80
        'IsPublished' => DBBoolean::class,
81
    ];
82
83
    private static $versioned_gridfield_extensions = true;
0 ignored issues
show
introduced by Will Rossiter
The private property $versioned_gridfield_extensions is not used, and could be removed.
Loading history...
84
85
    private static $table_name = 'Element';
0 ignored issues
show
introduced by John Milmine
The private property $table_name is not used, and could be removed.
Loading history...
86
87
    /**
88
     * @var string
89
     */
90
    private static $controller_class = ElementController::class;
91
92
    /**
93
     * @var string
94
     */
95
    private static $controller_template = 'ElementHolder';
0 ignored issues
show
introduced by Will Rossiter
The private property $controller_template is not used, and could be removed.
Loading history...
96
97
    /**
98
     * @var ElementController
99
     */
100
    protected $controller;
101
102
    /**
103
     * Cache various data to improve CMS load time
104
     *
105
     * @internal
106
     * @var array
107
     */
108
    protected $cacheData;
109
110
    private static $default_sort = 'Sort';
0 ignored issues
show
introduced by John Milmine
The private property $default_sort is not used, and could be removed.
Loading history...
111
112
    private static $singular_name = 'block';
0 ignored issues
show
introduced by Robbie Averill
The private property $singular_name is not used, and could be removed.
Loading history...
113
114
    private static $plural_name = 'blocks';
0 ignored issues
show
introduced by Robbie Averill
The private property $plural_name is not used, and could be removed.
Loading history...
115
116
    private static $summary_fields = [
0 ignored issues
show
introduced by Will Rossiter
The private property $summary_fields is not used, and could be removed.
Loading history...
117
        'EditorPreview' => 'Summary'
118
    ];
119
120
    /**
121
     * @config
122
     * @var array
123
     */
124
    private static $styles = [];
0 ignored issues
show
introduced by Will Rossiter
The private property $styles is not used, and could be removed.
Loading history...
125
126
    private static $searchable_fields = [
0 ignored issues
show
introduced by Will Rossiter
The private property $searchable_fields is not used, and could be removed.
Loading history...
127
        'ID' => [
128
            'field' => NumericField::class,
129
        ],
130
        'Title',
131
        'LastEdited'
132
    ];
133
134
    /**
135
     * Enable for backwards compatibility
136
     *
137
     * @var boolean
138
     */
139
    private static $disable_pretty_anchor_name = false;
140
141
    /**
142
     * Set to false to prevent an in-line edit form from showing in an elemental area. Instead the element will be
143
     * clickable and a GridFieldDetailForm will be used.
144
     *
145
     * @config
146
     * @var bool
147
     */
148
    private static $inline_editable = true;
0 ignored issues
show
introduced by Guy Marriott
The private property $inline_editable is not used, and could be removed.
Loading history...
149
150
    /**
151
     * Store used anchor names, this is to avoid title clashes
152
     * when calling 'getAnchor'
153
     *
154
     * @var array
155
     */
156
    protected static $used_anchors = [];
157
158
    /**
159
     * For caching 'getAnchor'
160
     *
161
     * @var string
162
     */
163
    protected $anchor = null;
164
165
    /**
166
     * Basic permissions, defaults to page perms where possible.
167
     *
168
     * @param Member $member
169
     * @return boolean
170
     */
171
    public function canView($member = null)
172
    {
173
        $extended = $this->extendedCan(__FUNCTION__, $member);
174
        if ($extended !== null) {
175
            return $extended;
176
        }
177
178
        if ($this->hasMethod('getPage')) {
179
            if ($page = $this->getPage()) {
180
                return $page->canView($member);
181
            }
182
        }
183
184
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
185
    }
186
187
    /**
188
     * Basic permissions, defaults to page perms where possible.
189
     *
190
     * @param Member $member
191
     *
192
     * @return boolean
193
     */
194
    public function canEdit($member = null)
195
    {
196
        $extended = $this->extendedCan(__FUNCTION__, $member);
197
        if ($extended !== null) {
198
            return $extended;
199
        }
200
201
        if ($this->hasMethod('getPage')) {
202
            if ($page = $this->getPage()) {
203
                return $page->canEdit($member);
204
            }
205
        }
206
207
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
208
    }
209
210
    /**
211
     * Basic permissions, defaults to page perms where possible.
212
     *
213
     * Uses archive not delete so that current stage is respected i.e if a
214
     * element is not published, then it can be deleted by someone who doesn't
215
     * have publishing permissions.
216
     *
217
     * @param Member $member
218
     *
219
     * @return boolean
220
     */
221
    public function canDelete($member = null)
222
    {
223
        $extended = $this->extendedCan(__FUNCTION__, $member);
224
        if ($extended !== null) {
225
            return $extended;
226
        }
227
228
        if ($this->hasMethod('getPage')) {
229
            if ($page = $this->getPage()) {
230
                return $page->canArchive($member);
231
            }
232
        }
233
234
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
235
    }
236
237
    /**
238
     * Basic permissions, defaults to page perms where possible.
239
     *
240
     * @param Member $member
241
     * @param array $context
242
     *
243
     * @return boolean
244
     */
245
    public function canCreate($member = null, $context = array())
246
    {
247
        $extended = $this->extendedCan(__FUNCTION__, $member);
248
        if ($extended !== null) {
249
            return $extended;
250
        }
251
252
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
253
    }
254
255
    /**
256
     * Increment the sort order if one hasn't been already defined. This
257
     * ensures that new elements are created at the end of the list by default.
258
     *
259
     * {@inheritDoc}
260
     */
261
    public function onBeforeWrite()
262
    {
263
        parent::onBeforeWrite();
264
265
        if (!$this->Sort) {
266
            if ($this->hasExtension(Versioned::class)) {
267
                $records = Versioned::get_by_stage(BaseElement::class, Versioned::DRAFT);
268
            } else {
269
                $records = BaseElement::get();
270
            }
271
272
            $records = $records->filter('ParentID', $this->ParentID);
0 ignored issues
show
Bug Best Practice introduced by Will Rossiter
The property ParentID does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
273
274
            $this->Sort = $records->max('Sort') + 1;
275
        }
276
    }
277
278
    public function getCMSFields()
279
    {
280
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
281
            // Remove relationship fields
282
            $fields->removeByName('ParentID');
283
            $fields->removeByName('Sort');
284
285
            // Remove link and file tracking tabs
286
            $fields->removeByName(['LinkTracking', 'FileTracking']);
287
288
            $fields->addFieldToTab(
289
                'Root.Settings',
290
                TextField::create('ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
291
                    ->setAttribute(
292
                        'placeholder',
293
                        _t(__CLASS__ . '.ExtraCssClassesPlaceholder', 'my_class another_class')
294
                    )
295
            );
296
297
            // Add a combined field for "Title" and "Displayed" checkbox in a Bootstrap input group
298
            $fields->removeByName('ShowTitle');
299
            $fields->replaceField(
300
                'Title',
301
                TextCheckboxGroupField::create(
302
                    TextField::create('Title', _t(__CLASS__ . '.TitleLabel', 'Title (displayed if checked)')),
303
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
304
                )
305
                    ->setName('TitleAndDisplayed')
306
            );
307
308
            // Rename the "Main" tab
309
            $fields->fieldByName('Root.Main')
310
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
311
312
            $fields->addFieldsToTab('Root.Main', [
313
                HiddenField::create('AbsoluteLink', false, Director::absoluteURL($this->PreviewLink())),
314
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
315
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
316
            ]);
317
318
            $styles = $this->config()->get('styles');
319
320
            if ($styles && count($styles) > 0) {
321
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
322
323
                $fields->insertBefore($styleDropdown, 'ExtraClass');
0 ignored issues
show
Bug introduced by Will Rossiter
'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

323
                $fields->insertBefore($styleDropdown, /** @scrutinizer ignore-type */ 'ExtraClass');
Loading history...
324
325
                $styleDropdown->setEmptyString(_t(__CLASS__.'.CUSTOM_STYLES', 'Select a style..'));
326
            } else {
327
                $fields->removeByName('Style');
328
            }
329
330
            // Hide the navigation section of the tabs in the React component {@see silverstripe/admin Tabs}
331
            $rootTabset = $fields->fieldByName('Root');
332
            $rootTabset->setSchemaState(['hideNav' => true]);
333
        });
334
335
        return parent::getCMSFields();
336
    }
337
338
    /**
339
     * Get the type of the current block, for use in GridField summaries, block
340
     * type dropdowns etc. Examples are "Content", "File", "Media", etc.
341
     *
342
     * @return string
343
     */
344
    public function getType()
345
    {
346
        return _t(__CLASS__ . '.BlockType', 'Block');
347
    }
348
349
    /**
350
     * Proxy through to configuration setting 'inline_editable'
351
     *
352
     * @return bool
353
     */
354
    public function inlineEditable()
355
    {
356
        return static::config()->get('inline_editable');
357
    }
358
359
    /**
360
     * @param ElementController $controller
361
     *
362
     * @return $this
363
     */
364
    public function setController($controller)
365
    {
366
        $this->controller = $controller;
367
368
        return $this;
369
    }
370
371
    /**
372
     * @throws Exception If the specified controller class doesn't exist
373
     *
374
     * @return ElementController
375
     */
376
    public function getController()
377
    {
378
        if ($this->controller) {
379
            return $this->controller;
380
        }
381
382
        $controllerClass = self::config()->controller_class;
383
384
        if (!class_exists($controllerClass)) {
385
            throw new Exception(
386
                'Could not find controller class ' . $controllerClass . ' as defined in ' . static::class
387
            );
388
        }
389
390
        $this->controller = Injector::inst()->create($controllerClass, $this);
391
        $this->controller->doInit();
392
393
        return $this->controller;
394
    }
395
396
    /**
397
     * @param string $name
398
     * @return $this
399
     */
400
    public function setAreaRelationNameCache($name)
401
    {
402
        $this->cacheData['area_relation_name'] = $name;
403
404
        return $this;
405
    }
406
407
    /**
408
     * @return Controller
409
     */
410
    public function Top()
411
    {
412
        return (Controller::has_curr()) ? Controller::curr() : null;
413
    }
414
415
    /**
416
     * Default way to render element in templates. Note that all blocks should
417
     * be rendered through their {@link ElementController} class as this
418
     * contains the holder styles.
419
     *
420
     * @return string|null HTML
421
     */
422
    public function forTemplate($holder = true)
423
    {
424
        $templates = $this->getRenderTemplates();
425
426
        if ($templates) {
427
            return $this->renderWith($templates);
428
        }
429
430
        return null;
431
    }
432
433
    /**
434
     * @param string $suffix
435
     *
436
     * @return array
437
     */
438
    public function getRenderTemplates($suffix = '')
439
    {
440
        $classes = ClassInfo::ancestry($this->ClassName);
441
        $classes[static::class] = static::class;
442
        $classes = array_reverse($classes);
443
        $templates = [];
444
445
        foreach ($classes as $key => $class) {
446
            if ($class == BaseElement::class) {
447
                continue;
448
            }
449
450
            if ($class == DataObject::class) {
451
                break;
452
            }
453
454
            if ($style = $this->Style) {
455
                $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName() . '_' . $style;
456
                $templates[$class][] = $class . $suffix . '_' . $style;
457
            }
458
            $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName();
459
            $templates[$class][] = $class . $suffix;
460
        }
461
462
        $this->extend('updateRenderTemplates', $templates, $suffix);
463
464
        $templateFlat = [];
465
        foreach ($templates as $class => $variations) {
466
            $templateFlat = array_merge($templateFlat, $variations);
467
        }
468
469
        return $templateFlat;
470
    }
471
472
    /**
473
     * Strip all namespaces from class namespace.
474
     *
475
     * @param string $classname e.g. "\Fully\Namespaced\Class"
476
     *
477
     * @return string following the param example, "Class"
478
     */
479
    protected function stripNamespacing($classname)
480
    {
481
        $classParts = explode('\\', $classname);
482
        return array_pop($classParts);
483
    }
484
485
    /**
486
     * @return string
487
     */
488
    public function getSimpleClassName()
489
    {
490
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
491
    }
492
493
    /**
494
     * @return null|DataObject
495
     * @throws \Psr\Container\NotFoundExceptionInterface
496
     * @throws \SilverStripe\ORM\ValidationException
497
     */
498
    public function getPage()
499
    {
500
        // Allow for repeated calls to be cached
501
        if (isset($this->cacheData['page'])) {
502
            return $this->cacheData['page'];
503
        }
504
505
        $area = $this->Parent();
506
507
        if ($area instanceof ElementalArea && $area->exists()) {
508
            $this->cacheData['page'] = $area->getOwnerPage();
509
            return $this->cacheData['page'];
510
        }
511
512
        return null;
513
    }
514
515
    /**
516
     * Get a unique anchor name
517
     *
518
     * @return string
519
     */
520
    public function getAnchor()
521
    {
522
        if ($this->anchor !== null) {
523
            return $this->anchor;
524
        }
525
526
        $anchorTitle = '';
527
528
        if (!$this->config()->disable_pretty_anchor_name) {
529
            if ($this->hasMethod('getAnchorTitle')) {
530
                $anchorTitle = $this->getAnchorTitle();
0 ignored issues
show
Bug introduced by John Milmine
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

530
                /** @scrutinizer ignore-call */ 
531
                $anchorTitle = $this->getAnchorTitle();
Loading history...
531
            } elseif ($this->config()->enable_title_in_template) {
532
                $anchorTitle = $this->getField('Title');
533
            }
534
        }
535
536
        if (!$anchorTitle) {
537
            $anchorTitle = 'e'.$this->ID;
538
        }
539
540
        $filter = URLSegmentFilter::create();
541
        $titleAsURL = $filter->filter($anchorTitle);
542
543
        // Ensure that this anchor name isn't already in use
544
        // ie. If two elemental blocks have the same title, it'll append '-2', '-3'
545
        $result = $titleAsURL;
546
        $count = 1;
547
        while (isset(self::$used_anchors[$result]) && self::$used_anchors[$result] !== $this->ID) {
548
            ++$count;
549
            $result = $titleAsURL . '-' . $count;
550
        }
551
        self::$used_anchors[$result] = $this->ID;
552
        return $this->anchor = $result;
553
    }
554
555
    /**
556
     * @param string|null $action
557
     * @return string|null
558
     * @throws \Psr\Container\NotFoundExceptionInterface
559
     * @throws \SilverStripe\ORM\ValidationException
560
     */
561
    public function AbsoluteLink($action = null)
562
    {
563
        if ($page = $this->getPage()) {
564
            $link = $page->AbsoluteLink($action) . '#' . $this->getAnchor();
565
566
            return $link;
567
        }
568
569
        return null;
570
    }
571
572
    /**
573
     * @param string|null $action
574
     * @return string
575
     * @throws \Psr\Container\NotFoundExceptionInterface
576
     * @throws \SilverStripe\ORM\ValidationException
577
     */
578
    public function Link($action = null)
579
    {
580
        if ($page = $this->getPage()) {
581
            $link = $page->Link($action) . '#' . $this->getAnchor();
582
583
            $this->extend('updateLink', $link);
584
585
            return $link;
586
        }
587
588
        return null;
589
    }
590
591
    /**
592
     * @param string|null $action
593
     * @return string
594
     * @throws \Psr\Container\NotFoundExceptionInterface
595
     * @throws \SilverStripe\ORM\ValidationException
596
     */
597
    public function PreviewLink($action = null)
598
    {
599
        $action = $action . '?ElementalPreview=' . mt_rand();
600
        $link = $this->Link($action);
601
        $this->extend('updatePreviewLink', $link);
602
603
        return $link;
604
    }
605
606
    /**
607
     * @return boolean
608
     */
609
    public function isCMSPreview()
610
    {
611
        if (Controller::has_curr()) {
612
            $controller = Controller::curr();
613
614
            if ($controller->getRequest()->requestVar('CMSPreview')) {
615
                return true;
616
            }
617
        }
618
619
        return false;
620
    }
621
622
    /**
623
     * @return null|string
624
     * @throws \Psr\Container\NotFoundExceptionInterface
625
     * @throws \SilverStripe\ORM\ValidationException
626
     */
627
    public function CMSEditLink()
628
    {
629
        // Allow for repeated calls to be returned from cache
630
        if (isset($this->cacheData['cms_edit_link'])) {
631
            return $this->cacheData['cms_edit_link'];
632
        }
633
634
        $relationName = $this->getAreaRelationName();
635
        $page = $this->getPage();
636
637
        if (!$page) {
638
            return null;
639
        }
640
641
        $editLinkPrefix = '';
642
        if (!$page instanceof SiteTree && method_exists($page, 'CMSEditLink')) {
643
            $editLinkPrefix = Controller::join_links($page->CMSEditLink(), 'ItemEditForm');
644
        } else {
645
            $editLinkPrefix = Controller::join_links(
646
                singleton(CMSPageEditController::class)->Link('EditForm'),
647
                $page->ID
648
            );
649
        }
650
651
        $link = Controller::join_links(
652
            $editLinkPrefix,
653
            'field/' . $relationName . '/item/',
654
            $this->ID
655
        );
656
657
        $link = Controller::join_links(
658
            $link,
659
            'edit'
660
        );
661
662
        $this->extend('updateCMSEditLink', $link);
663
664
        $this->cacheData['cms_edit_link'] = $link;
665
        return $link;
666
    }
667
668
    /**
669
     * Retrieve a elemental area relation for creating cms links
670
     *
671
     * @return int|string The name of a valid elemental area relation
672
     * @throws \Psr\Container\NotFoundExceptionInterface
673
     * @throws \SilverStripe\ORM\ValidationException
674
     */
675
    public function getAreaRelationName()
676
    {
677
        // Allow repeated calls to return from internal cache
678
        if (isset($this->cacheData['area_relation_name'])) {
679
            return $this->cacheData['area_relation_name'];
680
        }
681
682
        $page = $this->getPage();
683
684
        $result = 'ElementalArea';
685
686
        if ($page) {
687
            $has_one = $page->config()->get('has_one');
688
            $area = $this->Parent();
689
690
            foreach ($has_one as $relationName => $relationClass) {
691
                if ($page instanceof BaseElement && $relationName === 'Parent') {
692
                    continue;
693
                }
694
                if ($relationClass === $area->ClassName && $page->{$relationName}()->ID === $area->ID) {
695
                    $result = $relationName;
696
                    break;
697
                }
698
            }
699
        }
700
701
        $this->setAreaRelationNameCache($result);
702
703
        return $result;
704
    }
705
706
    /**
707
     * Sanitise a model class' name for inclusion in a link.
708
     *
709
     * @return string
710
     */
711
    public function sanitiseClassName($class, $delimiter = '-')
712
    {
713
        return str_replace('\\', $delimiter, $class);
714
    }
715
716
    public function unsanitiseClassName($class, $delimiter = '-')
717
    {
718
        return str_replace($delimiter, '\\', $class);
719
    }
720
721
    /**
722
     * @return null|string
723
     * @throws \Psr\Container\NotFoundExceptionInterface
724
     * @throws \SilverStripe\ORM\ValidationException
725
     */
726
    public function getEditLink()
727
    {
728
        return $this->CMSEditLink();
729
    }
730
731
    /**
732
     * @return DBField|null
733
     * @throws \Psr\Container\NotFoundExceptionInterface
734
     * @throws \SilverStripe\ORM\ValidationException
735
     */
736
    public function PageCMSEditLink()
737
    {
738
        if ($page = $this->getPage()) {
739
            return DBField::create_field('HTMLText', sprintf(
740
                '<a href="%s">%s</a>',
741
                $page->CMSEditLink(),
742
                $page->Title
743
            ));
744
        }
745
746
        return null;
747
    }
748
749
    /**
750
     * @return string
751
     */
752
    public function getMimeType()
753
    {
754
        return 'text/html';
755
    }
756
757
    /**
758
     * This can be overridden on child elements to create a summary for display
759
     * in GridFields.
760
     *
761
     * @return string
762
     */
763
    public function getSummary()
764
    {
765
        return '';
766
    }
767
768
769
    /**
770
     * The block schema defines a set of data that will be serialised and sent via GraphQL
771
     * to the React editor client.
772
     *
773
     * To modify the schema, either use the extension point or overload the `provideBlockSchema`
774
     * method.
775
     *
776
     * @internal This API may change in future. Treat this as a `final` method.
777
     * @return array
778
     */
779
    public function getBlockSchema()
780
    {
781
        $blockSchema = $this->provideBlockSchema();
782
783
        $this->extend('updateBlockSchema', $blockSchema);
784
785
        return $blockSchema;
786
    }
787
788
    /**
789
     * Provide block schema data, which will be serialised and sent via GraphQL to the editor client.
790
     *
791
     * Overload this method in child element classes to augment, or use the extension point on `getBlockSchema`
792
     * to update it from an `Extension`.
793
     *
794
     * @return array
795
     */
796
    protected function provideBlockSchema()
797
    {
798
        return [
799
            'iconClass' => $this->config()->get('icon'),
800
            'type' => $this->getType(),
801
            'actions' => [
802
                'edit' => $this->getEditLink(),
803
            ],
804
        ];
805
    }
806
807
    /**
808
     * Generate markup for element type icons suitable for use in GridFields.
809
     *
810
     * @return null|DBHTMLText
811
     */
812
    public function getIcon()
813
    {
814
        $data = ArrayData::create([]);
815
816
        $iconClass = $this->config()->get('icon');
817
        if ($iconClass) {
818
            $data->IconClass = $iconClass;
819
820
            // Add versioned states (rendered as a circle over the icon)
821
            if ($this->hasExtension(Versioned::class)) {
822
                $data->IsVersioned = true;
823
                if ($this->isOnDraftOnly()) {
0 ignored issues
show
Bug introduced by Robbie Averill
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

823
                if ($this->/** @scrutinizer ignore-call */ isOnDraftOnly()) {
Loading history...
824
                    $data->VersionState = 'draft';
825
                    $data->VersionStateTitle = _t(
826
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.ADDEDTODRAFTHELP',
827
                        'Item has not been published yet'
828
                    );
829
                } elseif ($this->isModifiedOnDraft()) {
0 ignored issues
show
Bug introduced by Robbie Averill
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

829
                } elseif ($this->/** @scrutinizer ignore-call */ isModifiedOnDraft()) {
Loading history...
830
                    $data->VersionState = 'modified';
831
                    $data->VersionStateTitle = $data->VersionStateTitle = _t(
832
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.MODIFIEDONDRAFTHELP',
833
                        'Item has unpublished changes'
834
                    );
835
                }
836
            }
837
838
            return $data->renderWith(__CLASS__ . '/PreviewIcon');
839
        }
840
841
        return null;
842
    }
843
844
    /**
845
     * Get a description for this content element, if available
846
     *
847
     * @return string
848
     */
849
    public function getDescription()
850
    {
851
        $description = $this->config()->uninherited('description');
852
        if ($description) {
853
            return _t(__CLASS__ . '.Description', $description);
854
        }
855
        return '';
856
    }
857
858
    /**
859
     * Generate markup for element type, with description suitable for use in
860
     * GridFields.
861
     *
862
     * @return DBField
863
     */
864
    public function getTypeNice()
865
    {
866
        $description = $this->getDescription();
867
        $desc = ($description) ? ' <span class="element__note"> &mdash; ' . $description . '</span>' : '';
868
869
        return DBField::create_field(
870
            'HTMLVarchar',
871
            $this->getType() . $desc
872
        );
873
    }
874
875
    /**
876
     * @return \SilverStripe\ORM\FieldType\DBHTMLText
877
     */
878
    public function getEditorPreview()
879
    {
880
        $templates = $this->getRenderTemplates('_EditorPreview');
881
        $templates[] = BaseElement::class . '_EditorPreview';
882
883
        return $this->renderWith($templates);
884
    }
885
886
    /**
887
     * @return Member
888
     */
889
    public function getAuthor()
890
    {
891
        if ($this->AuthorID) {
0 ignored issues
show
Bug Best Practice introduced by Will Rossiter
The property AuthorID does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __get, consider adding a @property annotation.
Loading history...
892
            return Member::get()->byId($this->AuthorID);
893
        }
894
895
        return null;
896
    }
897
898
    /**
899
     * Get a user defined style variant for this element, if available
900
     *
901
     * @return string
902
     */
903
    public function getStyleVariant()
904
    {
905
        $style = $this->Style;
906
        $styles = $this->config()->get('styles');
907
908
        if (isset($styles[$style])) {
909
            $style = strtolower($style);
910
        } else {
911
            $style = '';
912
        }
913
914
        $this->extend('updateStyleVariant', $style);
915
916
        return $style;
917
    }
918
919
    /**
920
     * @return mixed|null
921
     * @throws \Psr\Container\NotFoundExceptionInterface
922
     * @throws \SilverStripe\ORM\ValidationException
923
     */
924
    public function getPageTitle()
925
    {
926
        $page = $this->getPage();
927
928
        if ($page) {
929
            return $page->Title;
930
        }
931
932
        return null;
933
    }
934
935
    /**
936
     * @return boolean
937
     */
938
    public function First()
939
    {
940
        return ($this->Parent()->Elements()->first()->ID === $this->ID);
941
    }
942
943
    /**
944
     * @return boolean
945
     */
946
    public function Last()
947
    {
948
        return ($this->Parent()->Elements()->last()->ID === $this->ID);
949
    }
950
951
    /**
952
     * @return int
953
     */
954
    public function TotalItems()
955
    {
956
        return $this->Parent()->Elements()->count();
957
    }
958
959
    /**
960
     * Returns the position of the current element.
961
     *
962
     * @return int
963
     */
964
    public function Pos()
965
    {
966
        return ($this->Parent()->Elements()->filter('Sort:LessThan', $this->Sort)->count() + 1);
967
    }
968
969
    /**
970
     * @return string
971
     */
972
    public function EvenOdd()
973
    {
974
        $odd = (bool) ($this->Pos() % 2);
975
976
        return  ($odd) ? 'odd' : 'even';
977
    }
978
}
979