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'; |
||||
61 | |||||
62 | private static $db = [ |
||||
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 = [ |
||||
75 | "Image" => Image::class, |
||||
76 | "Page" => BlocksPage::class |
||||
77 | ]; |
||||
78 | private static $many_many = [ |
||||
79 | "Images" => Image::class, |
||||
80 | "Files" => File::class, |
||||
81 | ]; |
||||
82 | private static $many_many_extraFields = [ |
||||
83 | 'Images' => ['SortOrder' => 'Int'], |
||||
84 | 'Files' => ['SortOrder' => 'Int'], |
||||
85 | ]; |
||||
86 | private static $cascade_deletes = [ |
||||
87 | 'Image', 'Images', 'Files' |
||||
88 | ]; |
||||
89 | private static $owns = [ |
||||
90 | 'Image', "Images", "Files", |
||||
91 | ]; |
||||
92 | private static $summary_fields = [ |
||||
93 | 'BlockType' => 'Block Type', |
||||
94 | 'MenuTitle' => 'Menu Title', |
||||
95 | 'Summary' => 'Summary', |
||||
96 | ]; |
||||
97 | private static $translate = [ |
||||
98 | "MenuTitle", "Content", "AuditData", "BlockData" |
||||
99 | ]; |
||||
100 | private static $defaults = [ |
||||
101 | 'Type' => ContentBlock::class, |
||||
102 | ]; |
||||
103 | |||||
104 | private static $default_sort = 'Sort ASC'; |
||||
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) { |
||||
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
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
![]() |
|||||
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
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
![]() |
|||||
217 | } |
||||
218 | $result = (string) $typeInst->renderWith($template, $arrayData); |
||||
0 ignored issues
–
show
$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
![]() |
|||||
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) { |
||||
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
|
|||||
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
The property
AuditData does not exist on LeKoala\Blocks\Block . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
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
$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
![]() |
|||||
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
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
680 | $Type = new DropdownField('Type', $this->fieldLabel('Type'), $ValidTypes); |
||||
0 ignored issues
–
show
$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
![]() |
|||||
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) { |
||||
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) { |
||||
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
|
|||||
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
|
|||||
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
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
![]() The property
owner does not exist on LeKoala\Blocks\Block . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
840 | return $className . '/' . $this->owner->ID; |
||||
841 | } |
||||
842 | } |
||||
843 |
This check looks for function or method calls that always return null and whose return value is used.
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.