Completed
Push — master ( 1c0100...14fed5 )
by
unknown
16s
created

BaseElement   F

Complexity

Total Complexity 106

Size/Duplication

Total Lines 922
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 297
dl 0
loc 922
rs 2
c 0
b 0
f 0
wmc 106

43 Methods

Rating   Name   Duplication   Size   Complexity  
A Top() 0 3 2
A provideBlockSchema() 0 7 1
A Pos() 0 3 1
A getAuthor() 0 7 2
A sanitiseClassName() 0 3 1
A First() 0 3 1
A getBlockSchema() 0 7 1
A TotalItems() 0 3 1
A getPageTitle() 0 9 2
A getPage() 0 15 4
A getEditorPreview() 0 6 1
A getController() 0 18 3
A unsanitiseClassName() 0 3 1
A getTypeNice() 0 8 2
A canDelete() 0 14 5
A canView() 0 14 5
A getSummary() 0 3 1
A AbsoluteLink() 0 9 2
A getDescription() 0 7 2
A stripNamespacing() 0 4 1
B getAreaRelationName() 0 28 8
A PageCMSEditLink() 0 11 2
A getMimeType() 0 3 1
A CMSEditLink() 0 39 5
A getRenderTemplates() 0 32 6
A canEdit() 0 14 5
A Last() 0 3 1
A getStyleVariant() 0 14 2
A Link() 0 11 2
A PreviewLink() 0 7 1
A getType() 0 3 1
B getAnchor() 0 33 8
A EvenOdd() 0 5 2
A setController() 0 5 1
A getSimpleClassName() 0 3 1
A getCMSFields() 0 58 3
A canCreate() 0 8 3
A isCMSPreview() 0 11 3
A getEditLink() 0 3 1
A forTemplate() 0 9 2
A onBeforeWrite() 0 14 3
A getIcon() 0 30 5
A inlineEditable() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BaseElement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseElement, and based on these observations, apply Extract Interface, too.

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

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

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

811
                if ($this->/** @scrutinizer ignore-call */ isOnDraftOnly()) {
Loading history...
812
                    $data->VersionState = 'draft';
813
                    $data->VersionStateTitle = _t(
814
                        'SilverStripe\\Versioned\\VersionedGridFieldState\\VersionedGridFieldState.ADDEDTODRAFTHELP',
815
                        'Item has not been published yet'
816
                    );
817
                } elseif ($this->isModifiedOnDraft()) {
0 ignored issues
show
Bug introduced by Robbie Averill
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

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