Passed
Push — master ( 4f9355...71496c )
by Thomas
03:21 queued 01:15
created

Block::hasField()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
namespace LeKoala\Blocks;
4
5
use SilverStripe\Forms\Tab;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Assets\Image;
8
use SilverStripe\Forms\TabSet;
9
use SilverStripe\ORM\DataList;
10
use SilverStripe\ORM\ArrayList;
11
use SilverStripe\View\SSViewer;
12
use SilverStripe\Core\ClassInfo;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\View\ArrayData;
15
use LeKoala\Blocks\FieldType\DBJson;
16
use SilverStripe\Forms\TextField;
17
use SilverStripe\Control\Director;
18
use SilverStripe\Admin\LeftAndMain;
19
use SilverStripe\Forms\HiddenField;
20
use SilverStripe\Control\Controller;
21
use SilverStripe\Core\Config\Config;
22
use SilverStripe\Forms\LiteralField;
23
use SilverStripe\Forms\DropdownField;
24
use SilverStripe\Versioned\Versioned;
25
use LeKoala\Blocks\Types\ContentBlock;
26
use SilverStripe\ORM\FieldType\DBHTMLText;
27
use SilverStripe\Forms\GridField\GridField;
28
use SilverStripe\AssetAdmin\Forms\UploadField;
29
use SilverStripe\View\Parsers\URLSegmentFilter;
30
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
31
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
32
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
33
34
/**
35
 * The block dataobject is used to actually store the data
36
 *
37
 * @property int $Sort
38
 * @property string $Type
39
 * @property string $MenuTitle
40
 * @property string $HTMLID
41
 * @property string $Content
42
 * @property string $BlockData
43
 * @property string $Settings
44
 * @property int $ImageID
45
 * @property int $PageID
46
 * @method \SilverStripe\Assets\Image Image()
47
 * @method \LeKoala\Blocks\BlocksPage Page()
48
 * @method \SilverStripe\ORM\ManyManyList|\SilverStripe\Assets\Image[] Images()
49
 * @method \SilverStripe\ORM\ManyManyList|\SilverStripe\Assets\File[] Files()
50
 * @mixin \LeKoala\Extensions\SmartDataObjectExtension
51
 */
52
final class Block extends DataObject
53
{
54
    // constants
55
    const ITEMS_KEY = 'Items';
56
    const DATA_KEY = 'BlockData';
57
    const SETTINGS_KEY = 'Settings';
58
59
    // When using namespace, specify table name
60
    private static $table_name = 'Block';
0 ignored issues
show
introduced by
The private property $table_name 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
        'Type' => 'Varchar(59)',
64
        'MenuTitle' => 'Varchar(191)',
65
        'HTMLID' => 'Varchar(59)',
66
        'Content' => 'HTMLText', // The rendered content
67
        // Localized data
68
        'BlockData' => DBJson::class,
69
        // Unlocalized data
70
        'Settings' => DBJson::class,
71
        "Sort" => "Int",
72
    ];
73
74
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
75
        "Image" => Image::class,
76
        "Page" => BlocksPage::class
77
    ];
78
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
79
        "Images" => Image::class,
80
        "Files" => File::class,
81
    ];
82
    private static $many_many_extraFields = [
0 ignored issues
show
introduced by
The private property $many_many_extraFields is not used, and could be removed.
Loading history...
83
        'Images' => ['SortOrder' => 'Int'],
84
        'Files' => ['SortOrder' => 'Int'],
85
    ];
86
    private static $cascade_deletes = [
0 ignored issues
show
introduced by
The private property $cascade_deletes is not used, and could be removed.
Loading history...
87
        'Image', 'Images', 'Files'
88
    ];
89
    private static $owns = [
0 ignored issues
show
introduced by
The private property $owns is not used, and could be removed.
Loading history...
90
        'Image', "Images", "Files",
91
    ];
92
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
93
        'BlockType' => 'Block Type',
94
        'MenuTitle' => 'Menu Title',
95
        'Summary' => 'Summary',
96
    ];
97
    private static $translate = [
0 ignored issues
show
introduced by
The private property $translate is not used, and could be removed.
Loading history...
98
        "MenuTitle", "Content", "AuditData", "BlockData"
99
    ];
100
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
101
        'Type' => ContentBlock::class,
102
    ];
103
104
    private static $default_sort = 'Sort ASC';
0 ignored issues
show
introduced by
The private property $default_sort is not used, and could be removed.
Loading history...
105
106
    /**
107
     * Should we update the page after block update
108
     * Turn this off when updating all blocks of a page
109
     * otherwise it's really slow
110
     *
111
     * @var boolean
112
     */
113
    public static $auto_update_page = true;
114
115
    public function forTemplate()
116
    {
117
        return $this->Content;
118
    }
119
120
    public function getTitle()
121
    {
122
        $type = $this->BlockType();
123
        if ($this->ID) {
124
            return $type;
125
        }
126
        return 'New ' . $type;
127
    }
128
129
    /**
130
     * Each block type can have one "collection" of items
131
     *
132
     * The collection is filtered according to the blocs id
133
     * (a relation must be set on the related class)
134
     *
135
     * @return DataList
136
     */
137
    public function Collection()
138
    {
139
        $inst = $this->getTypeInstance();
140
        $list = $inst->Collection();
141
        if ($list) {
0 ignored issues
show
introduced by
$list is of type LeKoala\Blocks\DataList, thus it always evaluated to true.
Loading history...
142
            return $list->filter('BlockID', $this->ID);
143
        }
144
    }
145
146
    /**
147
     * Each block type can have one shared "collection" of items
148
     *
149
     * The shared collection is common across all blocks of the same type
150
     *
151
     * @return DataList
152
     */
153
    public function SharedCollection()
154
    {
155
        $inst = $this->getTypeInstance();
156
        return $inst->SharedCollection();
157
    }
158
159
    /**
160
     * Get sorted images
161
     *
162
     * @return \SilverStripe\ORM\ManyManyList
163
     */
164
    public function SortedImages()
165
    {
166
        return $this->Images()->Sort('SortOrder');
167
    }
168
169
    /**
170
     * Get sorted files
171
     *
172
     * @return \SilverStripe\ORM\ManyManyList
173
     */
174
    public function SortedFiles()
175
    {
176
        return $this->Files()->Sort('SortOrder');
177
    }
178
179
    /**
180
     * This is called onBeforeWrite and renders content
181
     * in order to store it in Content variable
182
     *
183
     * @return string
184
     */
185
    public function renderWithTemplate()
186
    {
187
        $template = 'Blocks/' . $this->BlockClass();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->BlockClass() targeting LeKoala\Blocks\Block::BlockClass() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure $this->BlockClass() of type void can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
        $template = 'Blocks/' . /** @scrutinizer ignore-type */ $this->BlockClass();
Loading history...
188
189
        // turn off source_file_comments
190
        // keep in mind that any cached template will have the source_file_comments if
191
        // it was not disabled, regardless of this setting so it may be confusing
192
        Config::nest();
193
        Config::modify()->set(SSViewer::class, 'source_file_comments', false);
194
195
        // Make sure theme exists in the list (not set in cms)
196
        $themes = SSViewer::get_themes();
197
        $configThemes = SSViewer::config()->themes;
198
        SSViewer::set_themes($configThemes);
199
        $chosenTemplate = SSViewer::chooseTemplate($template);
200
        // Render template
201
        $result = null;
202
        if ($chosenTemplate) {
203
            $typeInst = $this->getTypeInstance();
204
            $data = $this->DataArray();
205
            $settings = $this->SettingsArray();
206
            $extra = $typeInst->ExtraData();
207
            $data = array_merge($data, $settings, $extra);
208
            // We have items to normalize
209
            if (isset($data[self::ITEMS_KEY])) {
210
                $data[self::ITEMS_KEY] = self::normalizeIndexedList($data[self::ITEMS_KEY]);
211
            }
212
            // Somehow, data is not nested properly if not wrapped beforehand with ArrayData
213
            $arrayData = new ArrayData($data);
214
            // Maybe we need to disable hash rewriting
215
            if ($typeInst->hasMethod('disableAnchorRewriting')) {
216
                SSViewer::setRewriteHashLinksDefault($typeInst->disableAnchorRewriting());
0 ignored issues
show
Bug introduced by
The method disableAnchorRewriting() does not exist on LeKoala\Blocks\Types\ContentBlock. 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

216
                SSViewer::setRewriteHashLinksDefault($typeInst->/** @scrutinizer ignore-call */ disableAnchorRewriting());
Loading history...
217
            }
218
            $result = (string) $typeInst->renderWith($template, $arrayData);
0 ignored issues
show
Bug introduced by
$arrayData of type SilverStripe\View\ArrayData is incompatible with the type array expected by parameter $customFields of SilverStripe\View\ViewableData::renderWith(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
            $result = (string) $typeInst->renderWith($template, /** @scrutinizer ignore-type */ $arrayData);
Loading history...
219
            SSViewer::setRewriteHashLinksDefault(true);
220
        }
221
        // Restore themes just in case to prevent any side effect
222
        SSViewer::set_themes($themes);
223
224
        // Restore flag
225
        Config::unnest();
226
227
        return $result;
228
    }
229
230
    /**
231
     * Class helper to use in your templates
232
     *
233
     * @param string $name
234
     * @return string
235
     */
236
    public function Cls($name)
237
    {
238
        return 'Block-' . $this->BlockType() . '-' . $name;
239
    }
240
241
    /**
242
     * Convert an indexed array to an ArrayList
243
     * This allows loops, etc in the template
244
     *
245
     * @param array $indexedList
246
     * @return ArrayList
247
     */
248
    protected static function normalizeIndexedList($indexedList)
249
    {
250
        // this is required to add a global counter that works if multiple blocks  of the same type are used
251
        static $counter = 0;
252
        $list = new ArrayList();
253
        $i = 0;
254
255
        // remove empty items
256
        foreach ($indexedList as $index => $item) {
257
            $vals = array_values($item);
258
            $vals = array_filter($vals);
259
            if (empty($vals)) {
260
                unset($indexedList[$index]);
261
            }
262
        }
263
264
        $c = count($indexedList);
265
        foreach ($indexedList as $index => $item) {
266
            $i++;
267
            $counter++;
268
            // Add standard iterator stuff
269
            $FirstLast = '';
270
            if ($i === 1) {
271
                $FirstLast = 'first';
272
            } elseif ($i === $c) {
273
                $FirstLast = 'last';
274
            }
275
            $item['Pos'] = $index;
276
            $item['Total'] = $c;
277
            $item['Columns'] = 12 / $c;
278
            $item['Counter'] = $counter;
279
            $item['FirstLast'] = $FirstLast;
280
            $item['EvenOdd'] = $i % 2 ? 'even' : 'odd';
281
            foreach ($item as $k => $v) {
282
                // Handle files (stored in json as ["Files" => [ID]])
283
                if (is_array($v) && !empty($v['Files'])) {
284
                    $files = $v['Files'];
285
                    if (count($files) == 1) {
286
                        // Remove ID from end of string
287
                        // eg: if you added [$k, 'ImageID'] it should be accessible using "Image", not "ImageID"
288
                        $lastChars = substr($k, strlen($k) - 2, 2);
289
                        if ($lastChars == 'ID') {
290
                            $k = substr($k, 0, -2);
291
                        }
292
                        $item[$k] = self::getPublishedFileByID($files[0]);
293
                    } else {
294
                        $imageList = new ArrayList();
295
                        foreach ($files as $fileID) {
296
                            $imageList->push(self::getPublishedFileByID($fileID));
297
                        }
298
                        $item[$k] = $imageList;
299
                    }
300
                }
301
            }
302
            $list->push($item);
303
        }
304
        return $list;
305
    }
306
307
    /**
308
     * Template helper to access images
309
     *
310
     * @param string|int $ID
311
     * @return Image
312
     */
313
    public function ImageByID($ID)
314
    {
315
        if (!is_numeric($ID)) {
316
            $ID = $this->DataArray()[$ID]['Files'][0];
317
        }
318
        $Image = self::getPublishedImageByID($ID);
319
        if (!$Image) {
0 ignored issues
show
introduced by
$Image is of type SilverStripe\Assets\Image, thus it always evaluated to true.
Loading history...
320
            return false;
321
        }
322
        return $Image;
323
    }
324
325
    /**
326
     * Make sure the image is published for the block
327
     *
328
     * @param int $ID
329
     * @return Image
330
     */
331
    public static function getPublishedImageByID($ID)
332
    {
333
        if (class_exists(Versioned::class)) {
334
            $image = Versioned::get_one_by_stage(Image::class, 'Stage', "ID = " . (int) $ID);
335
        } else {
336
            $image = Image::get()->byID($ID);
337
        }
338
        // This is just annoying
339
        if ($image && !$image->isPublished()) {
340
            $image->doPublish();
341
        }
342
        return $image;
343
    }
344
345
    /**
346
     * Make sure the file is published for the block
347
     *
348
     * @param int $ID
349
     * @return File
350
     */
351
    public static function getPublishedFileByID($ID)
352
    {
353
        if (class_exists(Versioned::class)) {
354
            $file = Versioned::get_one_by_stage(File::class, 'Stage', "ID = " . (int) $ID);
355
        } else {
356
            $file = File::get()->byID($ID);
357
        }
358
        // This is just annoying
359
        if ($file && !$file->isPublished()) {
360
            $file->doPublish();
361
        }
362
        return $file;
363
    }
364
365
    /**
366
     * @return boolean
367
     */
368
    public function isInAdmin()
369
    {
370
        if (isset($_GET['live']) && Director::isDev()) {
371
            return true;
372
        }
373
        if (Controller::has_curr()) {
374
            return Controller::curr() instanceof LeftAndMain;
375
        }
376
        return false;
377
    }
378
379
    public function onBeforeWrite()
380
    {
381
        parent::onBeforeWrite();
382
383
        if (!$this->Sort) {
384
            $this->Sort = $this->getNextSort();
385
        }
386
387
        if (!$this->isInAdmin()) {
388
            return;
389
        }
390
391
        // If we have a menu title (for anchors) we need an HTML ID
392
        if (!$this->HTMLID && $this->MenuTitle) {
393
            $filter = new URLSegmentFilter;
394
            $this->HTMLID = $filter->filter($this->MenuTitle);
395
        }
396
397
        // Render template to content
398
        // We need this for summary to work properly
399
        $Content = $this->renderWithTemplate();
400
        $this->Content = $Content;
401
402
        // Clear unused data fields (see if we can remove setCastedField hijack altogether)
403
        $ID = $_POST['ID'] ?? 0;
404
405
        // Make sure we only assign data on currently edited block (not on other due to cascade update)
406
        if ($ID == $this->ID) {
407
            $PostedData = $_POST[self::DATA_KEY] ?? null;
408
            $PostedSettings = $_POST[self::SETTINGS_KEY] ?? null;
409
            if ($PostedData !== null) {
410
                $this->BlockData = $PostedData;
411
            }
412
            if ($PostedSettings !== null) {
413
                $this->Settings = $PostedSettings;
414
            }
415
        }
416
    }
417
418
    public function onAfterWrite()
419
    {
420
        parent::onAfterWrite();
421
        if (!$this->isInAdmin()) {
422
            return;
423
        }
424
        if (self::$auto_update_page) {
425
            // Update Page Content to reflect updated block content
426
            $this->Page()->write();
427
        }
428
    }
429
430
    /**
431
     * Get a name for this type
432
     * Basically calling getBlockName with the Type
433
     *
434
     * @return string
435
     */
436
    public function BlockType()
437
    {
438
        if (!$this->Type) {
439
            '(Undefined)';
440
        }
441
        return self::getBlockName($this->Type);
442
    }
443
444
    /**
445
     * Get a class name without namespace
446
     *
447
     * @param string $class
448
     * @return string
449
     */
450
    public static function getClassWithoutNamespace($class)
451
    {
452
        $parts = explode("\\", $class);
453
        return array_pop($parts);
454
    }
455
456
    /**
457
     * Get unqualified class of the block's type
458
     *
459
     * @return void
460
     */
461
    public function BlockClass()
462
    {
463
        return self::getClassWithoutNamespace($this->Type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getClassWit...tNamespace($this->Type) returns the type string which is incompatible with the documented return type void.
Loading history...
464
    }
465
466
    /**
467
     * Extend __get to allow loading data from Data store
468
     *
469
     * @param string $name
470
     * @return mixed
471
     */
472
    public function __get($name)
473
    {
474
        // A Data field
475
        if (strpos($name, self::DATA_KEY . '[') === 0) {
476
            return $this->getIn($name, $this->DataArray());
477
        }
478
        // A Settings field
479
        if (strpos($name, self::SETTINGS_KEY . '[') === 0) {
480
            return $this->getIn($name, $this->SettingsArray());
481
        }
482
        return parent::__get($name);
483
    }
484
485
    /**
486
     * Extend hasField to allow loading data from Data store
487
     *
488
     * @param string $name
489
     * @return mixed
490
     */
491
    public function hasField($name)
492
    {
493
        // A Data field
494
        if (strpos($name, self::DATA_KEY . '[') === 0) {
495
            return true;
496
        }
497
        // A Settings field
498
        if (strpos($name, self::SETTINGS_KEY . '[') === 0) {
499
            return true;
500
        }
501
        return parent::hasField($name);
502
    }
503
504
    /**
505
     * Split Name[Input][Sub][Value] notation
506
     *
507
     * @param string $name
508
     * @return array
509
     */
510
    public static function extractNameParts($name)
511
    {
512
        if (strpos($name, '[') !== false) {
513
            $matches = null;
514
            preg_match_all('/\[([a-zA-Z0-9_]+)\]/', $name, $matches);
515
            $matches = $matches[1];
516
        } else {
517
            $matches = [$name];
518
        }
519
        return $matches;
520
    }
521
522
    /**
523
     * Get nested data
524
     *
525
     * @param string $key
526
     * @param array $arr
527
     * @return string
528
     */
529
    public function getIn($key, $arr)
530
    {
531
        $matches = self::extractNameParts($key);
532
        $val = $arr;
533
        foreach ($matches as $part) {
534
            if (isset($val[$part])) {
535
                $val = $val[$part];
536
            } else {
537
                $val = null;
538
            }
539
        }
540
        return $val;
541
    }
542
543
    /**
544
     * Hijack setCastedField to ensure form saving works properly
545
     *
546
     * Add value on a per field basis
547
     *
548
     * @param string $fieldName
549
     * @param string $value
550
     * @return $this
551
     */
552
    public function setCastedField($fieldName, $value)
553
    {
554
        // A Data field
555
        if (strpos($fieldName, self::DATA_KEY . '[') === 0) {
556
            $obj = $this->dbObject(self::DATA_KEY)->addValue(self::extractNameParts($fieldName), $value);
557
            $obj->saveInto($this);
558
            return $this;
559
        }
560
        // A Settings field
561
        if (strpos($fieldName, self::SETTINGS_KEY . '[') === 0) {
562
            $obj = $this->dbObject(self::SETTINGS_KEY)->addValue(self::extractNameParts($fieldName), $value);
563
            $obj->saveInto($this);
564
            return $this;
565
        }
566
        return parent::setCastedField($fieldName, $value);
567
    }
568
569
    /**
570
     * Consistently returns an array regardless of what is in BlockData
571
     *
572
     * @return array
573
     */
574
    public function DataArray()
575
    {
576
        return $this->dbObject('BlockData')->decodeArray();
577
    }
578
579
    /**
580
     * Consistently returns an array regardless of what is in Settings
581
     *
582
     * @return array
583
     */
584
    public function SettingsArray()
585
    {
586
        return $this->dbObject('Settings')->decodeArray();
587
    }
588
589
    /**
590
     * When looping in template, wrap the blocks content is wrapped in a
591
     * div with theses classes
592
     *
593
     * @return string
594
     */
595
    public function getClass()
596
    {
597
        $inst = $this->getTypeInstance();
598
        $class = 'Block Block-' . $this->BlockType();
599
        $inst->updateClass($class);
600
        return $class;
601
    }
602
603
    /**
604
     * Get a viewable block instance wrapping this block
605
     *
606
     * @return BaseBlock
607
     */
608
    public function getTypeInstance()
609
    {
610
        if ($this->Type) {
611
            $class = $this->Type;
612
            if (class_exists($class)) {
613
                return new $class($this);
614
            }
615
        }
616
        // If there is no type or type does not exist, render as content block
617
        return new ContentBlock($this);
618
    }
619
    /**
620
     * Returns a summary to be displayed in the gridfield
621
     *
622
     * @return DBHTMLText
623
     */
624
    public function Summary()
625
    {
626
        // Read from content
627
        $summary = trim(\strip_tags($this->Content));
628
        $shortSummary = \substr($summary, 0, 100);
629
        // Collapse whitespace
630
        $shortSummary = preg_replace('/\s+/', ' ', $shortSummary);
631
        if (!$shortSummary) {
632
            if ($this->ImageID) {
633
                $shortSummary = $this->Image()->getTitle();
634
            }
635
        }
636
        // Avoid escaping issues
637
        $text = new DBHTMLText('Summary');
638
        $text->setValue($shortSummary);
639
        return $text;
640
    }
641
642
    public function getCMSActions()
643
    {
644
        $actions = parent::getCMSActions();
645
        return $actions;
646
    }
647
648
    public function getCMSFields()
649
    {
650
        // $fields = parent::getCMSFields();
651
        $fields = new BlockFieldList();
652
        $mainTab = new Tab("Main");
653
        $settingsTab = new Tab("Settings");
654
        $fields->push(new TabSet("Root", $mainTab, $settingsTab));
655
        // (!) Fields must be added to Root.Main to work properly
656
        $mainTab->push(new HiddenField('ID'));
657
        $mainTab->push(new HiddenField(self::DATA_KEY));
658
        $mainTab->push(new HiddenField(self::SETTINGS_KEY));
659
        $mainTab->push(new HiddenField('PageID'));
660
        // Show debug infos
661
        if (Director::isDev() && isset($_GET['debug'])) {
662
            $json = '';
663
            if ($this->AuditData) {
0 ignored issues
show
Bug Best Practice introduced by
The property AuditData does not exist on LeKoala\Blocks\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
664
                $json = $this->dbObject('AuditData')->pretty();
665
                $debugData = new LiteralField('JsonData', '<pre>Data: <code>' . $json . '</code></pre>');
666
            } else {
667
                $debugData = new LiteralField('JsonData', '<div class="message info">Does not contain any data</div>');
668
            }
669
            $fields->addFieldsToTab('Root.Debug', $debugData);
0 ignored issues
show
Bug introduced by
$debugData of type SilverStripe\Forms\LiteralField is incompatible with the type array expected by parameter $fields of SilverStripe\Forms\FieldList::addFieldsToTab(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

669
            $fields->addFieldsToTab('Root.Debug', /** @scrutinizer ignore-type */ $debugData);
Loading history...
670
            if ($this->Settings) {
671
                $json = $this->dbObject('Settings')->pretty();
672
                $debugSettings = new LiteralField('JsonSettings', '<pre>Settings: <code>' . $json . '</code></pre>');
673
            } else {
674
                $debugSettings = new LiteralField('JsonSettings', '<div class="message info">Does not contain any settings</div>');
675
            }
676
            $fields->addFieldsToTab('Root.Debug', $debugSettings);
677
        }
678
        // Only show valid types in a dropdown
679
        $ValidTypes = self::listValidTypes();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $ValidTypes is correct as self::listValidTypes() targeting LeKoala\Blocks\Block::listValidTypes() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
680
        $Type = new DropdownField('Type', $this->fieldLabel('Type'), $ValidTypes);
0 ignored issues
show
Bug introduced by
$ValidTypes of type void is incompatible with the type ArrayAccess|array expected by parameter $source of SilverStripe\Forms\DropdownField::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

680
        $Type = new DropdownField('Type', $this->fieldLabel('Type'), /** @scrutinizer ignore-type */ $ValidTypes);
Loading history...
681
        $Type->setAttribute('onchange', "jQuery('#Form_ItemEditForm_action_doSave').click()");
682
        if ($this->ID) {
683
            $settingsTab->push($Type);
684
        } else {
685
            $mainTab->push($Type);
686
        }
687
        // Other settings
688
        $settingsTab->push(new TextField('MenuTitle', 'Menu Title'));
689
        $settingsTab->push(new TextField('HTMLID', 'HTML ID'));
690
        // Show uploader
691
        // TODO: let injector do the job?
692
        $uploaderClass = BlockFieldList::getUploaderClass();
693
        $Image = $uploaderClass::create('Image');
694
        $fields->addFieldsToTab('Root.Main', $Image);
695
        // Handle Collection GridField
696
        $list = $this->Collection();
697
        if ($list) {
0 ignored issues
show
introduced by
$list is of type SilverStripe\ORM\DataList, thus it always evaluated to true.
Loading history...
698
            $class = $list->dataClass();
699
            $singleton = $class::singleton();
700
            $gridConfig = GridFieldConfig_RecordEditor::create();
701
            if ($singleton->hasField('Sort')) {
702
                $gridConfig->addComponent(new GridFieldOrderableRows());
703
            }
704
            $grid = new GridField($class, $singleton->plural_name(), $list, $gridConfig);
705
            $fields->addFieldToTab('Root.Main', $grid);
706
        }
707
        // Handle Shared Collection GridField
708
        $list = $this->SharedCollection();
709
        if ($list) {
0 ignored issues
show
introduced by
$list is of type SilverStripe\ORM\DataList, thus it always evaluated to true.
Loading history...
710
            $class = $list->dataClass();
711
            $singleton = $class::singleton();
712
            $gridConfig = GridFieldConfig_RecordEditor::create();
713
            $gridConfig->removeComponentsByType(GridFieldDeleteAction::class);
714
            if ($singleton->hasField('Sort')) {
715
                $gridConfig->addComponent(new GridFieldOrderableRows());
716
            }
717
            $grid = new GridField($class, $singleton->plural_name() . ' (shared)', $list, $gridConfig);
718
            $fields->addFieldToTab('Root.Main', $grid);
719
        }
720
        // Allow type instance to extends fields
721
        // Defined fields are processed later on for default behaviour
722
        $inst = $this->getTypeInstance();
723
        $inst->updateFields($fields);
724
        // Allow regular extension to work
725
        $this->extend('updateCMSFields', $fields);
726
        // Adjust uploaders
727
        $uploadFolder = 'Blocks/' . $this->PageID;
728
        // Adjust the single image field
729
        $Image = $fields->dataFieldByName('Image');
730
        if ($Image) {
731
            $Image->setFolderName($uploadFolder);
732
            $Image->setAllowedMaxFileNumber(1);
733
            $Image->setIsMultiUpload(false);
734
        }
735
        // Adjust any items fields
736
        $dataFields = $fields->dataFields();
737
        foreach ($dataFields as $dataField) {
738
            if ($dataField instanceof UploadField) {
739
                $dataField->setFolderName($uploadFolder . '/' . $this->BlockType());
740
                $fieldName = $dataField->getName();
741
742
                // Items uploader match only one file
743
                if (strpos($fieldName, '[') !== false) {
744
                    $dataField->setAllowedMaxFileNumber(1);
745
                    $dataField->setIsMultiUpload(false);
746
                }
747
            }
748
        }
749
750
        $fields->removeByName('Sort');
751
752
        return $fields;
753
    }
754
755
    /**
756
     * This allows you to define your own method if needed
757
     * Like when you have page dependent data objects that shouldn't use a global sort
758
     * Or if you want to sort by a given multiple to allow inserts later on
759
     * @return int
760
     */
761
    public function getNextSort()
762
    {
763
        $max = (int) self::get()->max('Sort');
764
        return $max + 1;
765
    }
766
767
    public function validate()
768
    {
769
        $result = parent::validate();
770
        return $result;
771
    }
772
773
    /**
774
     * List all classes extending BaseBlock
775
     *
776
     * @return array
777
     */
778
    public static function listBlocks()
779
    {
780
        $blocks = ClassInfo::subclassesFor(BaseBlock::class);
781
        // Remove BaseBlock
782
        \array_shift($blocks);
783
        return $blocks;
784
    }
785
786
    /**
787
     * Get a list of blocks mapped by class => name
788
     *
789
     * @return void
790
     */
791
    public static function listValidTypes()
792
    {
793
        $list = [];
794
        foreach (self::listBlocks() as $lcClass => $class) {
795
            $list[$class] = self::getBlockName($class);
796
        }
797
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list returns the type array which is incompatible with the documented return type void.
Loading history...
798
    }
799
800
    /**
801
     * Get a list of blocks mapped by unqualified class => class
802
     *
803
     * @return void
804
     */
805
    public static function listTemplates()
806
    {
807
        $list = [];
808
        foreach (self::listBlocks() as $lcClass => $class) {
809
            $className = self::getClassWithoutNamespace($class);
810
            $list[$className] = $class;
811
        }
812
        return $list;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $list returns the type array which is incompatible with the documented return type void.
Loading history...
813
    }
814
815
    /**
816
     * Get a more human readable name
817
     * TODO: i18n
818
     *
819
     * @param string $class
820
     * @return string
821
     */
822
    protected static function getBlockName($class)
823
    {
824
        if (!$class) {
825
            return;
826
        }
827
        $className = Block::getClassWithoutNamespace($class);
828
        return preg_replace('/Block$/', '', $className);
829
    }
830
831
    /**
832
     * The place where to store assets
833
     * We create a folder for each record to easily clean up after deletion
834
     *
835
     * @return string
836
     */
837
    public function getFolderName()
838
    {
839
        $className = Block::getClassWithoutNamespace(get_class($this->owner));
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type null and string; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

839
        $className = Block::getClassWithoutNamespace(get_class(/** @scrutinizer ignore-type */ $this->owner));
Loading history...
Bug Best Practice introduced by
The property owner does not exist on LeKoala\Blocks\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
840
        return $className . '/' . $this->owner->ID;
841
    }
842
}
843