Passed
Pull Request — master (#346)
by
unknown
02:05
created

BaseElement::getSummary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

780
                if ($this->/** @scrutinizer ignore-call */ isOnDraftOnly()) {
Loading history...
781
                    $data->VersionState = 'draft';
782
                    $data->VersionStateTitle = _t(
783
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.ADDEDTODRAFTHELP',
784
                        'Item has not been published yet'
785
                    );
786
                } 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

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