Completed
Push — master ( 665f94...fb9be7 )
by Robbie
12s
created

BaseElement::onBeforeWrite()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

301
                $fields->insertBefore($styleDropdown, /** @scrutinizer ignore-type */ 'ExtraClass');
Loading history...
302
303
                $styleDropdown->setEmptyString(_t(__CLASS__.'.CUSTOM_STYLES', 'Select a style..'));
304
            } else {
305
                $fields->removeByName('Style');
306
            }
307
308
            // Support for new history viewer in SS 4.2+
309
            if (class_exists(HistoryViewerField::class)) {
310
                Requirements::javascript('dnadesign/silverstripe-elemental:client/dist/js/bundle.js');
311
312
                $historyViewer = HistoryViewerField::create('ElementHistory');
313
                $fields->addFieldToTab('Root.History', $historyViewer);
314
315
                $fields->fieldByName('Root.History')
316
                    ->addExtraClass('elemental-block__history-tab tab--history-viewer');
317
            }
318
        });
319
320
        return parent::getCMSFields();
321
    }
322
323
    /**
324
     * Get the type of the current block, for use in GridField summaries, block
325
     * type dropdowns etc. Examples are "Content", "File", "Media", etc.
326
     *
327
     * @return string
328
     */
329
    public function getType()
330
    {
331
        return _t(__CLASS__ . '.BlockType', 'Block');
332
    }
333
334
    /**
335
     * @param ElementController $controller
336
     *
337
     * @return $this
338
     */
339
    public function setController($controller)
340
    {
341
        $this->controller = $controller;
342
343
        return $this;
344
    }
345
346
    /**
347
     * @throws Exception If the specified controller class doesn't exist
348
     *
349
     * @return ElementController
350
     */
351
    public function getController()
352
    {
353
        if ($this->controller) {
354
            return $this->controller;
355
        }
356
357
        $controllerClass = self::config()->controller_class;
358
359
        if (!class_exists($controllerClass)) {
360
            throw new Exception(
361
                'Could not find controller class ' . $controllerClass . ' as defined in ' . static::class
362
            );
363
        }
364
365
        $this->controller = Injector::inst()->create($controllerClass, $this);
366
        $this->controller->doInit();
367
368
        return $this->controller;
369
    }
370
371
    /**
372
     * @return Controller
373
     */
374
    public function Top()
375
    {
376
        return (Controller::has_curr()) ? Controller::curr() : null;
377
    }
378
379
    /**
380
     * Default way to render element in templates. Note that all blocks should
381
     * be rendered through their {@link ElementController} class as this
382
     * contains the holder styles.
383
     *
384
     * @return string|null HTML
385
     */
386
    public function forTemplate($holder = true)
387
    {
388
        $templates = $this->getRenderTemplates();
389
390
        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...
391
            return $this->renderWith($templates);
392
        }
393
394
        return null;
395
    }
396
397
    /**
398
     * @param string $suffix
399
     *
400
     * @return array
401
     */
402
    public function getRenderTemplates($suffix = '')
403
    {
404
        $classes = ClassInfo::ancestry($this->ClassName);
405
        $classes[static::class] = static::class;
406
        $classes = array_reverse($classes);
407
        $templates = [];
408
409
        foreach ($classes as $key => $class) {
410
            if ($class == BaseElement::class) {
411
                continue;
412
            }
413
414
            if ($class == DataObject::class) {
415
                break;
416
            }
417
418
            if ($style = $this->Style) {
419
                $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName() . '_' . $style;
420
                $templates[$class][] = $class . $suffix . '_' . $style;
421
            }
422
            $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName();
423
            $templates[$class][] = $class . $suffix;
424
        }
425
426
        $this->extend('updateRenderTemplates', $templates, $suffix);
427
428
        $templateFlat = [];
429
        foreach ($templates as $class => $variations) {
430
            $templateFlat = array_merge($templateFlat, $variations);
431
        }
432
433
        return $templateFlat;
434
    }
435
436
    /**
437
     * Strip all namespaces from class namespace.
438
     *
439
     * @param string $classname e.g. "\Fully\Namespaced\Class"
440
     *
441
     * @return string following the param example, "Class"
442
     */
443
    protected function stripNamespacing($classname)
444
    {
445
        $classParts = explode('\\', $classname);
446
        return array_pop($classParts);
447
    }
448
449
    /**
450
     * @return string
451
     */
452
    public function getSimpleClassName()
453
    {
454
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
455
    }
456
457
    /**
458
     * @return null|DataObject
459
     * @throws \Psr\Container\NotFoundExceptionInterface
460
     * @throws \SilverStripe\ORM\ValidationException
461
     */
462
    public function getPage()
463
    {
464
        $area = $this->Parent();
465
466
        if ($area instanceof ElementalArea && $area->exists()) {
467
            return $area->getOwnerPage();
468
        }
469
470
        return null;
471
    }
472
473
    /**
474
     * Get a unique anchor name
475
     *
476
     * @return string
477
     */
478
    public function getAnchor()
479
    {
480
        if ($this->anchor !== null) {
481
            return $this->anchor;
482
        }
483
484
        $anchorTitle = '';
485
486
        if (!$this->config()->disable_pretty_anchor_name) {
487
            if ($this->hasMethod('getAnchorTitle')) {
488
                $anchorTitle = $this->getAnchorTitle();
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

488
                /** @scrutinizer ignore-call */ 
489
                $anchorTitle = $this->getAnchorTitle();
Loading history...
489
            } elseif ($this->config()->enable_title_in_template) {
490
                $anchorTitle = $this->getField('Title');
491
            }
492
        }
493
494
        if (!$anchorTitle) {
495
            $anchorTitle = 'e'.$this->ID;
496
        }
497
498
        $filter = URLSegmentFilter::create();
499
        $titleAsURL = $filter->filter($anchorTitle);
500
501
        // Ensure that this anchor name isn't already in use
502
        // ie. If two elemental blocks have the same title, it'll append '-2', '-3'
503
        $result = $titleAsURL;
504
        $count = 1;
505
        while (isset(self::$used_anchors[$result]) && self::$used_anchors[$result] !== $this->ID) {
506
            ++$count;
507
            $result = $titleAsURL . '-' . $count;
508
        }
509
        self::$used_anchors[$result] = $this->ID;
510
        return $this->anchor = $result;
511
    }
512
513
    /**
514
     * @param string|null $action
515
     * @return string|null
516
     * @throws \Psr\Container\NotFoundExceptionInterface
517
     * @throws \SilverStripe\ORM\ValidationException
518
     */
519
    public function AbsoluteLink($action = null)
520
    {
521
        if ($page = $this->getPage()) {
522
            $link = $page->AbsoluteLink($action) . '#' . $this->getAnchor();
523
524
            return $link;
525
        }
526
527
        return null;
528
    }
529
530
    /**
531
     * @param string|null $action
532
     * @return string
533
     * @throws \Psr\Container\NotFoundExceptionInterface
534
     * @throws \SilverStripe\ORM\ValidationException
535
     */
536
    public function Link($action = null)
537
    {
538
        if ($page = $this->getPage()) {
539
            $link = $page->Link($action) . '#' . $this->getAnchor();
540
541
            $this->extend('updateLink', $link);
542
543
            return $link;
544
        }
545
546
        return null;
547
    }
548
549
    /**
550
     * @param string|null $action
551
     * @return string
552
     * @throws \Psr\Container\NotFoundExceptionInterface
553
     * @throws \SilverStripe\ORM\ValidationException
554
     */
555
    public function PreviewLink($action = null)
556
    {
557
        $action = $action . '?ElementalPreview=' . mt_rand();
558
        $link = $this->Link($action);
559
        $this->extend('updatePreviewLink', $link);
560
561
        return $link;
562
    }
563
564
    /**
565
     * @return boolean
566
     */
567
    public function isCMSPreview()
568
    {
569
        if (Controller::has_curr()) {
570
            $controller = Controller::curr();
571
572
            if ($controller->getRequest()->requestVar('CMSPreview')) {
573
                return true;
574
            }
575
        }
576
577
        return false;
578
    }
579
580
    /**
581
     * @return null|string
582
     * @throws \Psr\Container\NotFoundExceptionInterface
583
     * @throws \SilverStripe\ORM\ValidationException
584
     */
585
    public function CMSEditLink()
586
    {
587
        $relationName = $this->getAreaRelationName();
588
        $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

588
        /** @scrutinizer ignore-call */ 
589
        $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...
589
590
        if (!$page) {
591
            return null;
592
        }
593
594
        $editLinkPrefix = '';
595
        if (!$page instanceof SiteTree && method_exists($page, 'CMSEditLink')) {
596
            $editLinkPrefix = Controller::join_links($page->CMSEditLink(), 'ItemEditForm');
597
        } else {
598
            $editLinkPrefix = Controller::join_links(
599
                singleton(CMSPageEditController::class)->Link('EditForm'),
600
                $page->ID
601
            );
602
        }
603
604
        $link = Controller::join_links(
605
            $editLinkPrefix,
606
            'field/' . $relationName . '/item/',
607
            $this->ID
608
        );
609
610
        $link = Controller::join_links(
611
            $link,
612
            'edit'
613
        );
614
615
        $this->extend('updateCMSEditLink', $link);
616
617
        return $link;
618
    }
619
620
    /**
621
     * Retrieve a elemental area relation for creating cms links
622
     *
623
     * @return int|string The name of a valid elemental area relation
624
     * @throws \Psr\Container\NotFoundExceptionInterface
625
     * @throws \SilverStripe\ORM\ValidationException
626
     */
627
    public function getAreaRelationName()
628
    {
629
        $page = $this->getPage();
630
631
        if ($page) {
632
            $has_one = $page->config()->get('has_one');
633
            $area = $this->Parent();
634
635
            foreach ($has_one as $relationName => $relationClass) {
636
                if ($page instanceof BaseElement && $relationName === 'Parent') {
637
                    continue;
638
                }
639
                if ($relationClass === $area->ClassName && $page->{$relationName}()->ID === $area->ID) {
640
                    return $relationName;
641
                }
642
            }
643
        }
644
645
        return 'ElementalArea';
646
    }
647
648
    /**
649
     * Sanitise a model class' name for inclusion in a link.
650
     *
651
     * @return string
652
     */
653
    public function sanitiseClassName($class, $delimiter = '-')
654
    {
655
        return str_replace('\\', $delimiter, $class);
656
    }
657
658
    public function unsanitiseClassName($class, $delimiter = '-')
659
    {
660
        return str_replace($delimiter, '\\', $class);
661
    }
662
663
    /**
664
     * @return null|string
665
     * @throws \Psr\Container\NotFoundExceptionInterface
666
     * @throws \SilverStripe\ORM\ValidationException
667
     */
668
    public function getEditLink()
669
    {
670
        return $this->CMSEditLink();
671
    }
672
673
    /**
674
     * @return DBField|null
675
     * @throws \Psr\Container\NotFoundExceptionInterface
676
     * @throws \SilverStripe\ORM\ValidationException
677
     */
678
    public function PageCMSEditLink()
679
    {
680
        if ($page = $this->getPage()) {
681
            return DBField::create_field('HTMLText', sprintf(
682
                '<a href="%s">%s</a>',
683
                $page->CMSEditLink(),
684
                $page->Title
685
            ));
686
        }
687
688
        return null;
689
    }
690
691
    /**
692
     * @return string
693
     */
694
    public function getMimeType()
695
    {
696
        return 'text/html';
697
    }
698
699
    /**
700
     * This can be overridden on child elements to create a summary for display
701
     * in GridFields.
702
     *
703
     * @return string
704
     */
705
    public function getSummary()
706
    {
707
        return '';
708
    }
709
710
711
    /**
712
     * The block schema defines a set of data that will be serialised and sent via GraphQL
713
     * to the React editor client.
714
     *
715
     * To modify the schema, either use the extension point or overload the `provideBlockSchema`
716
     * method.
717
     *
718
     * @internal This API may change in future. Treat this as a `final` method.
719
     * @return array
720
     */
721
    public function getBlockSchema()
722
    {
723
        $blockSchema = $this->provideBlockSchema();
724
725
        $this->extend('updateBlockSchema', $blockSchema);
726
727
        return $blockSchema;
728
    }
729
730
    /**
731
     * Provide block schema data, which will be serialised and sent via GraphQL to the editor client.
732
     *
733
     * Overload this method in child element classes to augment, or use the extension point on `getBlockSchema`
734
     * to update it from an `Extension`.
735
     *
736
     * @return array
737
     */
738
    protected function provideBlockSchema()
739
    {
740
        return [
741
            'iconClass' => $this->config()->get('icon'),
742
            'type' => $this->getType(),
743
            'actions' => [
744
                'edit' => $this->getEditLink(),
745
            ],
746
        ];
747
    }
748
749
    /**
750
     * Generate markup for element type icons suitable for use in GridFields.
751
     *
752
     * @return null|DBHTMLText
753
     */
754
    public function getIcon()
755
    {
756
        $data = ArrayData::create([]);
757
758
        $iconClass = $this->config()->get('icon');
759
        if ($iconClass) {
760
            $data->IconClass = $iconClass;
761
762
            // Add versioned states (rendered as a circle over the icon)
763
            if ($this->hasExtension(Versioned::class)) {
764
                $data->IsVersioned = true;
765
                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

765
                if ($this->/** @scrutinizer ignore-call */ isOnDraftOnly()) {
Loading history...
766
                    $data->VersionState = 'draft';
767
                    $data->VersionStateTitle = _t(
768
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.ADDEDTODRAFTHELP',
769
                        'Item has not been published yet'
770
                    );
771
                } 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

771
                } elseif ($this->/** @scrutinizer ignore-call */ isModifiedOnDraft()) {
Loading history...
772
                    $data->VersionState = 'modified';
773
                    $data->VersionStateTitle = $data->VersionStateTitle = _t(
774
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.MODIFIEDONDRAFTHELP',
775
                        'Item has unpublished changes'
776
                    );
777
                }
778
            }
779
780
            return $data->renderWith(__CLASS__ . '/PreviewIcon');
781
        }
782
783
        return null;
784
    }
785
786
    /**
787
     * Get a description for this content element, if available
788
     *
789
     * @return string
790
     */
791
    public function getDescription()
792
    {
793
        $description = $this->config()->uninherited('description');
794
        if ($description) {
795
            return _t(__CLASS__ . '.Description', $description);
796
        }
797
        return '';
798
    }
799
800
    /**
801
     * Generate markup for element type, with description suitable for use in
802
     * GridFields.
803
     *
804
     * @return DBField
805
     */
806
    public function getTypeNice()
807
    {
808
        $description = $this->getDescription();
809
        $desc = ($description) ? ' <span class="element__note"> &mdash; ' . $description . '</span>' : '';
810
811
        return DBField::create_field(
812
            'HTMLVarchar',
813
            $this->getType() . $desc
814
        );
815
    }
816
817
    /**
818
     * @return \SilverStripe\ORM\FieldType\DBHTMLText
819
     */
820
    public function getEditorPreview()
821
    {
822
        $templates = $this->getRenderTemplates('_EditorPreview');
823
        $templates[] = BaseElement::class . '_EditorPreview';
824
825
        return $this->renderWith($templates);
826
    }
827
828
    /**
829
     * @return Member
830
     */
831
    public function getAuthor()
832
    {
833
        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...
834
            return Member::get()->byId($this->AuthorID);
835
        }
836
837
        return null;
838
    }
839
840
    /**
841
     * Get a user defined style variant for this element, if available
842
     *
843
     * @return string
844
     */
845
    public function getStyleVariant()
846
    {
847
        $style = $this->Style;
848
        $styles = $this->config()->get('styles');
849
850
        if (isset($styles[$style])) {
851
            $style = strtolower($style);
852
        } else {
853
            $style = '';
854
        }
855
856
        $this->extend('updateStyleVariant', $style);
857
858
        return $style;
859
    }
860
861
    /**
862
     * @return mixed|null
863
     * @throws \Psr\Container\NotFoundExceptionInterface
864
     * @throws \SilverStripe\ORM\ValidationException
865
     */
866
    public function getPageTitle()
867
    {
868
        $page = $this->getPage();
869
870
        if ($page) {
871
            return $page->Title;
872
        }
873
874
        return null;
875
    }
876
877
    /**
878
     * @return boolean
879
     */
880
    public function First()
881
    {
882
        return ($this->Parent()->Elements()->first()->ID === $this->ID);
883
    }
884
885
    /**
886
     * @return boolean
887
     */
888
    public function Last()
889
    {
890
        return ($this->Parent()->Elements()->last()->ID === $this->ID);
891
    }
892
893
    /**
894
     * @return int
895
     */
896
    public function TotalItems()
897
    {
898
        return $this->Parent()->Elements()->count();
899
    }
900
901
    /**
902
     * Returns the position of the current element.
903
     *
904
     * @return int
905
     */
906
    public function Pos()
907
    {
908
        return ($this->Parent()->Elements()->filter('Sort:LessThan', $this->Sort)->count() + 1);
909
    }
910
911
    /**
912
     * @return string
913
     */
914
    public function EvenOdd()
915
    {
916
        $odd = (bool) ($this->Pos() % 2);
917
918
        return  ($odd) ? 'odd' : 'even';
919
    }
920
}
921