Passed
Pull Request — master (#346)
by
unknown
04:00 queued 01:29
created

BaseElement::inlineEditable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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
     * @config
138
     * @var bool
139
     */
140
    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...
141
142
    /**
143
     * Store used anchor names, this is to avoid title clashes
144
     * when calling 'getAnchor'
145
     *
146
     * @var array
147
     */
148
    protected static $used_anchors = [];
149
150
    /**
151
     * For caching 'getAnchor'
152
     *
153
     * @var string
154
     */
155
    protected $anchor = null;
156
157
    /**
158
     * Basic permissions, defaults to page perms where possible.
159
     *
160
     * @param Member $member
161
     * @return boolean
162
     */
163
    public function canView($member = null)
164
    {
165
        $extended = $this->extendedCan(__FUNCTION__, $member);
166
        if ($extended !== null) {
167
            return $extended;
168
        }
169
170
        if ($this->hasMethod('getPage')) {
171
            if ($page = $this->getPage()) {
172
                return $page->canView($member);
173
            }
174
        }
175
176
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
177
    }
178
179
    /**
180
     * Basic permissions, defaults to page perms where possible.
181
     *
182
     * @param Member $member
183
     *
184
     * @return boolean
185
     */
186
    public function canEdit($member = null)
187
    {
188
        $extended = $this->extendedCan(__FUNCTION__, $member);
189
        if ($extended !== null) {
190
            return $extended;
191
        }
192
193
        if ($this->hasMethod('getPage')) {
194
            if ($page = $this->getPage()) {
195
                return $page->canEdit($member);
196
            }
197
        }
198
199
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
200
    }
201
202
    /**
203
     * Basic permissions, defaults to page perms where possible.
204
     *
205
     * Uses archive not delete so that current stage is respected i.e if a
206
     * element is not published, then it can be deleted by someone who doesn't
207
     * have publishing permissions.
208
     *
209
     * @param Member $member
210
     *
211
     * @return boolean
212
     */
213
    public function canDelete($member = null)
214
    {
215
        $extended = $this->extendedCan(__FUNCTION__, $member);
216
        if ($extended !== null) {
217
            return $extended;
218
        }
219
220
        if ($this->hasMethod('getPage')) {
221
            if ($page = $this->getPage()) {
222
                return $page->canArchive($member);
223
            }
224
        }
225
226
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
227
    }
228
229
    /**
230
     * Basic permissions, defaults to page perms where possible.
231
     *
232
     * @param Member $member
233
     * @param array $context
234
     *
235
     * @return boolean
236
     */
237
    public function canCreate($member = null, $context = array())
238
    {
239
        $extended = $this->extendedCan(__FUNCTION__, $member);
240
        if ($extended !== null) {
241
            return $extended;
242
        }
243
244
        return (Permission::check('CMS_ACCESS', 'any', $member)) ? true : null;
245
    }
246
247
    /**
248
     * Increment the sort order if one hasn't been already defined. This
249
     * ensures that new elements are created at the end of the list by default.
250
     *
251
     * {@inheritDoc}
252
     */
253
    public function onBeforeWrite()
254
    {
255
        parent::onBeforeWrite();
256
257
        if (!$this->Sort) {
258
            if ($this->hasExtension(Versioned::class)) {
259
                $records = Versioned::get_by_stage(BaseElement::class, Versioned::DRAFT);
260
            } else {
261
                $records = BaseElement::get();
262
            }
263
264
            $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...
265
266
            $this->Sort = $records->max('Sort') + 1;
267
        }
268
    }
269
270
    public function getCMSFields()
271
    {
272
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
273
            // Remove relationship fields
274
            $fields->removeByName('ParentID');
275
            $fields->removeByName('Sort');
276
277
            $fields->addFieldToTab(
278
                'Root.Settings',
279
                TextField::create('ExtraClass', _t(__CLASS__ . '.ExtraCssClassesLabel', 'Custom CSS classes'))
280
                    ->setAttribute(
281
                        'placeholder',
282
                        _t(__CLASS__ . '.ExtraCssClassesPlaceholder', 'my_class another_class')
283
                    )
284
            );
285
286
            // Add a combined field for "Title" and "Displayed" checkbox in a Bootstrap input group
287
            $fields->removeByName('ShowTitle');
288
            $fields->replaceField(
289
                'Title',
290
                TextCheckboxGroupField::create(
291
                    TextField::create('Title', _t(__CLASS__ . '.TitleLabel', 'Title (displayed if checked)')),
292
                    CheckboxField::create('ShowTitle', _t(__CLASS__ . '.ShowTitleLabel', 'Displayed'))
293
                )
294
                    ->setName('TitleAndDisplayed')
295
            );
296
297
            // Rename the "Main" tab
298
            $fields->fieldByName('Root.Main')
299
                ->setTitle(_t(__CLASS__ . '.MainTabLabel', 'Content'));
300
301
            $fields->addFieldsToTab('Root.Main', [
302
                HiddenField::create('AbsoluteLink', false, Director::absoluteURL($this->PreviewLink())),
303
                HiddenField::create('LiveLink', false, Director::absoluteURL($this->Link())),
304
                HiddenField::create('StageLink', false, Director::absoluteURL($this->PreviewLink())),
305
            ]);
306
307
            $styles = $this->config()->get('styles');
308
309
            if ($styles && count($styles) > 0) {
310
                $styleDropdown = DropdownField::create('Style', _t(__CLASS__.'.STYLE', 'Style variation'), $styles);
311
312
                $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

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

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

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

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

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