Total Complexity | 74 |
Total Lines | 994 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ModuleInstaller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ModuleInstaller, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class ModuleInstaller |
||
19 | { |
||
20 | /** |
||
21 | * Database connection instance |
||
22 | * |
||
23 | * @var SpoonDatabase |
||
24 | */ |
||
25 | private $database; |
||
26 | |||
27 | /** |
||
28 | * The module name. |
||
29 | * |
||
30 | * @var string |
||
31 | */ |
||
32 | private $module; |
||
33 | |||
34 | /** |
||
35 | * The default extras that have to be added to every page. |
||
36 | * |
||
37 | * @var array |
||
38 | */ |
||
39 | private $defaultExtras = []; |
||
40 | |||
41 | /** |
||
42 | * The frontend language(s) |
||
43 | * |
||
44 | * @var array |
||
45 | */ |
||
46 | private $languages = []; |
||
47 | |||
48 | /** |
||
49 | * The backend language(s) |
||
50 | * |
||
51 | * @var array |
||
52 | */ |
||
53 | private $interfaceLanguages = []; |
||
54 | |||
55 | /** |
||
56 | * Cached modules |
||
57 | * |
||
58 | * @var array |
||
59 | */ |
||
60 | private static $modules = []; |
||
61 | |||
62 | /** |
||
63 | * The variables passed by the installer |
||
64 | * |
||
65 | * @var array |
||
66 | */ |
||
67 | private $variables = []; |
||
68 | |||
69 | /** |
||
70 | * Should example data be installed. |
||
71 | * |
||
72 | * @var bool |
||
73 | */ |
||
74 | private $example; |
||
75 | |||
76 | /** |
||
77 | * @param SpoonDatabase $database The database-connection. |
||
78 | * @param array $languages The selected frontend languages. |
||
79 | * @param array $interfaceLanguages The selected backend languages. |
||
80 | * @param bool $example Should example data be installed. |
||
81 | * @param array $variables The passed variables. |
||
82 | */ |
||
83 | public function __construct( |
||
84 | SpoonDatabase $database, |
||
85 | array $languages, |
||
86 | array $interfaceLanguages, |
||
87 | bool $example = false, |
||
88 | array $variables = [] |
||
89 | ) { |
||
90 | $this->database = $database; |
||
91 | $this->languages = $languages; |
||
92 | $this->interfaceLanguages = $interfaceLanguages; |
||
93 | $this->example = $example; |
||
94 | $this->variables = $variables; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Adds a default extra to the stack of extras |
||
99 | * |
||
100 | * @param int $extraId The extra id to add to every page. |
||
101 | * @param string $position The position to put the default extra. |
||
102 | */ |
||
103 | protected function addDefaultExtra(int $extraId, string $position): void |
||
104 | { |
||
105 | $this->defaultExtras[] = ['id' => $extraId, 'position' => $position]; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Inserts a new module. |
||
110 | * The getModule method becomes available after using addModule and returns $module parameter. |
||
111 | * |
||
112 | * @param string $module The name of the module. |
||
113 | */ |
||
114 | protected function addModule(string $module): void |
||
115 | { |
||
116 | $this->module = (string) $module; |
||
117 | |||
118 | // module does not yet exists |
||
119 | if (!(bool) $this->getDatabase()->getVar('SELECT 1 FROM modules WHERE name = ? LIMIT 1', $this->module)) { |
||
120 | // build item |
||
121 | $item = [ |
||
122 | 'name' => $this->module, |
||
123 | 'installed_on' => gmdate('Y-m-d H:i:s'), |
||
124 | ]; |
||
125 | |||
126 | // insert module |
||
127 | $this->getDatabase()->insert('modules', $item); |
||
128 | |||
129 | return; |
||
130 | } |
||
131 | |||
132 | // activate and update description |
||
133 | $this->getDatabase()->update('modules', ['installed_on' => gmdate('Y-m-d H:i:s')], 'name = ?', $this->module); |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Add a search index |
||
138 | * |
||
139 | * @param string $module The module wherein will be searched. |
||
140 | * @param int $otherId The id of the record. |
||
141 | * @param array $fields A key/value pair of fields to index. |
||
142 | * @param string $language The frontend language for this entry. |
||
143 | */ |
||
144 | protected function addSearchIndex(string $module, int $otherId, array $fields, string $language): void |
||
145 | { |
||
146 | // get database |
||
147 | $database = $this->getDatabase(); |
||
148 | |||
149 | // validate cache |
||
150 | if (empty(self::$modules)) { |
||
151 | // get all modules |
||
152 | self::$modules = (array) $database->getColumn('SELECT m.name FROM modules AS m'); |
||
153 | } |
||
154 | |||
155 | // module exists? |
||
156 | if (!in_array('Search', self::$modules)) { |
||
157 | return; |
||
158 | } |
||
159 | |||
160 | // no fields? |
||
161 | if (empty($fields)) { |
||
162 | return; |
||
163 | } |
||
164 | |||
165 | // insert search index |
||
166 | foreach ($fields as $field => $value) { |
||
167 | // reformat value |
||
168 | $value = strip_tags((string) $value); |
||
169 | |||
170 | // insert in database |
||
171 | $database->execute( |
||
172 | 'INSERT INTO search_index (module, other_id, language, field, value, active) |
||
173 | VALUES (?, ?, ?, ?, ?, ?) |
||
174 | ON DUPLICATE KEY UPDATE value = ?, active = ?', |
||
175 | [(string) $module, (int) $otherId, (string) $language, (string) $field, $value, true, $value, true] |
||
176 | ); |
||
177 | } |
||
178 | |||
179 | BackendSearchModel::invalidateCache(); |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Method that will be overridden by the specific installers |
||
184 | */ |
||
185 | protected function execute(): void |
||
186 | { |
||
187 | // just a placeholder |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Get the database-handle |
||
192 | * |
||
193 | * @return SpoonDatabase |
||
194 | */ |
||
195 | protected function getDatabase(): SpoonDatabase |
||
196 | { |
||
197 | return $this->database; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Get the module name |
||
202 | * |
||
203 | * @return string |
||
204 | */ |
||
205 | protected function getModule(): string |
||
206 | { |
||
207 | return $this->module; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Get the default extras. |
||
212 | * |
||
213 | * @return array |
||
214 | */ |
||
215 | public function getDefaultExtras(): array |
||
216 | { |
||
217 | return $this->defaultExtras; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Get the default user |
||
222 | * |
||
223 | * @return int |
||
224 | */ |
||
225 | protected function getDefaultUserID(): int |
||
226 | { |
||
227 | try { |
||
228 | // fetch default user id |
||
229 | return (int) $this->getDatabase()->getVar( |
||
230 | 'SELECT id |
||
231 | FROM users |
||
232 | WHERE is_god = ? AND active = ? AND deleted = ? |
||
233 | ORDER BY id ASC', |
||
234 | [true, true, false] |
||
235 | ); |
||
236 | } catch (\Exception $e) { |
||
237 | return 1; |
||
238 | } |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Get the selected cms interface languages |
||
243 | * |
||
244 | * @return array |
||
245 | */ |
||
246 | protected function getInterfaceLanguages(): array |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Get the selected languages |
||
253 | * |
||
254 | * @return array |
||
255 | */ |
||
256 | protected function getLanguages(): array |
||
257 | { |
||
258 | return $this->languages; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Get a locale item. |
||
263 | * |
||
264 | * @param string $name |
||
265 | * @param string $module |
||
266 | * @param string $language The language abbreviation. |
||
267 | * @param string $type The type of locale. |
||
268 | * @param string $application |
||
269 | * |
||
270 | * @return string |
||
271 | */ |
||
272 | protected function getLocale( |
||
273 | string $name, |
||
274 | string $module = 'Core', |
||
275 | string $language = 'en', |
||
276 | string $type = 'lbl', |
||
277 | string $application = 'Backend' |
||
278 | ): string { |
||
279 | $translation = (string) $this->getDatabase()->getVar( |
||
280 | 'SELECT value |
||
281 | FROM locale |
||
282 | WHERE name = ? AND module = ? AND language = ? AND type = ? AND application = ?', |
||
283 | [$name, $module, $language, $type, $application] |
||
284 | ); |
||
285 | |||
286 | return ($translation !== '') ? $translation : $name; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Get a setting |
||
291 | * |
||
292 | * @param string $module The name of the module. |
||
293 | * @param string $name The name of the setting. |
||
294 | * |
||
295 | * @return mixed |
||
296 | */ |
||
297 | protected function getSetting(string $module, string $name) |
||
298 | { |
||
299 | return unserialize( |
||
300 | $this->getDatabase()->getVar( |
||
301 | 'SELECT value |
||
302 | FROM modules_settings |
||
303 | WHERE module = ? AND name = ?', |
||
304 | [$module, $name] |
||
305 | ) |
||
306 | ); |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Get the id of the requested template of the active theme. |
||
311 | * |
||
312 | * @param string $template |
||
313 | * @param string $theme |
||
314 | * |
||
315 | * @return int |
||
316 | */ |
||
317 | protected function getTemplateId(string $template, string $theme = null): int |
||
318 | { |
||
319 | // no theme set = default theme |
||
320 | if ($theme === null) { |
||
321 | $theme = $this->getSetting('Core', 'theme'); |
||
322 | } |
||
323 | |||
324 | // if the theme is still null we should fallback to the core |
||
325 | if ($theme === null) { |
||
326 | $theme = 'Fork'; |
||
327 | } |
||
328 | |||
329 | // return best matching template id |
||
330 | return (int) $this->getDatabase()->getVar( |
||
331 | 'SELECT id FROM themes_templates |
||
332 | WHERE theme = ? |
||
333 | ORDER BY path LIKE ? DESC, id ASC |
||
334 | LIMIT 1', |
||
335 | [$theme, '%' . $template . '%'] |
||
336 | ); |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Get a variable |
||
341 | * |
||
342 | * @param string $name |
||
343 | * |
||
344 | * @return mixed |
||
345 | */ |
||
346 | protected function getVariable(string $name) |
||
347 | { |
||
348 | return $this->variables[$name] ?? null; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Imports the locale XML file |
||
353 | * |
||
354 | * @param string $filename The full path for the XML-file. |
||
355 | * @param bool $overwriteConflicts Should we overwrite when there is a conflict? |
||
356 | */ |
||
357 | protected function importLocale(string $filename, bool $overwriteConflicts = false): void |
||
358 | { |
||
359 | // load the file content and execute it |
||
360 | $content = trim(file_get_contents($filename)); |
||
361 | |||
362 | // file actually has content |
||
363 | if (empty($content)) { |
||
364 | return; |
||
365 | } |
||
366 | |||
367 | // load xml |
||
368 | $xml = @simplexml_load_string($content); |
||
369 | |||
370 | // import if it's valid xml |
||
371 | if ($xml === false) { |
||
372 | return; |
||
373 | } |
||
374 | |||
375 | // import locale |
||
376 | BackendLocaleModel::importXML( |
||
377 | $xml, |
||
378 | $overwriteConflicts, |
||
379 | $this->getLanguages(), |
||
380 | $this->getInterfaceLanguages(), |
||
381 | $this->getDefaultUserID(), |
||
382 | gmdate('Y-m-d H:i:s') |
||
383 | ); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Imports the sql file |
||
388 | * |
||
389 | * @param string $filename The full path for the SQL-file. |
||
390 | */ |
||
391 | protected function importSQL(string $filename): void |
||
392 | { |
||
393 | // load the file content and execute it |
||
394 | $queries = trim(file_get_contents($filename)); |
||
395 | |||
396 | // file actually has content |
||
397 | if (empty($queries)) { |
||
398 | return; |
||
399 | } |
||
400 | |||
401 | $this->getDatabase()->execute($queries); |
||
402 | } |
||
403 | |||
404 | protected function insertDashboardWidget(string $module, string $widget): void |
||
405 | { |
||
406 | // get database |
||
407 | $database = $this->getDatabase(); |
||
408 | |||
409 | // fetch current settings |
||
410 | $groupSettings = (array) $database->getRecords( |
||
411 | 'SELECT * FROM groups_settings WHERE name = ?', |
||
412 | ['dashboard_sequence'] |
||
413 | ); |
||
414 | $userSettings = (array) $database->getRecords( |
||
415 | 'SELECT * FROM users_settings WHERE name = ?', |
||
416 | ['dashboard_sequence'] |
||
417 | ); |
||
418 | |||
419 | // loop group settings |
||
420 | foreach ($groupSettings as $settings) { |
||
421 | // unserialize data |
||
422 | $settings['value'] = unserialize($settings['value'], ['allowed_classes' => false]); |
||
423 | |||
424 | // add new widget |
||
425 | $settings['value'][$module][] = $widget; |
||
426 | |||
427 | // re-serialize value |
||
428 | $settings['value'] = serialize($settings['value']); |
||
429 | |||
430 | // update in database |
||
431 | $database->update( |
||
432 | 'groups_settings', |
||
433 | $settings, |
||
434 | 'group_id = ? AND name = ?', |
||
435 | [$settings['group_id'], $settings['name']] |
||
436 | ); |
||
437 | } |
||
438 | |||
439 | // loop user settings |
||
440 | foreach ($userSettings as $settings) { |
||
441 | // unserialize data |
||
442 | $settings['value'] = unserialize($settings['value'], ['allowed_classes' => false]); |
||
443 | |||
444 | // add new widget |
||
445 | $settings['value'][$module][] = $widget; |
||
446 | |||
447 | // re-serialize value |
||
448 | $settings['value'] = serialize($settings['value']); |
||
449 | |||
450 | // update in database |
||
451 | $database->update( |
||
452 | 'users_settings', |
||
453 | $settings, |
||
454 | 'user_id = ? AND name = ?', |
||
455 | [$settings['user_id'], $settings['name']] |
||
456 | ); |
||
457 | } |
||
458 | } |
||
459 | |||
460 | private function getNextSequenceForModule(string $module): int |
||
461 | { |
||
462 | // set next sequence number for this module |
||
463 | $sequence = (int) $this->getDatabase()->getVar( |
||
464 | 'SELECT MAX(sequence) + 1 FROM modules_extras WHERE module = ?', |
||
465 | [$module] |
||
466 | ); |
||
467 | |||
468 | // this is the first extra for this module: generate new 1000-series |
||
469 | if ($sequence > 0) { |
||
470 | return $sequence; |
||
471 | } |
||
472 | |||
473 | return (int) $this->getDatabase()->getVar( |
||
474 | 'SELECT CEILING(MAX(sequence) / 1000) * 1000 FROM modules_extras' |
||
475 | ); |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Insert an extra |
||
480 | * |
||
481 | * @param string $module The module for the extra. |
||
482 | * @param ModuleExtraType $type The type, possible values are: homepage, widget, block. |
||
483 | * @param string $label The label for the extra. |
||
484 | * @param string|null $action The action. |
||
485 | * @param array|null $data data, will be passed in the extra. |
||
486 | * @param bool $hidden Is this extra hidden? |
||
487 | * @param int|null $sequence The sequence for the extra. |
||
488 | * |
||
489 | * @return int |
||
490 | */ |
||
491 | protected function insertExtra( |
||
492 | string $module, |
||
493 | ModuleExtraType $type, |
||
494 | string $label, |
||
495 | string $action = null, |
||
496 | array $data = null, |
||
497 | bool $hidden = false, |
||
498 | int $sequence = null |
||
499 | ): int { |
||
500 | $extraId = $this->findModuleExtraId($module, $type, $label, $data); |
||
501 | if ($extraId !== 0) { |
||
502 | return $extraId; |
||
503 | } |
||
504 | |||
505 | return Model::insertExtra( |
||
506 | $type, |
||
507 | $module, |
||
508 | $action, |
||
509 | $label, |
||
510 | $data, |
||
511 | $hidden, |
||
512 | $sequence ?? $this->getNextSequenceForModule($module) |
||
513 | ); |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * @param string $module |
||
518 | * @param ModuleExtraType $type |
||
519 | * @param string $label |
||
520 | * @param array|null $data |
||
521 | * |
||
522 | * @return int |
||
523 | */ |
||
524 | private function findModuleExtraId(string $module, ModuleExtraType $type, string $label, array $data = null): int |
||
525 | { |
||
526 | // build query |
||
527 | $query = 'SELECT id FROM modules_extras WHERE module = ? AND type = ? AND label = ?'; |
||
528 | $parameters = [$module, $type, $label]; |
||
529 | |||
530 | if ($data === null) { |
||
531 | $query .= ' AND data IS NULL'; |
||
532 | |||
533 | return (int) $this->getDatabase()->getVar($query, $parameters); |
||
534 | } |
||
535 | |||
536 | $query .= ' AND data = ?'; |
||
537 | $parameters[] = serialize($data); |
||
538 | |||
539 | // get id (if it already exists) |
||
540 | return (int) $this->getDatabase()->getVar($query, $parameters); |
||
541 | } |
||
542 | |||
543 | /** |
||
544 | * Insert a meta item |
||
545 | * |
||
546 | * @param string $keywords The keyword of the item. |
||
547 | * @param string $description A description of the item. |
||
548 | * @param string $title The page title for the item. |
||
549 | * @param string $url The unique URL. |
||
550 | * @param bool $keywordsOverwrite Should the keywords be overwritten? |
||
551 | * @param bool $descriptionOverwrite Should the descriptions be overwritten? |
||
552 | * @param bool $titleOverwrite Should the page title be overwritten? |
||
553 | * @param bool $urlOverwrite Should the URL be overwritten? |
||
554 | * @param string $custom Any custom meta-data. |
||
555 | * @param string $seoFollow Any custom meta-data. |
||
556 | * @param string $seoIndex Any custom meta-data. |
||
557 | * @param array $data Any custom meta-data. |
||
558 | * |
||
559 | * @return int |
||
560 | */ |
||
561 | protected function insertMeta( |
||
562 | string $keywords, |
||
563 | string $description, |
||
564 | string $title, |
||
565 | string $url, |
||
566 | bool $keywordsOverwrite = false, |
||
567 | bool $descriptionOverwrite = false, |
||
568 | bool $titleOverwrite = false, |
||
569 | bool $urlOverwrite = false, |
||
570 | string $custom = null, |
||
571 | string $seoFollow = null, |
||
572 | string $seoIndex = null, |
||
573 | array $data = null |
||
574 | ): int { |
||
575 | return (int) $this->getDatabase()->insert( |
||
576 | 'meta', |
||
577 | [ |
||
578 | 'keywords' => $keywords, |
||
579 | 'keywords_overwrite' => $keywordsOverwrite, |
||
580 | 'description' => $description, |
||
581 | 'description_overwrite' => $descriptionOverwrite, |
||
582 | 'title' => $title, |
||
583 | 'title_overwrite' => $titleOverwrite, |
||
584 | 'url' => CommonUri::getUrl($url), |
||
585 | 'url_overwrite' => $urlOverwrite, |
||
586 | 'custom' => $custom, |
||
587 | 'seo_follow' => $seoFollow, |
||
588 | 'seo_index' => $seoIndex, |
||
589 | 'data' => $data !== null ? serialize($data) : null, |
||
590 | ] |
||
591 | ); |
||
592 | } |
||
593 | |||
594 | /** |
||
595 | * Looks for the next page id, if it is the first page it will default to 1 |
||
596 | * |
||
597 | * @param string $language |
||
598 | * |
||
599 | * @return int |
||
600 | */ |
||
601 | private function getNextPageIdForLanguage(string $language): int |
||
602 | { |
||
603 | $maximumPageId = (int) $this->getDatabase()->getVar( |
||
604 | 'SELECT MAX(id) FROM pages WHERE language = ?', |
||
605 | [$language] |
||
606 | ); |
||
607 | |||
608 | return ++$maximumPageId; |
||
609 | } |
||
610 | |||
611 | private function archiveAllRevisionsOfAPageForLanguage(int $pageId, string $language): void |
||
612 | { |
||
613 | $this->getDatabase()->update( |
||
614 | 'pages', |
||
615 | ['status' => 'archive'], |
||
616 | 'id = ? AND language = ?', |
||
617 | [$pageId, $language] |
||
618 | ); |
||
619 | } |
||
620 | |||
621 | private function getNextPageSequence(string $language, int $parentId, string $type): int |
||
622 | { |
||
623 | $maximumPageSequence = (int) $this->getDatabase()->getVar( |
||
624 | 'SELECT MAX(sequence) FROM pages WHERE language = ? AND parent_id = ? AND type = ?', |
||
625 | [$language, $parentId, $type] |
||
626 | ); |
||
627 | |||
628 | return ++$maximumPageSequence; |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Add the missing data to the meta record |
||
633 | * |
||
634 | * @param array $meta |
||
635 | * @param string $defaultValue |
||
636 | * |
||
637 | * @return array |
||
638 | */ |
||
639 | private function completeMetaRecord(array $meta, string $defaultValue): array |
||
640 | { |
||
641 | $meta['keywords'] = $meta['keywords'] ?? $defaultValue; |
||
642 | $meta['keywords_overwrite'] = $meta['keywords_overwrite'] ?? false; |
||
643 | $meta['description'] = $meta['description'] ?? $defaultValue; |
||
644 | $meta['description_overwrite'] = $meta['description_overwrite'] ?? false; |
||
645 | $meta['title'] = $meta['title'] ?? $defaultValue; |
||
646 | $meta['title_overwrite'] = $meta['title_overwrite'] ?? false; |
||
647 | $meta['url'] = $meta['url'] ?? $defaultValue; |
||
648 | $meta['url_overwrite'] = $meta['url_overwrite'] ?? false; |
||
649 | $meta['custom'] = $meta['custom'] ?? null; |
||
650 | $meta['seo_follow'] = $meta['seo_follow'] ?? null; |
||
651 | $meta['seo_index'] = $meta['seo_index'] ?? null; |
||
652 | $meta['data'] = $meta['data'] ?? null; |
||
653 | |||
654 | return $meta; |
||
655 | } |
||
656 | |||
657 | private function getNewMetaId(array $meta, string $defaultValue): int |
||
658 | { |
||
659 | $meta = $this->completeMetaRecord($meta, $defaultValue); |
||
660 | |||
661 | return $this->insertMeta( |
||
662 | $meta['keywords'], |
||
663 | $meta['description'], |
||
664 | $meta['title'], |
||
665 | $meta['url'], |
||
666 | $meta['keywords_overwrite'], |
||
667 | $meta['description_overwrite'], |
||
668 | $meta['title_overwrite'], |
||
669 | $meta['url_overwrite'], |
||
670 | $meta['custom'], |
||
671 | $meta['seo_follow'], |
||
672 | $meta['seo_index'], |
||
673 | $meta['data'] |
||
674 | ); |
||
675 | } |
||
676 | |||
677 | private function completePageRevisionRecord(array $revision, array $meta = []): array |
||
678 | { |
||
679 | $revision['id'] = $revision['id'] ?? $this->getNextPageIdForLanguage($revision['language']); |
||
680 | $revision['user_id'] = $revision['user_id'] ?? $this->getDefaultUserID(); |
||
681 | $revision['template_id'] = $revision['template_id'] ?? $this->getTemplateId('Default'); |
||
682 | $revision['type'] = $revision['type'] ?? 'page'; |
||
683 | $revision['parent_id'] = $revision['parent_id'] ?? ( |
||
684 | $revision['type'] === 'page' ? Model::HOME_PAGE_ID : BackendPagesModel::NO_PARENT_PAGE_ID |
||
685 | ); |
||
686 | $revision['navigation_title'] = $revision['navigation_title'] ?? $revision['title']; |
||
687 | $revision['navigation_title_overwrite'] = $revision['navigation_title_overwrite'] ?? false; |
||
688 | $revision['hidden'] = $revision['hidden'] ?? false; |
||
689 | $revision['status'] = $revision['status'] ?? 'active'; |
||
690 | $revision['publish_on'] = $revision['publish_on'] ?? gmdate('Y-m-d H:i:s'); |
||
691 | $revision['created_on'] = $revision['created_on'] ?? gmdate('Y-m-d H:i:s'); |
||
692 | $revision['edited_on'] = $revision['edited_on'] ?? gmdate('Y-m-d H:i:s'); |
||
693 | $revision['data'] = $revision['data'] ?? null; |
||
694 | $revision['allow_move'] = $revision['allow_move'] ?? true; |
||
695 | $revision['allow_children'] = $revision['allow_children'] ?? true; |
||
696 | $revision['allow_edit'] = $revision['allow_edit'] ?? true; |
||
697 | $revision['allow_delete'] = $revision['allow_delete'] ?? true; |
||
698 | $revision['sequence'] = $revision['sequence'] ?? $this->getNextPageSequence( |
||
699 | $revision['language'], |
||
700 | $revision['parent_id'], |
||
701 | $revision['type'] |
||
702 | ); |
||
703 | $revision['meta_id'] = $revision['meta_id'] ?? $this->getNewMetaId($meta, $revision['title']); |
||
704 | foreach ($this->getLanguages() as $language) { |
||
705 | if ($language !== $revision['language']) { |
||
706 | $revision['data']['hreflang_' . $language] = $revision['id']; |
||
707 | } |
||
708 | } |
||
709 | if (!isset($revision['data']['image']) && $this->installExample()) { |
||
710 | $revision['data']['image'] = $this->getAndCopyRandomImage(); |
||
711 | } |
||
712 | if ($revision['data'] !== null) { |
||
713 | $revision['data'] = serialize($revision['data']); |
||
714 | } |
||
715 | |||
716 | return $revision; |
||
717 | } |
||
718 | |||
719 | /** |
||
720 | * Insert a page |
||
721 | * |
||
722 | * @param array $revision An array with the revision data. |
||
723 | * @param array $meta The meta-data. |
||
724 | * @param array[] $blocks The blocks. |
||
725 | * |
||
726 | * @throws \SpoonDatabaseException |
||
727 | * @throws \SpoonException |
||
728 | * |
||
729 | * @return int |
||
730 | */ |
||
731 | protected function insertPage(array $revision, array $meta = null, array ...$blocks): int |
||
732 | { |
||
733 | // build revision |
||
734 | if (!isset($revision['language'])) { |
||
735 | throw new \SpoonException('language is required for installing pages'); |
||
736 | } |
||
737 | if (!isset($revision['title'])) { |
||
738 | throw new \SpoonException('title is required for installing pages'); |
||
739 | } |
||
740 | // deactivate previous page revisions |
||
741 | if (isset($revision['id'])) { |
||
742 | $this->archiveAllRevisionsOfAPageForLanguage($revision['id'], $revision['language']); |
||
743 | } |
||
744 | |||
745 | $revision = $this->completePageRevisionRecord($revision, (array) $meta); |
||
746 | |||
747 | // insert page |
||
748 | $revision['revision_id'] = $this->getDatabase()->insert('pages', $revision); |
||
749 | |||
750 | if (empty($blocks)) { |
||
751 | return $revision['id']; |
||
752 | } |
||
753 | |||
754 | $this->getDatabase()->insert( |
||
755 | 'pages_blocks', |
||
756 | $this->completePageBlockRecords($blocks, $revision['revision_id']) |
||
757 | ); |
||
758 | |||
759 | // return page id |
||
760 | return $revision['id']; |
||
761 | } |
||
762 | |||
763 | private function completePageBlockRecords(array $blocks, int $defaultRevisionId): array |
||
764 | { |
||
765 | // array of positions and linked blocks (will be used to automatically set block sequence) |
||
766 | $positions = []; |
||
767 | |||
768 | return array_map( |
||
769 | function (array $block) use (&$positions, $defaultRevisionId) { |
||
770 | $block['position'] = $block['position'] ?? 'main'; |
||
771 | $positions[$block['position']][] = $block; |
||
772 | $block['revision_id'] = $block['revision_id'] ?? $defaultRevisionId; |
||
773 | $block['created_on'] = $block['created_on'] ?? gmdate('Y-m-d H:i:s'); |
||
774 | $block['edited_on'] = $block['edited_on'] ?? gmdate('Y-m-d H:i:s'); |
||
775 | $block['extra_id'] = $block['extra_id'] ?? null; |
||
776 | $block['visible'] = $block['visible'] ?? true; |
||
777 | $block['sequence'] = $block['sequence'] ?? count($positions[$block['position']]) - 1; |
||
778 | $block['html'] = $block['html'] ?? ''; |
||
779 | |||
780 | // get the html from the template file if it is defined |
||
781 | if (!empty($block['html'])) { |
||
782 | $block['html'] = file_get_contents($block['html']); |
||
783 | } |
||
784 | |||
785 | // sort array by its keys, so the array is always the same for SpoonDatabase::insert, |
||
786 | // when you don't provide an array with arrays sorted in the same order, the fields get |
||
787 | // mixed into different columns |
||
788 | ksort($block); |
||
789 | |||
790 | return $block; |
||
791 | }, |
||
792 | $blocks |
||
793 | ); |
||
794 | } |
||
795 | |||
796 | /** |
||
797 | * Should example data be installed |
||
798 | * |
||
799 | * @return bool |
||
800 | */ |
||
801 | protected function installExample(): bool |
||
804 | } |
||
805 | |||
806 | /** |
||
807 | * Make a module searchable |
||
808 | * |
||
809 | * @param string $module The module to make searchable. |
||
810 | * @param bool $searchable Enable/disable search for this module by default? |
||
811 | * @param int $weight Set default search weight for this module. |
||
812 | */ |
||
813 | protected function makeSearchable(string $module, bool $searchable = true, int $weight = 1): void |
||
814 | { |
||
819 | ); |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Set the rights for an action |
||
824 | * |
||
825 | * @param int $groupId The group wherefore the rights will be set. |
||
826 | * @param string $module The module wherein the action appears. |
||
827 | * @param string $action The action wherefore the rights have to set. |
||
828 | * @param int $level The level, default is 7 (max). |
||
829 | */ |
||
830 | protected function setActionRights(int $groupId, string $module, string $action, int $level = 7): void |
||
831 | { |
||
832 | // check if the action already exists |
||
833 | $actionRightAlreadyExist = (bool) $this->getDatabase()->getVar( |
||
834 | 'SELECT 1 |
||
835 | FROM groups_rights_actions |
||
836 | WHERE group_id = ? AND module = ? AND action = ? |
||
837 | LIMIT 1', |
||
838 | [$groupId, $module, $action] |
||
839 | ); |
||
840 | |||
841 | if ($actionRightAlreadyExist) { |
||
842 | return; |
||
843 | } |
||
844 | |||
845 | $this->getDatabase()->insert( |
||
846 | 'groups_rights_actions', |
||
847 | [ |
||
848 | 'group_id' => $groupId, |
||
849 | 'module' => $module, |
||
850 | 'action' => $action, |
||
851 | 'level' => $level, |
||
852 | ] |
||
853 | ); |
||
854 | } |
||
855 | |||
856 | /** |
||
857 | * Sets the rights for a module |
||
858 | * |
||
859 | * @param int $groupId The group wherefore the rights will be set. |
||
860 | * @param string $module The module too set the rights for. |
||
861 | */ |
||
862 | protected function setModuleRights(int $groupId, string $module): void |
||
881 | ] |
||
882 | ); |
||
883 | } |
||
884 | |||
885 | private function getNextBackendNavigationSequence(int $parentId): int |
||
886 | { |
||
887 | // get maximum sequence for this parent |
||
888 | $currentMaxBackendNavigationSequence = (int) $this->getDatabase()->getVar( |
||
889 | 'SELECT MAX(sequence) |
||
890 | FROM backend_navigation |
||
891 | WHERE parent_id = ?', |
||
892 | [$parentId] |
||
893 | ); |
||
894 | |||
895 | return ++$currentMaxBackendNavigationSequence; |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Set a new navigation item. |
||
900 | * |
||
901 | * @param int|null $parentId Id of the navigation item under we should add this. |
||
902 | * @param string $label Label for the item. |
||
903 | * @param string|null $url Url for the item. If omitted the first child is used. |
||
904 | * @param array $selectedFor Set selected when these actions are active. |
||
905 | * @param int $sequence Sequence to use for this item. |
||
906 | * |
||
907 | * @return int |
||
908 | */ |
||
909 | protected function setNavigation( |
||
943 | ] |
||
944 | ); |
||
945 | } |
||
946 | |||
947 | /** |
||
948 | * Stores a module specific setting in the database. |
||
949 | * |
||
950 | * @param string $module The module wherefore the setting will be set. |
||
951 | * @param string $name The name of the setting. |
||
952 | * @param mixed $value The optional value. |
||
953 | * @param bool $overwrite Overwrite no matter what. |
||
954 | */ |
||
955 | protected function setSetting(string $module, string $name, $value = null, bool $overwrite = false): void |
||
989 | ] |
||
990 | ); |
||
991 | } |
||
992 | |||
993 | private function getAndCopyRandomImage(): string |
||
1012 | } |
||
1013 | } |
||
1014 |