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
![]() |
|||||
61 | |||||
62 | private static $db = [ |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
75 | "Image" => Image::class, |
||||
76 | "Page" => BlocksPage::class |
||||
77 | ]; |
||||
78 | private static $many_many = [ |
||||
0 ignored issues
–
show
|
|||||
79 | "Images" => Image::class, |
||||
80 | "Files" => File::class, |
||||
81 | ]; |
||||
82 | private static $many_many_extraFields = [ |
||||
0 ignored issues
–
show
|
|||||
83 | 'Images' => ['SortOrder' => 'Int'], |
||||
84 | 'Files' => ['SortOrder' => 'Int'], |
||||
85 | ]; |
||||
86 | private static $cascade_deletes = [ |
||||
0 ignored issues
–
show
|
|||||
87 | 'Image', 'Images', 'Files' |
||||
88 | ]; |
||||
89 | private static $owns = [ |
||||
0 ignored issues
–
show
|
|||||
90 | 'Image', "Images", "Files", |
||||
91 | ]; |
||||
92 | private static $summary_fields = [ |
||||
0 ignored issues
–
show
|
|||||
93 | 'BlockType' => 'Block Type', |
||||
94 | 'MenuTitle' => 'Menu Title', |
||||
95 | 'Summary' => 'Summary', |
||||
96 | ]; |
||||
97 | private static $translate = [ |
||||
0 ignored issues
–
show
|
|||||
98 | "MenuTitle", "Content", "AuditData", "BlockData" |
||||
99 | ]; |
||||
100 | private static $defaults = [ |
||||
0 ignored issues
–
show
|
|||||
101 | 'Type' => ContentBlock::class, |
||||
102 | ]; |
||||
103 | |||||
104 | private static $default_sort = 'Sort ASC'; |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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 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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() 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) { |
||||
0 ignored issues
–
show
|
|||||
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) { |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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 |