Issues (93)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Extensions/SiteTreeContentReview.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SilverStripe\ContentReview\Extensions;
4
5
use Exception;
6
use SilverStripe\CMS\Model\SiteTree;
7
use SilverStripe\ContentReview\Jobs\ContentReviewNotificationJob;
8
use SilverStripe\ContentReview\Models\ContentReviewLog;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Core\Manifest\ModuleLoader;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\LiteralField;
14
use SilverStripe\Forms\FormAction;
15
use SilverStripe\Forms\CompositeField;
16
use SilverStripe\Forms\Tab;
17
use SilverStripe\Forms\DateField;
18
use SilverStripe\Forms\DateTimeField;
19
use SilverStripe\Forms\DropdownField;
20
use SilverStripe\Forms\GridField\GridField;
21
use SilverStripe\Forms\GridField\GridFieldConfig;
22
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
23
use SilverStripe\Forms\GridField\GridFieldDataColumns;
24
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
25
use SilverStripe\Forms\ListboxField;
26
use SilverStripe\Forms\OptionsetField;
27
use SilverStripe\Forms\ReadonlyField;
28
use SilverStripe\Forms\HeaderField;
29
use SilverStripe\ORM\ArrayList;
30
use SilverStripe\ORM\DataExtension;
31
use SilverStripe\ORM\DataObject;
32
use SilverStripe\ORM\DB;
33
use SilverStripe\ORM\FieldType\DBDatetime;
34
use SilverStripe\ORM\FieldType\DBDate;
35
use SilverStripe\ORM\SS_List;
36
use SilverStripe\Security\Group;
37
use SilverStripe\Security\Member;
38
use SilverStripe\Security\Permission;
39
use SilverStripe\Security\PermissionProvider;
40
use SilverStripe\Security\Security;
41
use SilverStripe\SiteConfig\SiteConfig;
42
use SilverStripe\View\Requirements;
43
use Symbiote\QueuedJobs\DataObjects\QueuedJobDescriptor;
44
use Symbiote\QueuedJobs\Services\QueuedJobService;
45
46
/**
47
 * Set dates at which content needs to be reviewed and provide a report and emails to alert
48
 * to content needing review.
49
 *
50
 * @property string $ContentReviewType
51
 * @property int    $ReviewPeriodDays
52
 * @property Date   $NextReviewDate
53
 * @property string $LastEditedByName
54
 * @property string $OwnerNames
55
 *
56
 * @method DataList ReviewLogs()
57
 * @method DataList ContentReviewGroups()
58
 * @method DataList ContentReviewUsers()
59
 */
60
class SiteTreeContentReview extends DataExtension implements PermissionProvider
61
{
62
    /**
63
     * @var array
64
     */
65
    private static $db = array(
66
        "ContentReviewType" => "Enum('Inherit, Disabled, Custom', 'Inherit')",
67
        "ReviewPeriodDays"  => "Int",
68
        "NextReviewDate"    => "Date",
69
        "LastEditedByName"  => "Varchar(255)",
70
        "OwnerNames"        => "Varchar(255)",
71
    );
72
73
    /**
74
     * @var array
75
     */
76
    private static $defaults = array(
77
        "ContentReviewType" => "Inherit",
78
    );
79
80
    /**
81
     * @var array
82
     */
83
    private static $has_many = array(
84
        "ReviewLogs" => ContentReviewLog::class,
85
    );
86
87
    /**
88
     * @var array
89
     */
90
    private static $belongs_many_many = array(
91
        "ContentReviewGroups" => Group::class,
92
        "ContentReviewUsers"  => Member::class,
93
    );
94
95
    /**
96
     * @var array
97
     */
98
    private static $schedule = array(
99
        0   => "No automatic review date",
100
        1   => "1 day",
101
        7   => "1 week",
102
        30  => "1 month",
103
        60  => "2 months",
104
        91  => "3 months",
105
        121 => "4 months",
106
        152 => "5 months",
107
        183 => "6 months",
108
        365 => "12 months",
109
    );
110
111
    /**
112
     * @return array
113
     */
114
    public static function get_schedule()
115
    {
116
        return Config::inst()->get(static::class, 'schedule');
117
    }
118
119
    /**
120
     * Takes a list of groups and members and return a list of unique member.
121
     *
122
     * @param SS_List $groups
123
     * @param SS_List $members
124
     *
125
     * @return ArrayList
126
     */
127
    public static function merge_owners(SS_List $groups, SS_List $members)
128
    {
129
        $contentReviewOwners = new ArrayList();
130
131
        if ($groups->count()) {
132
            $groupIDs = array();
133
134
            foreach ($groups as $group) {
135
                $familyIDs = $group->collateFamilyIDs();
136
137
                if (is_array($familyIDs)) {
138
                    $groupIDs = array_merge($groupIDs, array_values($familyIDs));
139
                }
140
            }
141
142
            array_unique($groupIDs);
143
144
            if (count($groupIDs)) {
145
                $groupMembers = DataObject::get(Member::class)->where("\"Group\".\"ID\" IN (" . implode(",", $groupIDs) . ")")
146
                    ->leftJoin("Group_Members", "\"Member\".\"ID\" = \"Group_Members\".\"MemberID\"")
147
                    /** @skipUpgrade */
148
                    ->leftJoin('Group', "\"Group_Members\".\"GroupID\" = \"Group\".\"ID\"");
149
150
                $contentReviewOwners->merge($groupMembers);
151
            }
152
        }
153
154
        $contentReviewOwners->merge($members);
155
        $contentReviewOwners->removeDuplicates();
156
157
        return $contentReviewOwners;
158
    }
159
160
    /**
161
     * @param FieldList $actions
162
     */
163
    public function updateCMSActions(FieldList $actions)
164
    {
165
        if (!$this->canBeReviewedBy(Security::getCurrentUser())) {
166
            return;
167
        }
168
169
        $module = ModuleLoader::getModule('silverstripe/contentreview');
170
        Requirements::css($module->getRelativeResourcePath('client/dist/styles/contentreview.css'));
171
        Requirements::javascript($module->getRelativeResourcePath('client/dist/js/contentreview.js'));
172
173
        $reviewTab = LiteralField::create('ContentReviewButton', $this->owner->renderWith(__CLASS__ . '_button'));
174
        $actions->insertAfter('MajorActions', $reviewTab);
175
    }
176
177
    /**
178
     * Returns false if the content review have disabled.
179
     *
180
     * @param SiteTree $page
181
     *
182
     * @return bool|Date
183
     */
184
    public function getReviewDate(SiteTree $page = null)
185
    {
186
        if ($page === null) {
187
            $page = $this->owner;
188
        }
189
190
        if ($page->obj('NextReviewDate')->exists()) {
191
            return $page->obj('NextReviewDate');
192
        }
193
194
        $options = $this->owner->getOptions();
195
196
        if (!$options) {
197
            return false;
198
        }
199
200
        if (!$options->ReviewPeriodDays) {
201
            return false;
202
        }
203
204
        // Failover to check on ReviewPeriodDays + LastEdited
205
        $nextReviewUnixSec = strtotime(' + ' . $options->ReviewPeriodDays . ' days', DBDatetime::now()->getTimestamp());
206
        $date = DBDate::create('NextReviewDate');
207
        $date->setValue($nextReviewUnixSec);
208
209
        return $date;
210
    }
211
212
    /**
213
     * Get the object that have the information about the content review settings. Either:
214
     *
215
     *  - a SiteTreeContentReview decorated object
216
     *  - the default SiteTree config
217
     *  - false if this page have it's content review disabled
218
     *
219
     * Will go through parents and root pages will use the site config if their setting is Inherit.
220
     *
221
     * @return bool|DataObject
222
     *
223
     * @throws Exception
224
     */
225
    public function getOptions()
226
    {
227
        if ($this->owner->ContentReviewType == "Custom") {
228
            return $this->owner;
229
        }
230
231
        if ($this->owner->ContentReviewType == "Disabled") {
232
            return false;
233
        }
234
235
        $page = $this->owner;
236
237
        // $page is inheriting it's settings from it's parent, find
238
        // the first valid parent with a valid setting
239
        while ($parent = $page->Parent()) {
240
            // Root page, use site config
241
            if (!$parent->exists()) {
242
                return SiteConfig::current_site_config();
243
            }
244
245
            if ($parent->ContentReviewType == "Custom") {
246
                return $parent;
247
            }
248
249
            if ($parent->ContentReviewType == "Disabled") {
250
                return false;
251
            }
252
253
            $page = $parent;
254
        }
255
256
        throw new Exception("This shouldn't really happen, as per usual developer logic.");
257
    }
258
259
    /**
260
     * @return string
261
     */
262
    public function getOwnerNames()
263
    {
264
        $options = $this->getOptions();
265
266
        $names = array();
267
268
        if (!$options) {
269
            return "";
270
        }
271
272
        foreach ($options->OwnerGroups() as $group) {
273
            $names[] = $group->getBreadcrumbs(" > ");
274
        }
275
276
        foreach ($options->OwnerUsers() as $group) {
277
            $names[] = $group->getName();
278
        }
279
280
        return implode(", ", $names);
281
    }
282
283
    /**
284
     * @return null|string
285
     */
286
    public function getEditorName()
287
    {
288
        $member = Security::getCurrentUser();
289
290
        if ($member) {
291
            return $member->getTitle();
292
        }
293
294
        return null;
295
    }
296
297
    /**
298
     * Get all Members that are Content Owners to this page. This includes checking group
299
     * hierarchy and adding any direct users.
300
     *
301
     * @return ArrayList
302
     */
303
    public function ContentReviewOwners()
304
    {
305
        return SiteTreeContentReview::merge_owners(
306
            $this->OwnerGroups(),
307
            $this->OwnerUsers()
308
        );
309
    }
310
311
    /**
312
     * @return ManyManyList
313
     */
314
    public function OwnerGroups()
315
    {
316
        return $this->owner->getManyManyComponents("ContentReviewGroups");
317
    }
318
319
    /**
320
     * @return ManyManyList
321
     */
322
    public function OwnerUsers()
323
    {
324
        return $this->owner->getManyManyComponents("ContentReviewUsers");
325
    }
326
327
    /**
328
     * @param FieldList $fields
329
     */
330
    public function updateSettingsFields(FieldList $fields)
331
    {
332
        $module = ModuleLoader::getModule('silverstripe/contentreview');
333
        Requirements::javascript($module->getRelativeResourcePath('client/dist/js/contentreview.js'));
334
335
        // Display read-only version only
336
        if (!Permission::check("EDIT_CONTENT_REVIEW_FIELDS")) {
337
            $schedule = self::get_schedule();
338
            $contentOwners = ReadonlyField::create("ROContentOwners", _t("ContentReview.CONTENTOWNERS", "Content Owners"), $this->getOwnerNames());
339
            $nextReviewAt = DateField::create('RONextReviewDate', _t("ContentReview.NEXTREVIEWDATE", "Next review date"), $this->owner->NextReviewDate);
340
341
            if (!isset($schedule[$this->owner->ReviewPeriodDays])) {
342
                $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $schedule[0]);
343
            } else {
344
                $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $schedule[$this->owner->ReviewPeriodDays]);
345
            }
346
347
            $logConfig = GridFieldConfig::create()
348
                ->addComponent(Injector::inst()->create(GridFieldSortableHeader::class))
349
                ->addComponent($logColumns = Injector::inst()->create(GridFieldDataColumns::class));
350
351
            // Cast the value to the users preferred date format
352
            $logColumns->setFieldCasting(array(
353
                'Created' => DateTimeField::class . '->value',
354
            ));
355
356
            $logs = GridField::create("ROReviewNotes", "Review Notes", $this->owner->ReviewLogs(), $logConfig);
357
358
359
            $optionsFrom = ReadonlyField::create("ROType", _t("ContentReview.SETTINGSFROM", "Options are"), $this->owner->ContentReviewType);
360
361
            $fields->addFieldsToTab("Root.ContentReview", array(
362
                $contentOwners,
363
                $nextReviewAt->performReadonlyTransformation(),
364
                $reviewFreq,
365
                $optionsFrom,
366
                $logs,
367
            ));
368
369
            return;
370
        }
371
372
        $options = array();
373
        $options["Disabled"] = _t("ContentReview.DISABLE", "Disable content review");
374
        $options["Inherit"] = _t("ContentReview.INHERIT", "Inherit from parent page");
375
        $options["Custom"] = _t("ContentReview.CUSTOM", "Custom settings");
376
377
        $viewersOptionsField = OptionsetField::create("ContentReviewType", _t("ContentReview.OPTIONS", "Options"), $options);
378
379
        $users = Permission::get_members_by_permission(array("CMS_ACCESS_CMSMain", "ADMIN"));
380
381
        $usersMap = $users->map("ID", "Title")->toArray();
382
383
        asort($usersMap);
384
385
        $userField = ListboxField::create("OwnerUsers", _t("ContentReview.PAGEOWNERUSERS", "Users"), $usersMap)
386
            ->addExtraClass('custom-setting')
387
            ->setAttribute("data-placeholder", _t("ContentReview.ADDUSERS", "Add users"))
388
            ->setDescription(_t('ContentReview.OWNERUSERSDESCRIPTION', 'Page owners that are responsible for reviews'));
389
390
        $groupsMap = array();
391
392
        foreach (Group::get() as $group) {
393
            $groupsMap[$group->ID] = $group->getBreadcrumbs(" > ");
394
        }
395
        asort($groupsMap);
396
397
        $groupField = ListboxField::create("OwnerGroups", _t("ContentReview.PAGEOWNERGROUPS", "Groups"), $groupsMap)
398
            ->addExtraClass('custom-setting')
399
            ->setAttribute("data-placeholder", _t("ContentReview.ADDGROUP", "Add groups"))
400
            ->setDescription(_t("ContentReview.OWNERGROUPSDESCRIPTION", "Page owners that are responsible for reviews"));
401
402
        $reviewDate = DateField::create("NextReviewDate", _t("ContentReview.NEXTREVIEWDATE", "Next review date"))
403
            ->setDescription(_t("ContentReview.NEXTREVIEWDATADESCRIPTION", "Leave blank for no review"));
404
405
        $reviewFrequency = DropdownField::create(
406
            "ReviewPeriodDays",
407
            _t("ContentReview.REVIEWFREQUENCY", "Review frequency"),
408
            self::get_schedule()
409
        )
410
            ->addExtraClass('custom-setting')
411
            ->setDescription(_t("ContentReview.REVIEWFREQUENCYDESCRIPTION", "The review date will be set to this far in the future whenever the page is published"));
412
413
        $notesField = GridField::create("ReviewNotes", "Review Notes", $this->owner->ReviewLogs(), GridFieldConfig_RecordEditor::create());
414
415
        $fields->addFieldsToTab("Root.ContentReview", array(
416
            HeaderField::create('ContentReviewHeader', _t("ContentReview.REVIEWHEADER", "Content review"), 2),
417
            $viewersOptionsField,
418
            CompositeField::create(
419
                $userField,
420
                $groupField,
421
                $reviewDate,
422
                $reviewFrequency
423
            )->addExtraClass("review-settings"),
424
            ReadonlyField::create("ROContentOwners", _t("ContentReview.CONTENTOWNERS", "Content Owners"), $this->getOwnerNames()),
425
            $notesField,
426
        ));
427
    }
428
429
    /**
430
     * Creates a ContentReviewLog and connects it to this Page.
431
     *
432
     * @param Member $reviewer
433
     * @param string $message
434
     */
435
    public function addReviewNote(Member $reviewer, $message)
436
    {
437
        $reviewLog = ContentReviewLog::create();
438
        $reviewLog->Note = $message;
439
        $reviewLog->ReviewerID = $reviewer->ID;
440
        $this->owner->ReviewLogs()->add($reviewLog);
441
    }
442
443
    /**
444
     * Advance review date to the next date based on review period or set it to null
445
     * if there is no schedule. Returns true if date was required and false is content
446
     * review is 'off'.
447
     *
448
     * @return bool
449
     */
450
    public function advanceReviewDate()
451
    {
452
        $nextDateTimestamp = false;
453
        $options = $this->getOptions();
454
455
        if ($options && $options->ReviewPeriodDays) {
456
            $nextDateTimestamp = strtotime(
457
                ' + ' . $options->ReviewPeriodDays . ' days',
458
                DBDatetime::now()->getTimestamp()
459
            );
460
461
            $this->owner->NextReviewDate = DBDate::create()->setValue($nextDateTimestamp)->Format('y-MM-dd');
462
            $this->owner->write();
463
        }
464
465
        return (bool) $nextDateTimestamp;
466
    }
467
468
    /**
469
     * Check if a review is due by a member for this owner.
470
     *
471
     * @param Member $member
472
     *
473
     * @return bool
474
     */
475
    public function canBeReviewedBy(Member $member = null)
476
    {
477
        if (!$this->owner->obj("NextReviewDate")->exists()) {
478
            return false;
479
        }
480
481
        if ($this->owner->obj("NextReviewDate")->InFuture()) {
482
            return false;
483
        }
484
485
        $options = $this->getOptions();
486
487
        if (!$options) {
488
            return false;
489
        }
490
491
        if (!$options
492
            // Options can be a SiteConfig with different extension applied
493
            || (!$options->hasExtension(__CLASS__) && !$options->hasExtension(ContentReviewDefaultSettings::class))
494
        ) {
495
            return false;
496
        }
497
498
        if ($options->OwnerGroups()->count() == 0 && $options->OwnerUsers()->count() == 0) {
499
            return false;
500
        }
501
502
        if (!$member) {
503
            return true;
504
        }
505
506
        if ($member->inGroups($options->OwnerGroups())) {
507
            return true;
508
        }
509
510
        if ($options->OwnerUsers()->find("ID", $member->ID)) {
511
            return true;
512
        }
513
514
        return false;
515
    }
516
517
    /**
518
     * Set the review data from the review period, if set.
519
     */
520
    public function onBeforeWrite()
521
    {
522
        // Only update if DB fields have been changed
523
        $changedFields = $this->owner->getChangedFields(true, 2);
524
        if ($changedFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $changedFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
525
            $this->owner->LastEditedByName = $this->owner->getEditorName();
526
            $this->owner->OwnerNames = $this->owner->getOwnerNames();
527
        }
528
529
        // If the user changed the type, we need to recalculate the review date.
530
        if ($this->owner->isChanged("ContentReviewType", 2)) {
531
            if ($this->owner->ContentReviewType == "Disabled") {
532
                $this->setDefaultReviewDateForDisabled();
533
            } elseif ($this->owner->ContentReviewType == "Custom") {
534
                $this->setDefaultReviewDateForCustom();
535
            } else {
536
                $this->setDefaultReviewDateForInherited();
537
            }
538
        }
539
540
        // Ensure that a inherited page always have a next review date
541
        if ($this->owner->ContentReviewType == "Inherit" && !$this->owner->NextReviewDate) {
542
            $this->setDefaultReviewDateForInherited();
543
        }
544
545
        // We need to update all the child pages that inherit this setting. We can only
546
        // change children after this record has been created, otherwise the stageChildren
547
        // method will grab all pages in the DB (this messes up unit testing)
548
        if (!$this->owner->exists()) {
549
            return;
550
        }
551
552
        // parent page change its review period
553
        // && !$this->owner->isChanged('ContentReviewType', 2)
554
        if ($this->owner->isChanged('ReviewPeriodDays', 2)) {
555
            $nextReviewUnixSec = strtotime(
556
                ' + ' . $this->owner->ReviewPeriodDays . ' days',
557
                DBDatetime::now()->getTimestamp()
558
            );
559
            $this->owner->NextReviewDate = DBDate::create()->setValue($nextReviewUnixSec)->Format('y-MM-dd');
560
        }
561
    }
562
563
    private function setDefaultReviewDateForDisabled()
564
    {
565
        $this->owner->NextReviewDate = null;
566
    }
567
568
    protected function setDefaultReviewDateForCustom()
569
    {
570
        // Don't overwrite existing value
571
        if ($this->owner->NextReviewDate) {
572
            return;
573
        }
574
575
        $this->owner->NextReviewDate = null;
576
        $nextDate = $this->getReviewDate();
577
578 View Code Duplication
        if (is_object($nextDate)) {
579
            $this->owner->NextReviewDate = $nextDate->getValue();
580
        } else {
581
            $this->owner->NextReviewDate = $nextDate;
582
        }
583
    }
584
585
    protected function setDefaultReviewDateForInherited()
586
    {
587
        // Don't overwrite existing value
588
        if ($this->owner->NextReviewDate) {
589
            return;
590
        }
591
592
        $options = $this->getOptions();
593
        $nextDate = null;
594
595
        if ($options instanceof SiteTree) {
596
            $nextDate = $this->getReviewDate($options);
597
        } elseif ($options instanceof SiteConfig) {
598
            $nextDate = $this->getReviewDate();
599
        }
600
601 View Code Duplication
        if (is_object($nextDate)) {
602
            $this->owner->NextReviewDate = $nextDate->getValue();
603
        } else {
604
            $this->owner->NextReviewDate = $nextDate;
605
        }
606
    }
607
608
    /**
609
     * Provide permissions to the CMS.
610
     *
611
     * @return array
612
     */
613
    public function providePermissions()
614
    {
615
        return array(
616
            "EDIT_CONTENT_REVIEW_FIELDS" => array(
617
                "name"     => "Set content owners and review dates",
618
                "category" => _t("Permissions.CONTENT_CATEGORY", "Content permissions"),
619
                "sort"     => 50,
620
            ),
621
        );
622
    }
623
624
    /**
625
     * If the queued jobs module is installed, queue up the first job for 9am tomorrow morning
626
     * (by default).
627
     */
628
    public function requireDefaultRecords()
629
    {
630
        if (class_exists(ContentReviewNotificationJob::class)) {
631
            // Ensure there is not already a job queued
632
            if (QueuedJobDescriptor::get()->filter("Implementation", ContentReviewNotificationJob::class)->first()) {
633
                return;
634
            }
635
636
            $nextRun = Injector::inst()->create(ContentReviewNotificationJob::class);
637
            $runHour = Config::inst()->get(ContentReviewNotificationJob::class, "first_run_hour");
638
            $firstRunTime = date("Y-m-d H:i:s", mktime($runHour, 0, 0, date("m"), date("d") + 1, date("y")));
639
640
            singleton(QueuedJobService::class)->queueJob(
641
                $nextRun,
642
                $firstRunTime
643
            );
644
645
            DB::alteration_message(sprintf("Added ContentReviewNotificationJob to run at %s", $firstRunTime));
646
        }
647
    }
648
}
649