Issues (281)

Branch: master

src/Backend/Core/Engine/Model.php (4 issues)

1
<?php
2
3
namespace Backend\Core\Engine;
4
5
use Common\ModuleExtraType;
6
use InvalidArgumentException;
7
use Symfony\Component\Filesystem\Filesystem;
8
use Symfony\Component\Finder\Finder;
9
use Backend\Modules\Extensions\Engine\Model as BackendExtensionsModel;
10
use Backend\Modules\Pages\Engine\Model as BackendPagesModel;
11
use Backend\Core\Engine\Model as BackendModel;
12
use Frontend\Core\Language\Language as FrontendLanguage;
13
use Backend\Core\Language\Language as BackendLanguage;
14
15
/**
16
 * In this file we store all generic functions that we will be using in the backend.
17
 */
18
class Model extends \Common\Core\Model
19
{
20
    /**
21
     * Checks the settings and optionally returns an array with warnings
22
     *
23
     * @return array
24
     */
25 2
    public static function checkSettings(): array
26
    {
27 2
        $warnings = [];
28
29
        // check if debug-mode is active
30 2
        if (BackendModel::getContainer()->getParameter('kernel.debug')) {
31 2
            $warnings[] = ['message' => BackendLanguage::err('DebugModeIsActive')];
32
        }
33
34
        // check for extensions warnings
35 2
        $warnings = array_merge($warnings, BackendExtensionsModel::checkSettings());
36
37 2
        return $warnings;
38
    }
39
40
    /**
41
     * Creates an URL for a given action and module
42
     * If you don't specify an action the current action will be used.
43
     * If you don't specify a module the current module will be used.
44
     * If you don't specify a language the current language will be used.
45
     *
46
     * @param string $action The action to build the URL for.
47
     * @param string $module The module to build the URL for.
48
     * @param string $language The language to use, if not provided we will use the working language.
49
     * @param array $parameters GET-parameters to use.
50
     * @param bool $encodeSquareBrackets Should the square brackets be allowed so we can use them in de datagrid?
51
     *
52
     * @throws \Exception If $action, $module or both are not set
53
     *
54
     * @return string
55
     */
56 131
    public static function createUrlForAction(
57
        string $action = null,
58
        string $module = null,
59
        string $language = null,
60
        array $parameters = null,
61
        bool $encodeSquareBrackets = true
62
    ): string {
63 131
        $language = $language ?? BackendLanguage::getWorkingLanguage();
64
65
        // checking if we have an url, because in a cronjob we don't have one
66 131
        if (self::getContainer()->has('url')) {
67
            // grab the URL from the reference
68 131
            $url = self::getContainer()->get('url');
69 131
            $action = $action ?? $url->getAction();
70 131
            $module = $module ?? $url->getModule();
71
        }
72
73
        // error checking
74 131
        if ($action === null || $module === null) {
75
            throw new \Exception('Action and Module must not be empty when creating an url.');
76
        }
77
78 131
        $parameters['token'] = self::getToken();
79 131
        if (self::requestIsAvailable()) {
80 131
            $queryParameterBag = self::getRequest()->query;
81
82
            // add offset, order & sort (only if not yet manually added)
83 131
            if (!isset($parameters['offset']) && $queryParameterBag->has('offset')) {
84
                $parameters['offset'] = $queryParameterBag->getInt('offset');
85
            }
86 131
            if (!isset($parameters['order']) && $queryParameterBag->has('order')) {
87
                $parameters['order'] = $queryParameterBag->get('order');
88
            }
89 131
            if (!isset($parameters['sort']) && $queryParameterBag->has('sort')) {
90
                $parameters['sort'] = $queryParameterBag->get('sort');
91
            }
92
        }
93
94 131
        $queryString = '?' . http_build_query($parameters);
0 ignored issues
show
It seems like $parameters can also be of type null; however, parameter $data of http_build_query() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

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

94
        $queryString = '?' . http_build_query(/** @scrutinizer ignore-type */ $parameters);
Loading history...
95
96 131
        if (!$encodeSquareBrackets) {
97
            // we use things like [id] to parse database column data in so we need to unescape those
98 22
            $queryString = str_replace([urlencode('['), urlencode(']')], ['[', ']'], $queryString);
99
        }
100
101 131
        return self::get('router')->generate(
102 131
            'backend',
103
            [
104 131
                '_locale' => $language,
105 131
                'module' => self::camelCaseToLowerSnakeCase($module),
106 131
                'action' => self::camelCaseToLowerSnakeCase($action),
107
            ]
108 131
        ) . $queryString;
109
    }
110
111
    /**
112
     * @param string $string
113
     *
114
     * @return string
115
     */
116 131
    public static function camelCaseToLowerSnakeCase(string $string): string
117
    {
118 131
        return mb_strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $string));
119
    }
120
121
    /**
122
     * Delete a page extra by module, type or data.
123
     *
124
     * Data is a key/value array. Example: array(id => 23, language => nl);
125
     *
126
     * @param string $module The module wherefore the extra exists.
127
     * @param string $type The type of extra, possible values are block, homepage, widget.
128
     * @param array $data Extra data that exists.
129
     */
130
    public static function deleteExtra(string $module = null, string $type = null, array $data = null): void
131
    {
132
        // init
133
        $query = 'SELECT i.id, i.data FROM modules_extras AS i WHERE 1';
134
        $parameters = [];
135
136
        // module
137
        if ($module !== null) {
138
            $query .= ' AND i.module = ?';
139
            $parameters[] = $module;
140
        }
141
142
        // type
143
        if ($type !== null) {
144
            $query .= ' AND i.type = ?';
145
            $parameters[] = $type;
146
        }
147
148
        // get extras
149
        $extras = (array) self::getContainer()->get('database')->getRecords($query, $parameters);
150
151
        // loop found extras
152
        foreach ($extras as $extra) {
153
            // get extra data
154
            $extraData = $extra['data'] !== null ? (array) unserialize($extra['data'], ['allowed_classes' => false]) : null;
155
156
            // if we have $data parameter set and $extraData not null we should not delete such extra
157
            if ($data !== null && $extraData === null) {
158
                continue;
159
            }
160
161
            if ($data !== null && $extraData !== null) {
162
                foreach ($data as $dataKey => $dataValue) {
163
                    if (isset($extraData[$dataKey]) && $dataValue !== $extraData[$dataKey]) {
164
                        continue 2;
165
                    }
166
                }
167
            }
168
169
            self::deleteExtraById($extra['id']);
170
        }
171
    }
172
173
    /**
174
     * Delete a page extra by its id
175
     *
176
     * @param int $id The id of the extra to delete.
177
     * @param bool $deleteBlock Should the block be deleted? Default is false.
178
     */
179 2
    public static function deleteExtraById(int $id, bool $deleteBlock = false): void
180
    {
181 2
        self::getContainer()->get('database')->delete('modules_extras', 'id = ?', $id);
182
183 2
        if ($deleteBlock) {
184
            self::getContainer()->get('database')->delete('pages_blocks', 'extra_id = ?', $id);
185
186
            return;
187
        }
188
189 2
        self::getContainer()->get('database')->update(
190 2
            'pages_blocks',
191 2
            ['extra_id' => null],
192 2
            'extra_id = ?',
193 2
            $id
194
        );
195 2
    }
196
197
    /**
198
     * Delete all extras for a certain value in the data array of that module_extra.
199
     *
200
     * @param string $module The module for the extra.
201
     * @param string $field The field of the data you want to check the value for.
202
     * @param string $value The value to check the field for.
203
     * @param string $action In case you want to search for a certain action.
204
     */
205
    public static function deleteExtrasForData(
206
        string $module,
207
        string $field,
208
        string $value,
209
        string $action = null
210
    ): void {
211
        $ids = self::getExtrasForData($module, $field, $value, $action);
212
213
        // we have extras
214
        if (!empty($ids)) {
215
            // delete extras
216
            self::getContainer()->get('database')->delete('modules_extras', 'id IN (' . implode(',', $ids) . ')');
217
        }
218
    }
219
220
    /**
221
     * Generate a random string
222
     *
223
     * @param int $length Length of random string.
224
     * @param bool $numeric Use numeric characters.
225
     * @param bool $lowercase Use alphanumeric lowercase characters.
226
     * @param bool $uppercase Use alphanumeric uppercase characters.
227
     * @param bool $special Use special characters.
228
     *
229
     * @return string
230
     */
231 132
    public static function generateRandomString(
232
        int $length = 15,
233
        bool $numeric = true,
234
        bool $lowercase = true,
235
        bool $uppercase = true,
236
        bool $special = true
237
    ): string {
238 132
        $characters = '';
239 132
        $string = '';
240
241
        // possible characters
242 132
        if ($numeric) {
243 132
            $characters .= '1234567890';
244
        }
245 132
        if ($lowercase) {
246 132
            $characters .= 'abcdefghijklmnopqrstuvwxyz';
247
        }
248 132
        if ($uppercase) {
249 1
            $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
250
        }
251 132
        if ($special) {
252
            $characters .= '-_.:;,?!@#&=)([]{}*+%$';
253
        }
254
255
        // get random characters
256 132
        for ($i = 0; $i < $length; ++$i) {
257
            // random index
258 132
            $index = random_int(0, mb_strlen($characters));
259
260
            // add character to salt
261 132
            $string .= mb_substr($characters, $index, 1, self::getContainer()->getParameter('kernel.charset'));
262
        }
263
264 132
        return $string;
265
    }
266
267
    /**
268
     * Fetch the list of long date formats including examples of these formats.
269
     *
270
     * @return array
271
     */
272
    public static function getDateFormatsLong(): array
273
    {
274
        $possibleFormats = [];
275
276
        // loop available formats
277
        foreach ((array) self::get('fork.settings')->get('Core', 'date_formats_long') as $format) {
278
            // get date based on given format
279
            $possibleFormats[$format] = \SpoonDate::getDate(
280
                $format,
281
                null,
282
                Authentication::getUser()->getSetting('interface_language')
283
            );
284
        }
285
286
        return $possibleFormats;
287
    }
288
289
    /**
290
     * Fetch the list of short date formats including examples of these formats.
291
     *
292
     * @return array
293
     */
294
    public static function getDateFormatsShort(): array
295
    {
296
        $possibleFormats = [];
297
298
        // loop available formats
299
        foreach ((array) self::get('fork.settings')->get('Core', 'date_formats_short') as $format) {
300
            // get date based on given format
301
            $possibleFormats[$format] = \SpoonDate::getDate(
302
                $format,
303
                null,
304
                Authentication::getUser()->getSetting('interface_language')
305
            );
306
        }
307
308
        return $possibleFormats;
309
    }
310
311
    public static function getExtras(array $ids): array
312
    {
313
        // get database
314
        $database = self::getContainer()->get('database');
315
316
        array_walk($ids, 'intval');
317
318
        // create an array with an equal amount of question marks as ids provided
319
        $extraIdPlaceHolders = array_fill(0, count($ids), '?');
320
321
        // get extras
322
        return (array) $database->getRecords(
323
            'SELECT i.*
324
             FROM modules_extras AS i
325
             WHERE i.id IN (' . implode(', ', $extraIdPlaceHolders) . ')',
326
            $ids
327
        );
328
    }
329
330
    /**
331
     * Get extras for data
332
     *
333
     * @param string $module The module for the extra.
334
     * @param string $key The key of the data you want to check the value for.
335
     * @param string $value The value to check the key for.
336
     * @param string $action In case you want to search for a certain action.
337
     *
338
     * @return array The ids for the extras.
339
     */
340
    public static function getExtrasForData(string $module, string $key, string $value, string $action = null): array
341
    {
342
        $query = 'SELECT i.id, i.data
343
                 FROM modules_extras AS i
344
                 WHERE i.module = ? AND i.data != ?';
345
        $parameters = [$module, 'NULL'];
346
347
        // Filter on the action if it is given.
348
        if ($action !== null) {
349
            $query .= ' AND i.action = ?';
350
            $parameters[] = $action;
351
        }
352
353
        $moduleExtras = (array) self::getContainer()->get('database')->getPairs($query, $parameters);
354
355
        // No module extra's found
356
        if (empty($moduleExtras)) {
357
            return [];
358
        }
359
360
        return array_keys(
361
            array_filter(
362
                $moduleExtras,
363
                function (?string $serializedData) use ($key, $value) {
364
                    $data = $serializedData === null ? [] : unserialize($serializedData, ['allowed_classes' => false]);
365
366
                    return isset($data[$key]) && (string) $data[$key] === $value;
367
                }
368
            )
369
        );
370
    }
371
372
    /**
373
     * Get the page-keys
374
     *
375
     * @param string $language The language to use, if not provided we will use the working language.
376
     *
377
     * @return array
378
     */
379 7
    public static function getKeys(string $language = null): array
380
    {
381 7
        if ($language === null) {
382
            $language = BackendLanguage::getWorkingLanguage();
383
        }
384
385 7
        return BackendPagesModel::getCacheBuilder()->getKeys($language);
386
    }
387
388
    /**
389
     * Get the modules that are available on the filesystem
390
     *
391
     * @param bool $includeCore Should core be included as a module?
392
     *
393
     * @return array
394
     */
395 132
    public static function getModulesOnFilesystem(bool $includeCore = true): array
396
    {
397 132
        $modules = $includeCore ? ['Core'] : [];
398 132
        $finder = new Finder();
399 132
        $directories = $finder->directories()->in(__DIR__ . '/../../Modules')->depth('==0');
400 132
        foreach ($directories as $directory) {
401 132
            $modules[] = $directory->getBasename();
402
        }
403
404 132
        return $modules;
405
    }
406
407
    /**
408
     * Fetch the list of modules, but for a dropdown.
409
     *
410
     * @return array
411
     */
412
    public static function getModulesForDropDown(): array
413
    {
414
        $dropDown = ['Core' => 'Core'];
415
416
        // fetch modules
417
        $modules = self::getModules();
418
419
        // loop and add into the return-array (with correct label)
420
        foreach ($modules as $module) {
421
            $dropDown[$module] = \SpoonFilter::ucfirst(BackendLanguage::lbl(\SpoonFilter::toCamelCase($module)));
422
        }
423
424
        return $dropDown;
425
    }
426
427
    /**
428
     * Get the navigation-items
429
     *
430
     * @param string $language The language to use, if not provided we will use the working language.
431
     *
432
     * @return array
433
     */
434 7
    public static function getNavigation(string $language = null): array
435
    {
436 7
        if ($language === null) {
437
            $language = BackendLanguage::getWorkingLanguage();
438
        }
439
440 7
        $cacheBuilder = BackendPagesModel::getCacheBuilder();
441
442 7
        return $cacheBuilder->getNavigation($language);
443
    }
444
445
    /**
446
     * Fetch the list of number formats including examples of these formats.
447
     *
448
     * @return array
449
     */
450
    public static function getNumberFormats(): array
451
    {
452
        return (array) self::get('fork.settings')->get('Core', 'number_formats');
453
    }
454
455
    /**
456
     * Fetch the list of time formats including examples of these formats.
457
     *
458
     * @return array
459
     */
460
    public static function getTimeFormats(): array
461
    {
462
        $possibleFormats = [];
463
        $interfaceLanguage = Authentication::getUser()->getSetting('interface_language');
464
465
        foreach (self::get('fork.settings')->get('Core', 'time_formats') as $format) {
466
            $possibleFormats[$format] = \SpoonDate::getDate($format, null, $interfaceLanguage);
467
        }
468
469
        return $possibleFormats;
470
    }
471
472
    /**
473
     * Get the token which will protect us
474
     *
475
     * @return string
476
     */
477 131
    public static function getToken(): string
478
    {
479 131
        if (self::getSession()->has('csrf_token') && self::getSession()->get('csrf_token') !== '') {
480 131
            return self::getSession()->get('csrf_token');
481
        }
482
483 131
        $token = self::generateRandomString(10, true, true, false, false);
484 131
        self::getSession()->set('csrf_token', $token);
485
486 131
        return $token;
487
    }
488
489
    /**
490
     * Get URL for a given pageId
491
     *
492
     * @param int $pageId The id of the page to get the URL for.
493
     * @param string $language The language to use, if not provided we will use the working language.
494
     *
495
     * @return string
496
     */
497 7
    public static function getUrl(int $pageId, string $language = null): string
498
    {
499 7
        if ($language === null) {
500 5
            $language = BackendLanguage::getWorkingLanguage();
501
        }
502
503
        // Prepend the language if the site is multi language
504 7
        $url = self::getContainer()->getParameter('site.multilanguage') ? '/' . $language . '/' : '/';
505
506
        // get the menuItems
507 7
        $keys = self::getKeys($language);
508
509
        // get the URL, if it doesn't exist return 404
510 7
        if (!isset($keys[$pageId])) {
511
            return self::getUrl(BackendModel::ERROR_PAGE_ID, $language);
512
        }
513
514
        // return the unique URL!
515 7
        return urldecode($url . $keys[$pageId]);
516
    }
517
518
    /**
519
     * Get the URL for a give module & action combination
520
     *
521
     * @param string $module The module wherefore the URL should be build.
522
     * @param string $action The specific action wherefore the URL should be build.
523
     * @param string $language The language wherein the URL should be retrieved,
524
     *                         if not provided we will load the language that was provided in the URL.
525
     * @param array $data An array with keys and values that partially or fully match the data of the block.
526
     *                         If it matches multiple versions of that block it will just return the first match.
527
     *
528
     * @return string
529
     */
530 7
    public static function getUrlForBlock(
531
        string $module,
532
        string $action = null,
533
        string $language = null,
534
        array $data = null
535
    ): string {
536 7
        if ($language === null) {
537 7
            $language = BackendLanguage::getWorkingLanguage();
538
        }
539
540 7
        $pageIdForUrl = null;
541 7
        $navigation = self::getNavigation($language);
542
543 7
        $dataMatch = false;
544
        // loop types
545 7
        foreach ($navigation as $level) {
546
            // loop level
547 7
            foreach ($level as $pages) {
548
                // loop pages
549 7
                foreach ($pages as $pageId => $properties) {
550
                    // only process pages with extra_blocks that are visible
551 7
                    if (!isset($properties['extra_blocks']) || $properties['hidden']) {
552
                        continue;
553
                    }
554
555
                    // loop extras
556 7
                    foreach ($properties['extra_blocks'] as $extra) {
557
                        // direct link?
558 7
                        if ($extra['module'] === $module && $extra['action'] === $action && $extra['action'] !== null) {
559
                            // if there is data check if all the requested data matches the extra data
560
                            if ($data !== null && isset($extra['data'])
561
                                && array_intersect_assoc($data, (array) $extra['data']) !== $data
562
                            ) {
563
                                // It is the correct action but has the wrong data
564
                                continue;
565
                            }
566
567
                            // exact page was found, so return
568
                            return self::getUrl($properties['page_id'], $language);
569
                        }
570
571 7
                        if ($extra['module'] === $module && $extra['action'] === null) {
572
                            // if there is data check if all the requested data matches the extra data
573 7
                            if ($data !== null && isset($extra['data'])) {
574
                                if (array_intersect_assoc($data, (array) $extra['data']) !== $data) {
575
                                    // It is the correct module but has the wrong data
576
                                    continue;
577
                                }
578
579
                                $pageIdForUrl = (int) $pageId;
580
                                $dataMatch = true;
581
                            }
582
583 7
                            if ($data === null && $extra['data'] === null) {
584 7
                                $pageIdForUrl = (int) $pageId;
585 7
                                $dataMatch = true;
586
                            }
587
588 7
                            if (!$dataMatch) {
589 7
                                $pageIdForUrl = (int) $pageId;
590
                            }
591
                        }
592
                    }
593
                }
594
            }
595
        }
596
597
        // Page not found so return the 404 url
598 7
        if ($pageIdForUrl === null) {
599
            return self::getUrl(self::ERROR_PAGE_ID, $language);
600
        }
601
602 7
        $url = self::getUrl($pageIdForUrl, $language);
603
604
        // set locale with force
605 7
        FrontendLanguage::setLocale($language, true);
606
607
        // append action
608 7
        if ($action !== null) {
609 7
            $url .= '/' . urldecode(FrontendLanguage::act(\SpoonFilter::toCamelCase($action)));
610
        }
611
612
        // return the unique URL!
613 7
        return $url;
614
    }
615
616
    /**
617
     * Image Delete
618
     *
619
     * @param string $module Module name.
620
     * @param string $filename Filename.
621
     * @param string $subDirectory Subdirectory.
622
     * @param array $fileSizes Possible file sizes.
623
     */
624
    public static function imageDelete(
625
        string $module,
626
        string $filename,
627
        string $subDirectory = '',
628
        array $fileSizes = null
629
    ): void {
630
        if (empty($fileSizes)) {
631
            $model = get_class_vars('Backend' . \SpoonFilter::toCamelCase($module) . 'Model');
632
            $fileSizes = $model['fileSizes'];
633
        }
634
635
        // also include the source directory
636
        $fileSizes[] = 'source';
637
638
        $baseDirectory = FRONTEND_FILES_PATH . '/' . $module . (empty($subDirectory) ? '/' : '/' . $subDirectory . '/');
639
        $filesystem = new Filesystem();
640
        array_walk(
641
            $fileSizes,
642
            function (string $sizeDirectory) use ($baseDirectory, $filename, $filesystem) {
643
                $fullPath = $baseDirectory . basename($sizeDirectory) . '/' . $filename;
644
                if (is_file($fullPath)) {
645
                    $filesystem->remove($fullPath);
646
                }
647
            }
648
        );
649
    }
650
651
    /**
652
     * Insert extra
653
     *
654
     * @param ModuleExtraType $type What type do you want to insert, 'homepage', 'block' or 'widget'.
655
     * @param string $module The module you are inserting this extra for.
656
     * @param string $action The action this extra will use.
657
     * @param string $label Label which will be used when you want to connect this block.
658
     * @param array $data Containing extra variables.
659
     * @param bool $hidden Should this extra be visible in frontend or not?
660
     * @param int $sequence
661
     *
662
     * @throws Exception If extra type is not allowed
663
     *
664
     * @return int The new extra id
665
     */
666 3
    public static function insertExtra(
667
        ModuleExtraType $type,
668
        string $module,
669
        string $action = null,
670
        string $label = null,
671
        array $data = null,
672
        bool $hidden = false,
673
        int $sequence = null
674
    ): int {
675
        // return id for inserted extra
676 3
        return self::get('database')->insert(
677 3
            'modules_extras',
678
            [
679 3
                'module' => $module,
680 3
                'type' => $type,
681 3
                'label' => $label ?? $module, // if label is empty, fallback to module
682 3
                'action' => $action ?? null,
683 3
                'data' => $data === null ? null : serialize($data),
684 3
                'hidden' => $hidden,
685 3
                'sequence' => $sequence ?? self::getNextModuleExtraSequenceForModule($module),
686
            ]
687
        );
688
    }
689
690
    /**
691
     * This returns the identifier for the editor the logged in user prefers to use in forms.
692
     *
693
     * @return string
694
     */
695 131
    public static function getPreferredEditor(): string
696
    {
697 131
        $defaultPreferredEditor = self::getContainer()->getParameter('fork.form.default_preferred_editor');
698
699 131
        if (!Authentication::isLoggedIn()) {
700 131
            return $defaultPreferredEditor;
701
        }
702
703 40
        return Authentication::getUser()->getSetting('preferred_editor', $defaultPreferredEditor);
704
    }
705
706
    /**
707
     * @param string $module
708
     *
709
     * @return int
710
     */
711 2
    private static function getNextModuleExtraSequenceForModule(string $module): int
712
    {
713 2
        $database = self::get('database');
714
        // set next sequence number for this module
715 2
        $sequence = (int) $database->getVar(
716 2
            'SELECT MAX(sequence) + 1 FROM modules_extras WHERE module = ?',
717 2
            [$module]
718
        );
719
720
        // this is the first extra for this module: generate new 1000-series
721 2
        if ($sequence > 0) {
722 2
            return $sequence;
723
        }
724
725
        return (int) $database->getVar(
726
            'SELECT CEILING(MAX(sequence) / 1000) * 1000 FROM modules_extras'
727
        );
728
    }
729
730
    /**
731
     * Is module installed?
732
     *
733
     * @param string $module
734
     *
735
     * @return bool
736
     */
737 10
    public static function isModuleInstalled(string $module): bool
738
    {
739 10
        return in_array($module, self::getModules(), true);
740
    }
741
742
    /**
743
     * Submit ham, this call is intended for the marking of false positives, things that were incorrectly marked as
744
     * spam.
745
     *
746
     * @param string $userIp IP address of the comment submitter.
747
     * @param string $userAgent User agent information.
748
     * @param string $content The content that was submitted.
749
     * @param string $author Submitted name with the comment.
750
     * @param string $email Submitted email address.
751
     * @param string $url Commenter URL.
752
     * @param string $permalink The permanent location of the entry the comment was submitted to.
753
     * @param string $type May be blank, comment, trackback, pingback, or a made up value like "registration".
754
     * @param string $referrer The content of the HTTP_REFERER header should be sent here.
755
     * @param array $others Other data (the variables from $_SERVER).
756
     *
757
     * @throws Exception
758
     *
759
     * @return bool If everything went fine, true will be returned, otherwise an exception will be triggered.
760
     */
761
    public static function submitHam(
762
        string $userIp,
763
        string $userAgent,
764
        string $content,
765
        string $author = null,
766
        string $email = null,
767
        string $url = null,
768
        string $permalink = null,
769
        string $type = null,
770
        string $referrer = null,
771
        array $others = null
772
    ): bool {
773
        try {
774
            $akismet = self::getAkismet();
775
        } catch (InvalidArgumentException $invalidArgumentException) {
776
            return false;
777
        }
778
779
        // try it to decide it the item is spam
780
        try {
781
            // check with Akismet if the item is spam
782
            return $akismet->submitHam(
783
                $userIp,
784
                $userAgent,
785
                $content,
786
                $author,
787
                $email,
788
                $url,
789
                $permalink,
790
                $type,
791
                $referrer,
792
                $others
0 ignored issues
show
It seems like $others can also be of type null; however, parameter $others of ForkCMS\Utility\Akismet::submitHam() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

792
                /** @scrutinizer ignore-type */ $others
Loading history...
793
            );
794
        } catch (Exception $e) {
795
            if (BackendModel::getContainer()->getParameter('kernel.debug')) {
796
                throw $e;
797
            }
798
        }
799
800
        return false;
801
    }
802
803
    /**
804
     * Submit spam, his call is for submitting comments that weren't marked as spam but should have been.
805
     *
806
     * @param string $userIp IP address of the comment submitter.
807
     * @param string $userAgent User agent information.
808
     * @param string $content The content that was submitted.
809
     * @param string $author Submitted name with the comment.
810
     * @param string $email Submitted email address.
811
     * @param string $url Commenter URL.
812
     * @param string $permalink The permanent location of the entry the comment was submitted to.
813
     * @param string $type May be blank, comment, trackback, pingback, or a made up value like "registration".
814
     * @param string $referrer The content of the HTTP_REFERER header should be sent here.
815
     * @param array $others Other data (the variables from $_SERVER).
816
     *
817
     * @throws Exception
818
     *
819
     * @return bool If everything went fine true will be returned, otherwise an exception will be triggered.
820
     */
821
    public static function submitSpam(
822
        string $userIp,
823
        string $userAgent,
824
        string $content,
825
        string $author = null,
826
        string $email = null,
827
        string $url = null,
828
        string $permalink = null,
829
        string $type = null,
830
        string $referrer = null,
831
        array $others = null
832
    ): bool {
833
        try {
834
            $akismet = self::getAkismet();
835
        } catch (InvalidArgumentException $invalidArgumentException) {
836
            return false;
837
        }
838
839
        // try it to decide it the item is spam
840
        try {
841
            // check with Akismet if the item is spam
842
            return $akismet->submitSpam(
843
                $userIp,
844
                $userAgent,
845
                $content,
846
                $author,
847
                $email,
848
                $url,
849
                $permalink,
850
                $type,
851
                $referrer,
852
                $others
0 ignored issues
show
It seems like $others can also be of type null; however, parameter $others of ForkCMS\Utility\Akismet::submitSpam() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

852
                /** @scrutinizer ignore-type */ $others
Loading history...
853
            );
854
        } catch (Exception $e) {
855
            if (BackendModel::getContainer()->getParameter('kernel.debug')) {
856
                throw $e;
857
            }
858
        }
859
860
        return false;
861
    }
862
863
    /**
864
     * Update extra
865
     *
866
     * @param int $id The id for the extra.
867
     * @param string $key The key you want to update.
868
     * @param mixed $value The new value.
869
     *
870
     * @throws Exception If key parameter is not allowed
871
     */
872 4
    public static function updateExtra(int $id, string $key, $value): void
873
    {
874
        // define allowed keys
875 4
        $allowedKeys = ['label', 'action', 'data', 'hidden', 'sequence'];
876
877
        // key is not allowed
878 4
        if (!in_array($key, $allowedKeys, true)) {
879
            throw new Exception('The key ' . $key . ' can\'t be updated.');
880
        }
881
882
        // key is 'data' and value is not serialized
883 4
        if ($key === 'data' && is_array($value)) {
884
            // serialize value
885 4
            $value = $value === null ? null : serialize($value);
0 ignored issues
show
The condition $value === null is always false.
Loading history...
886
        }
887
888 4
        self::getContainer()->get('database')->update('modules_extras', [$key => $value], 'id = ?', [$id]);
889 4
    }
890
891
    /**
892
     * Update extra data
893
     *
894
     * @param int $id The id for the extra.
895
     * @param string $key The key in the data you want to update.
896
     * @param string|array $value The new value.
897
     */
898
    public static function updateExtraData(int $id, string $key, $value): void
899
    {
900
        $database = self::getContainer()->get('database');
901
902
        $serializedData = (string) $database->getVar(
903
            'SELECT i.data
904
             FROM modules_extras AS i
905
             WHERE i.id = ?',
906
            [$id]
907
        );
908
909
        $data = empty($serializedData) ? [] : unserialize($serializedData, ['allowed_classes' => false]);
910
        $data[$key] = $value;
911
        $database->update('modules_extras', ['data' => serialize($data)], 'id = ?', [$id]);
912
    }
913
}
914