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

BaseElement::getEditorPreview()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
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\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
    /**
345
     * Proxy through to configuration setting 'inline_editable'
346
     *
347
     * @return bool
348
     */
349
    public function inlineEditable()
350
    {
351
        return static::config()->get('inline_editable');
352
    }
353
354
    /**
355
     * @param ElementController $controller
356
     *
357
     * @return $this
358
     */
359
    public function setController($controller)
360
    {
361
        $this->controller = $controller;
362
363
        return $this;
364
    }
365
366
    /**
367
     * @throws Exception If the specified controller class doesn't exist
368
     *
369
     * @return ElementController
370
     */
371
    public function getController()
372
    {
373
        if ($this->controller) {
374
            return $this->controller;
375
        }
376
377
        $controllerClass = self::config()->controller_class;
378
379
        if (!class_exists($controllerClass)) {
380
            throw new Exception(
381
                'Could not find controller class ' . $controllerClass . ' as defined in ' . static::class
382
            );
383
        }
384
385
        $this->controller = Injector::inst()->create($controllerClass, $this);
386
        $this->controller->doInit();
387
388
        return $this->controller;
389
    }
390
391
    /**
392
     * @return Controller
393
     */
394
    public function Top()
395
    {
396
        return (Controller::has_curr()) ? Controller::curr() : null;
397
    }
398
399
    /**
400
     * Default way to render element in templates. Note that all blocks should
401
     * be rendered through their {@link ElementController} class as this
402
     * contains the holder styles.
403
     *
404
     * @return string|null HTML
405
     */
406
    public function forTemplate($holder = true)
407
    {
408
        $templates = $this->getRenderTemplates();
409
410
        if ($templates) {
411
            return $this->renderWith($templates);
412
        }
413
414
        return null;
415
    }
416
417
    /**
418
     * @param string $suffix
419
     *
420
     * @return array
421
     */
422
    public function getRenderTemplates($suffix = '')
423
    {
424
        $classes = ClassInfo::ancestry($this->ClassName);
425
        $classes[static::class] = static::class;
426
        $classes = array_reverse($classes);
427
        $templates = [];
428
429
        foreach ($classes as $key => $class) {
430
            if ($class == BaseElement::class) {
431
                continue;
432
            }
433
434
            if ($class == DataObject::class) {
435
                break;
436
            }
437
438
            if ($style = $this->Style) {
439
                $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName() . '_' . $style;
440
                $templates[$class][] = $class . $suffix . '_' . $style;
441
            }
442
            $templates[$class][] = $class . $suffix . '_'. $this->getAreaRelationName();
443
            $templates[$class][] = $class . $suffix;
444
        }
445
446
        $this->extend('updateRenderTemplates', $templates, $suffix);
447
448
        $templateFlat = [];
449
        foreach ($templates as $class => $variations) {
450
            $templateFlat = array_merge($templateFlat, $variations);
451
        }
452
453
        return $templateFlat;
454
    }
455
456
    /**
457
     * Strip all namespaces from class namespace.
458
     *
459
     * @param string $classname e.g. "\Fully\Namespaced\Class"
460
     *
461
     * @return string following the param example, "Class"
462
     */
463
    protected function stripNamespacing($classname)
464
    {
465
        $classParts = explode('\\', $classname);
466
        return array_pop($classParts);
467
    }
468
469
    /**
470
     * @return string
471
     */
472
    public function getSimpleClassName()
473
    {
474
        return strtolower($this->sanitiseClassName($this->ClassName, '__'));
475
    }
476
477
    /**
478
     * @return null|DataObject
479
     * @throws \Psr\Container\NotFoundExceptionInterface
480
     * @throws \SilverStripe\ORM\ValidationException
481
     */
482
    public function getPage()
483
    {
484
        $area = $this->Parent();
485
486
        if ($area instanceof ElementalArea && $area->exists()) {
487
            return $area->getOwnerPage();
488
        }
489
490
        return null;
491
    }
492
493
    /**
494
     * Get a unique anchor name
495
     *
496
     * @return string
497
     */
498
    public function getAnchor()
499
    {
500
        if ($this->anchor !== null) {
501
            return $this->anchor;
502
        }
503
504
        $anchorTitle = '';
505
506
        if (!$this->config()->disable_pretty_anchor_name) {
507
            if ($this->hasMethod('getAnchorTitle')) {
508
                $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

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

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

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

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