Completed
Push — master ( ff1c82...da0740 )
by
unknown
12s
created

src/Models/BaseElement.php (22 issues)

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
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
The private property $description is not used, and could be removed.
Loading history...
61
62
    private static $db = [
0 ignored issues
show
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
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
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
The private property $casting is not used, and could be removed.
Loading history...
79
        'BlockSchema' => DBObjectType::class,
80
        'InlineEditable' => DBBoolean::class,
81
        'IsLiveVersion' => DBBoolean::class,
82
        'IsPublished' => DBBoolean::class,
83
    ];
84
85
    private static $versioned_gridfield_extensions = true;
0 ignored issues
show
The private property $versioned_gridfield_extensions is not used, and could be removed.
Loading history...
86
87
    private static $table_name = 'Element';
0 ignored issues
show
The private property $table_name is not used, and could be removed.
Loading history...
88
89
    /**
90
     * @var string
91
     */
92
    private static $controller_class = ElementController::class;
93
94
    /**
95
     * @var string
96
     */
97
    private static $controller_template = 'ElementHolder';
0 ignored issues
show
The private property $controller_template is not used, and could be removed.
Loading history...
98
99
    /**
100
     * @var ElementController
101
     */
102
    protected $controller;
103
104
    /**
105
     * Cache various data to improve CMS load time
106
     *
107
     * @internal
108
     * @var array
109
     */
110
    protected $cacheData;
111
112
    private static $default_sort = 'Sort';
0 ignored issues
show
The private property $default_sort is not used, and could be removed.
Loading history...
113
114
    private static $singular_name = 'block';
0 ignored issues
show
The private property $singular_name is not used, and could be removed.
Loading history...
115
116
    private static $plural_name = 'blocks';
0 ignored issues
show
The private property $plural_name is not used, and could be removed.
Loading history...
117
118
    private static $summary_fields = [
0 ignored issues
show
The private property $summary_fields is not used, and could be removed.
Loading history...
119
        'EditorPreview' => 'Summary'
120
    ];
121
122
    /**
123
     * @config
124
     * @var array
125
     */
126
    private static $styles = [];
0 ignored issues
show
The private property $styles is not used, and could be removed.
Loading history...
127
128
    private static $searchable_fields = [
0 ignored issues
show
The private property $searchable_fields is not used, and could be removed.
Loading history...
129
        'ID' => [
130
            'field' => NumericField::class,
131
        ],
132
        'Title',
133
        'LastEdited'
134
    ];
135
136
    /**
137
     * Enable for backwards compatibility
138
     *
139
     * @var boolean
140
     */
141
    private static $disable_pretty_anchor_name = false;
142
143
    /**
144
     * Set to false to prevent an in-line edit form from showing in an elemental area. Instead the element will be
145
     * clickable and a GridFieldDetailForm will be used.
146
     *
147
     * @config
148
     * @var bool
149
     */
150
    private static $inline_editable = true;
0 ignored issues
show
The private property $inline_editable is not used, and could be removed.
Loading history...
151
152
    /**
153
     * Store used anchor names, this is to avoid title clashes
154
     * when calling 'getAnchor'
155
     *
156
     * @var array
157
     */
158
    protected static $used_anchors = [];
159
160
    /**
161
     * For caching 'getAnchor'
162
     *
163
     * @var string
164
     */
165
    protected $anchor = null;
166
167
    /**
168
     * Basic permissions, defaults to page perms where possible.
169
     *
170
     * @param Member $member
171
     * @return boolean
172
     */
173
    public function canView($member = null)
174
    {
175
        $extended = $this->extendedCan(__FUNCTION__, $member);
176
        if ($extended !== null) {
177
            return $extended;
178
        }
179
180
        if ($this->hasMethod('getPage')) {
181
            if ($page = $this->getPage()) {
182
                return $page->canView($member);
183
            }
184
        }
185
186
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
187
    }
188
189
    /**
190
     * Basic permissions, defaults to page perms where possible.
191
     *
192
     * @param Member $member
193
     *
194
     * @return boolean
195
     */
196
    public function canEdit($member = null)
197
    {
198
        $extended = $this->extendedCan(__FUNCTION__, $member);
199
        if ($extended !== null) {
200
            return $extended;
201
        }
202
203
        if ($this->hasMethod('getPage')) {
204
            if ($page = $this->getPage()) {
205
                return $page->canEdit($member);
206
            }
207
        }
208
209
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
210
    }
211
212
    /**
213
     * Basic permissions, defaults to page perms where possible.
214
     *
215
     * Uses archive not delete so that current stage is respected i.e if a
216
     * element is not published, then it can be deleted by someone who doesn't
217
     * have publishing permissions.
218
     *
219
     * @param Member $member
220
     *
221
     * @return boolean
222
     */
223
    public function canDelete($member = null)
224
    {
225
        $extended = $this->extendedCan(__FUNCTION__, $member);
226
        if ($extended !== null) {
227
            return $extended;
228
        }
229
230
        if ($this->hasMethod('getPage')) {
231
            if ($page = $this->getPage()) {
232
                return $page->canArchive($member);
233
            }
234
        }
235
236
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
237
    }
238
239
    /**
240
     * Basic permissions, defaults to page perms where possible.
241
     *
242
     * @param Member $member
243
     * @param array $context
244
     *
245
     * @return boolean
246
     */
247
    public function canCreate($member = null, $context = array())
248
    {
249
        $extended = $this->extendedCan(__FUNCTION__, $member);
250
        if ($extended !== null) {
251
            return $extended;
252
        }
253
254
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
255
    }
256
257
    /**
258
     * Increment the sort order if one hasn't been already defined. This
259
     * ensures that new elements are created at the end of the list by default.
260
     *
261
     * {@inheritDoc}
262
     */
263
    public function onBeforeWrite()
264
    {
265
        parent::onBeforeWrite();
266
267
        if (!$this->Sort) {
268
            if ($this->hasExtension(Versioned::class)) {
269
                $records = Versioned::get_by_stage(BaseElement::class, Versioned::DRAFT);
270
            } else {
271
                $records = BaseElement::get();
272
            }
273
274
            $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...
275
276
            $this->Sort = $records->max('Sort') + 1;
277
        }
278
    }
279
280
    public function getCMSFields()
281
    {
282
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
283
            // Remove relationship fields
284
            $fields->removeByName('ParentID');
285
            $fields->removeByName('Sort');
286
287
            // Remove link and file tracking tabs
288
            $fields->removeByName(['LinkTracking', 'FileTracking']);
289
290
            $fields->addFieldToTab(
291
                'Root.Settings',
292
                TextField::create('ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
293
                    ->setAttribute(
294
                        'placeholder',
295
                        _t(__CLASS__ . '.ExtraCssClassesPlaceholder', 'my_class another_class')
296
                    )
297
            );
298
299
            // Add a combined field for "Title" and "Displayed" checkbox in a Bootstrap input group
300
            $fields->removeByName('ShowTitle');
301
            $fields->replaceField(
302
                'Title',
303
                TextCheckboxGroupField::create(
304
                    TextField::create('Title', _t(__CLASS__ . '.TitleLabel', 'Title (displayed if checked)')),
305
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
306
                )
307
                    ->setName('TitleAndDisplayed')
308
            );
309
310
            // Rename the "Main" tab
311
            $fields->fieldByName('Root.Main')
312
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
313
314
            $fields->addFieldsToTab('Root.Main', [
315
                HiddenField::create('AbsoluteLink', false, Director::absoluteURL($this->PreviewLink())),
316
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
317
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
318
            ]);
319
320
            $styles = $this->config()->get('styles');
321
322
            if ($styles && count($styles) > 0) {
323
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
324
325
                $fields->insertBefore($styleDropdown, 'ExtraClass');
0 ignored issues
show
'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

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

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

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

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