ModuleInstaller   F
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 994
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 346
dl 0
loc 994
rs 2.48
c 0
b 0
f 0
wmc 74

38 Methods

Rating   Name   Duplication   Size   Complexity  
A getInterfaceLanguages() 0 3 1
A getDatabase() 0 3 1
A getModule() 0 3 1
A addDefaultExtra() 0 3 1
A installExample() 0 3 1
A execute() 0 2 1
A getLanguages() 0 3 1
A getDefaultExtras() 0 3 1
A getVariable() 0 3 1
A insertPage() 0 30 5
A addModule() 0 20 2
A completePageBlockRecords() 0 30 2
A archiveAllRevisionsOfAPageForLanguage() 0 7 1
A getNextPageSequence() 0 8 1
A __construct() 0 12 1
A makeSearchable() 0 6 1
B completePageRevisionRecord() 0 40 7
A importSQL() 0 11 2
A setModuleRights() 0 19 2
A insertDashboardWidget() 0 52 3
A insertMeta() 0 29 2
A getLocale() 0 15 2
A getNextPageIdForLanguage() 0 8 1
A setActionRights() 0 22 2
A getSetting() 0 8 1
A findModuleExtraId() 0 17 2
A completeMetaRecord() 0 16 1
A getNewMetaId() 0 17 1
A setNavigation() 0 34 4
A addSearchIndex() 0 36 5
A insertExtra() 0 22 2
A getTemplateId() 0 19 3
A getDefaultUserID() 0 13 2
A setSetting() 0 34 3
A getNextBackendNavigationSequence() 0 11 1
A getAndCopyRandomImage() 0 19 1
A importLocale() 0 26 3
A getNextSequenceForModule() 0 15 2

How to fix   Complexity   

Complex Class

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
2
3
namespace Backend\Core\Installer;
4
5
use Backend\Core\Engine\Model;
6
use Backend\Modules\Search\Engine\Model as BackendSearchModel;
7
use Backend\Modules\Pages\Engine\Model as BackendPagesModel;
8
use SpoonDatabase;
9
use Symfony\Component\Filesystem\Filesystem;
10
use Symfony\Component\Finder\Finder;
11
use Common\Uri as CommonUri;
12
use Backend\Modules\Locale\Engine\Model as BackendLocaleModel;
13
use Common\ModuleExtraType;
14
15
/**
16
 * The base-class for the installer
17
 */
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
247
    {
248
        return $this->interfaceLanguages;
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
802
    {
803
        return $this->example;
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
    {
815
        $this->getDatabase()->execute(
816
            'INSERT INTO search_modules (module, searchable, weight) VALUES (?, ?, ?)
817
             ON DUPLICATE KEY UPDATE searchable = ?, weight = ?',
818
            [$module, $searchable, $weight, $searchable, $weight]
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
863
    {
864
        $moduleRightAlreadyExist = (bool) $this->getDatabase()->getVar(
865
            'SELECT 1
866
             FROM groups_rights_modules
867
             WHERE group_id = ? AND module = ?
868
             LIMIT 1',
869
            [$groupId, $module]
870
        );
871
872
        if ($moduleRightAlreadyExist) {
873
            return;
874
        }
875
876
        $this->getDatabase()->insert(
877
            'groups_rights_modules',
878
            [
879
                'group_id' => $groupId,
880
                'module' => $module,
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(
910
        $parentId,
911
        string $label,
912
        string $url = null,
913
        array $selectedFor = null,
914
        int $sequence = null
915
    ): int {
916
        // if it is null we should cast it to int so we get a 0
917
        $parentId = (int) $parentId;
918
919
        $sequence = $sequence ?? $this->getNextBackendNavigationSequence($parentId);
920
921
        // get the id for this url
922
        $id = (int) $this->getDatabase()->getVar(
923
            'SELECT id
924
             FROM backend_navigation
925
             WHERE parent_id = ? AND label = ? AND url ' . ($url === null ? 'IS' : '=') . ' ?',
926
            [$parentId, $label, $url]
927
        );
928
929
        // already exists so return current id
930
        if ($id !== 0) {
931
            return $id;
932
        }
933
934
        // doesn't exist yet, add it
935
        return (int) $this->getDatabase()->insert(
936
            'backend_navigation',
937
            [
938
                'parent_id' => $parentId,
939
                'label' => $label,
940
                'url' => $url,
941
                'selected_for' => $selectedFor === null ? null : serialize($selectedFor),
942
                'sequence' => $sequence,
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
956
    {
957
        $value = serialize($value);
958
959
        if ($overwrite) {
960
            $this->getDatabase()->execute(
961
                'INSERT INTO modules_settings (module, name, value)
962
                 VALUES (?, ?, ?)
963
                 ON DUPLICATE KEY UPDATE value = ?',
964
                [$module, $name, $value, $value]
965
            );
966
967
            return;
968
        }
969
970
        // check if this setting already exists
971
        $moduleSettingAlreadyExists = (bool) $this->getDatabase()->getVar(
972
            'SELECT 1
973
             FROM modules_settings
974
             WHERE module = ? AND name = ?
975
             LIMIT 1',
976
            [$module, $name]
977
        );
978
979
        if ($moduleSettingAlreadyExists) {
980
            return;
981
        }
982
983
        $this->getDatabase()->insert(
984
            'modules_settings',
985
            [
986
                'module' => $module,
987
                'name' => $name,
988
                'value' => $value,
989
            ]
990
        );
991
    }
992
993
    private function getAndCopyRandomImage(): string
994
    {
995
        $finder = new Finder();
996
        $finder
997
            ->files()
998
            ->name('*.jpg')
999
            ->in(__DIR__ . '/Data/images');
1000
1001
        $finder = iterator_to_array($finder);
1002
        $randomImage = $finder[array_rand($finder)];
1003
        $randomName = time() . '.jpg';
1004
1005
        $fileSystem = new Filesystem();
1006
        $fileSystem->copy(
1007
            $randomImage->getRealPath(),
1008
            __DIR__ . '/../../../Frontend/Files/Pages/images/source/' . $randomName
1009
        );
1010
1011
        return $randomName;
1012
    }
1013
}
1014