Issues (47)

src/CronJob.php (12 issues)

1
<?php
2
3
namespace LeKoala\SimpleJobs;
4
5
use Exception;
6
use Cron\CronExpression;
7
use LeKoala\CmsActions\CustomAction;
0 ignored issues
show
The type LeKoala\CmsActions\CustomAction was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use SilverStripe\ORM\DataList;
9
use SilverStripe\Core\ClassInfo;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Forms\GridField\GridField;
15
use SilverStripe\CronTask\Interfaces\CronTask;
16
use SilverStripe\Forms\GridField\GridFieldConfig_RecordViewer;
17
use SilverStripe\Security\Permission;
18
19
/**
20
 * Class \LeKoala\SimpleJobs\CronJob
21
 *
22
 * @property ?string $TaskClass
23
 * @property ?string $Title
24
 * @property ?string $Category
25
 * @property ?string $Description
26
 * @property bool|int $Disabled
27
 * @mixin \SilverStripe\Assets\Shortcodes\FileLinkTracking
28
 * @mixin \SilverStripe\Assets\AssetControlExtension
29
 * @mixin \SilverStripe\CMS\Model\SiteTreeLinkTracking
30
 * @mixin \SilverStripe\Versioned\RecursivePublishable
31
 * @mixin \SilverStripe\Versioned\VersionedStateExtension
32
 */
33
class CronJob extends DataObject
34
{
35
    /**
36
     * @var string
37
     */
38
    private static $singular_name = 'Scheduled Job';
0 ignored issues
show
The private property $singular_name is not used, and could be removed.
Loading history...
39
40
    /**
41
     * @var string
42
     */
43
    private static $plural_name = 'Scheduled Jobs';
0 ignored issues
show
The private property $plural_name is not used, and could be removed.
Loading history...
44
45
    /**
46
     * @var string
47
     */
48
    private static $table_name = 'CronJob';
0 ignored issues
show
The private property $table_name is not used, and could be removed.
Loading history...
49
50
    /**
51
     * @var array<string, string>
52
     */
53
    private static $db = [
0 ignored issues
show
The private property $db is not used, and could be removed.
Loading history...
54
        'TaskClass' => 'Varchar(255)',
55
        'Title' => 'Varchar(255)',
56
        'Category' => 'Varchar(255)',
57
        'Description' => 'Varchar(255)',
58
        'Disabled' => 'Boolean',
59
    ];
60
61
    /**
62
     * @var string
63
     */
64
    private static $default_sort = 'TaskClass ASC';
0 ignored issues
show
The private property $default_sort is not used, and could be removed.
Loading history...
65
66
    /**
67
     * @var array<string, string>
68
     */
69
    private static $summary_fields = [
0 ignored issues
show
The private property $summary_fields is not used, and could be removed.
Loading history...
70
        'Title' => 'Title',
71
        'Category' => 'Category',
72
        'IsDisabled' => 'IsDisabled',
73
        'Description' => 'Description',
74
        'LastResult.Created' => 'Last Run',
75
        'NextRun' => 'Next Run',
76
    ];
77
78
    public function canView($member = null, $context = [])
0 ignored issues
show
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

78
    public function canView($member = null, /** @scrutinizer ignore-unused */ $context = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
79
    {
80
        return Permission::check('CMS_ACCESS_SimpleJobsAdmin', 'any', $member);
81
    }
82
83
    public function canEdit($member = null, $context = [])
0 ignored issues
show
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

83
    public function canEdit($member = null, /** @scrutinizer ignore-unused */ $context = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
    {
85
        return Permission::check('CMS_ACCESS_SimpleJobsAdmin', 'any', $member);
86
    }
87
88
    public function canCreate($member = null, $context = [])
89
    {
90
        return Permission::check('CMS_ACCESS_SimpleJobsAdmin', 'any', $member);
91
    }
92
93
    /**
94
     * @param Member $member
95
     * @return boolean
96
     */
97
    public function canDelete($member = null)
98
    {
99
        return false;
100
    }
101
102
    /**
103
     * @return array<class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string>.
Loading history...
104
     */
105
    public static function allTasks(): array
106
    {
107
        return ClassInfo::implementorsOf(CronTask::class);
108
    }
109
110
    public function triggerManually(): string
111
    {
112
        $inst = $this->TaskInstance();
113
        /** @var void|null|string|bool $result */
114
        $result = $inst->process();
0 ignored issues
show
Are you sure the assignment to $result is correct as $inst->process() targeting SilverStripe\CronTask\In...ces\CronTask::process() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
115
        if ($result !== null) {
116
            if ($result === false) {
117
                return 'Task failed';
118
            }
119
            if (is_string($result)) {
120
                return $result;
121
            }
122
        }
123
        return "Task has been triggered";
124
    }
125
126
    public function getCMSActions()
127
    {
128
        $actions = parent::getCMSActions();
129
        if (class_exists(CustomAction::class) && Permission::check('ADMIN')) {
130
            $triggerManually = new CustomAction('triggerManually', 'Trigger manually');
131
            $triggerManually->setConfirmation("Are you sure you want to trigger the task?");
132
            $actions->push($triggerManually);
133
        }
134
        return $actions;
135
    }
136
137
    /**
138
     * @return FieldList
139
     */
140
    public function getCMSFields()
141
    {
142
        $fields = parent::getCMSFields();
143
        $fields->makeFieldReadonly('Title');
144
        $fields->makeFieldReadonly('Category');
145
        $fields->makeFieldReadonly('Description');
146
        $fields->makeFieldReadonly('TaskClass');
147
        if ($this->IsSystemDisabled()) {
148
            $fields->makeFieldReadonly('Disabled');
149
        }
150
151
        // Display results
152
        $resultsGridConfig = GridFieldConfig_RecordViewer::create();
153
        $resultsGrid = new GridField('Results', 'Results', $this->AllResults(), $resultsGridConfig);
154
        $fields->addFieldToTab('Root.Results', $resultsGrid);
155
156
        // Optionally provide a list of to be affected items
157
        $taskClass = $this->TaskClass;
158
        if (method_exists($taskClass, 'getJobRecords')) {
159
            $records = $taskClass::getJobRecords();
160
            if ($records) {
161
                $recordsGridConfig = GridFieldConfig_RecordViewer::create();
162
                $recordsGrid = new GridField('Records', 'Records', $records, $recordsGridConfig);
163
                $fields->addFieldToTab('Root.Records', $recordsGrid);
164
            }
165
        }
166
167
        return $fields;
168
    }
169
170
    public static function regenerateFromClasses(bool $update = false): void
171
    {
172
        $list = self::allTasks();
173
        foreach ($list as $class) {
174
            $obj = self::getByTaskClass($class);
175
            if ($obj && $update === false) {
176
                continue;
177
            }
178
            if (!$obj) {
179
                $obj = new CronJob();
180
            }
181
            $obj->TaskClass = $class;
182
            $obj->Title =  method_exists($class, 'getJobTitle') ? $class::getJobTitle() : $class;
183
            $obj->Category = method_exists($class, 'getJobCategory') ? $class::getJobCategory() : 'general';
184
            $obj->Description = method_exists($class, 'getJobDescription') ? $class::getJobDescription() : null;
185
            $obj->write();
186
        }
187
    }
188
189
    public static function getByTaskClass(string $class): ?CronJob
190
    {
191
        /** @var CronJob|null $obj */
192
        $obj = self::get()->filter('TaskClass', $class)->first();
193
        return $obj;
194
    }
195
196
    public function IsDisabled(): bool
197
    {
198
        if ($this->IsSystemDisabled()) {
199
            return true;
200
        }
201
        return $this->Disabled;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->Disabled could return the type integer which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
202
    }
203
204
    public function IsTaskDisabled(): bool
205
    {
206
        $taskClass = $this->TaskClass;
207
        if ($taskClass && method_exists($taskClass, 'IsDisabled')) {
208
            return $taskClass::IsDisabled();
209
        }
210
        return false;
211
    }
212
213
    public function IsSystemDisabled(): bool
214
    {
215
        if ($this->IsTaskDisabled()) {
216
            return true;
217
        }
218
        $disabledTask = Config::inst()->get(SimpleJobsController::class, 'disabled_tasks');
219
        if (in_array($this->TaskClass, $disabledTask)) {
220
            return true;
221
        }
222
        return false;
223
    }
224
225
    /**
226
     * @return CronTask
227
     */
228
    public function TaskInstance(): CronTask
229
    {
230
        /** @var class-string $class */
231
        $class = $this->TaskClass;
232
        $inst = new $class;
233
        if ($inst instanceof CronTask) {
234
            return $inst;
235
        }
236
        throw new Exception("Invalid class $class");
237
    }
238
239
    public function NextRun(): string
240
    {
241
        if ($this->IsDisabled()) {
242
            return '';
243
        }
244
        $task = $this->TaskInstance();
245
        $cron = new CronExpression($task->getSchedule());
246
        return $cron->getNextRunDate()->format('Y-m-d H:i:s');
247
    }
248
249
    /**
250
     * @return DataList
251
     */
252
    public function AllResults()
253
    {
254
        $result = CronTaskResult::get()->filter([
255
            'TaskClass' => $this->TaskClass
256
        ])->Sort('Created DESC');
257
        return $result;
258
    }
259
260
    public function LastResult(): ?CronTaskResult
261
    {
262
        /** @var CronTaskResult|null $result */
263
        $result = CronTaskResult::get()->filter([
264
            'TaskClass' => $this->TaskClass
265
        ])->Sort('Created DESC')->first();
266
        return $result;
267
    }
268
269
    public function LastFailedResult(): ?CronTaskResult
270
    {
271
        /** @var CronTaskResult|null $result */
272
        $result = CronTaskResult::get()->filter([
273
            'TaskClass' => $this->TaskClass,
274
            'Failed' => 1
275
        ])->Sort('Created DESC')->first();
276
        return $result;
277
    }
278
}
279