Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
// +---------------------------------------------------------------------------+
// This file is part of Stud.IP
// CronjobSchedule.class.php
//
// Copyright (C) 2013 Jan-Hendrik Willms <tleilax+studip@gmail.com>
// +---------------------------------------------------------------------------+
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or any later version.
// +---------------------------------------------------------------------------+
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// +---------------------------------------------------------------------------+
/**
* CronjobSchedule - Model for the database table "cronjobs_schedules"
*
* @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @since 2.4
*
* @property string schedule_id database column
* @property string id alias column for schedule_id
* @property string task_id database column
* @property string active database column
* @property string title database column
* @property string description database column
* @property string parameters database column
* @property string priority database column
* @property string type database column
* @property string minute database column
* @property string hour database column
* @property string day database column
* @property string month database column
* @property string day_of_week database column
* @property string next_execution database column
* @property string last_execution database column
* @property string last_result database column
* @property string execution_count database column
* @property string mkdate database column
* @property string chdate database column
* @property SimpleORMapCollection logs has_many CronjobLog
* @property CronjobTask task belongs_to CronjobTask
*/
class CronjobSchedule extends SimpleORMap
{
const PRIORITY_LOW = 'low';
const PRIORITY_NORMAL = 'normal';
const PRIORITY_HIGH = 'high';
protected static function configure($config = [])
{
$config['db_table'] = 'cronjobs_schedules';
$config['belongs_to']['task'] = [
'class_name' => CronjobTask::class,
'foreign_key' => 'task_id',
];
$config['has_many']['logs'] = [
'class_name' => CronjobLog::class,
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'on_delete' => 'delete',
'on_store' => 'store',
];
$config['registered_callbacks']['before_store'][] = 'cbJsonifyParameters';
$config['registered_callbacks']['after_store'][] = 'cbJsonifyParameters';
$config['registered_callbacks']['after_initialize'][] = 'cbJsonifyParameters';
parent::configure($config);
}
/**
* Returns a mapped version of the priorities (key = priority value,
* value = localized priority label).
*
* @return Array The mapped priorities
*/
public static function getPriorities()
{
$mapping = [];
$mapping[self::PRIORITY_LOW] = _('niedrig');
$mapping[self::PRIORITY_NORMAL] = _('normal');
$mapping[self::PRIORITY_HIGH] = _('hoch');
return $mapping;
}
/**
* Maps a priority value to it's localized label.
*
* @param String $priority Priority value
* @return String The localized label
* @throws RuntimeException when an unknown priority value is passed
*/
public static function describePriority($priority)
{
$priority = $priority ?? 'normal';
$mapping = self::getPriorities();
if (!isset($mapping[$priority])) {
throw new RuntimeException('Access to unknown priority "' . $priority . '"');
}
return $mapping[$priority];
}
/**
* replaces title with task name if title is empty.
*
* @return string the title or the task name
*/
public function getTitle()
{

Jan-Hendrik Willms
committed
return ($this->content['title'] ?: $this->task->name) ?? '';
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
}
protected function cbJsonifyParameters($type)
{
if ($type === 'before_store' && !is_string($this->parameters)) {
$this->parameters = json_encode($this->parameters ?: null);
}
if (in_array($type, ['after_initialize', 'after_store']) && is_string($this->parameters)) {
$parameters = json_decode($this->parameters, true) ?: [];
if ($this->task->valid) {
$default_parameters = $this->task->extractDefaultParameters();
foreach ($default_parameters as $key => $value) {
if (!isset($parameters[$key])) {
$parameters[$key] = $value;
}
}
}
$this->parameters = $parameters;
}
}
/**
* Stores the schedule in database. Will bail out with an exception if
* the provided task does not exists. Will also nullify the title if it
* matches the task name (see CronjobSchedule::getTitle()).
*
* @return CronjobSchedule Returns itself to allow chaining
*/
public function store()
{
if ($this->task === null) {
$message = sprintf('A task with the id "%s" does not exist.', $this->task_id);
throw new InvalidArgumentException($message);
}
// Remove title if it is the default (task's name)
if ($this->title === $this->task->name) {
$this->title = null;
}
parent::store();
return $this;
}
/**
* Activates this schedule.
*
* @return CronjobSchedule Returns itself to allow chaining
*/
public function activate()
{
$this->active = 1;
$this->next_execution = $this->calculateNextExecution();
$this->store();
return $this;
}
/**
* Deactivates this schedule.
*
* @return CronjobSchedule Returns itself to allow chaining
*/
public function deactivate()
{
$this->active = 0;
$this->store();
return $this;
}
/**
* Executes this schedule.
*
* @param bool $force Pass true to force execution of the schedule even
* if it's not activated
* @return mixed The result of the execution
* @throws RuntimeException When either the schedule or the according is
* not activated
*/
public function execute($force = false)
{
if (!$force && !$this->active) {
throw new RuntimeException('Execution aborted. Schedule is not active');
}
if (!$this->task->active) {
throw new RuntimeException('Execution aborted. Associated task is not active');
}
$this->last_execution = time();
$this->execution_count += 1;
$this->next_execution = $this->calculateNextExecution();
$this->store();
$this->task->execution_count += 1;
$this->task->store();
$result = $this->task->engage($this->last_result, $this->parameters);
if ($this->type === 'once') {
$this->active = 0;
}
$this->last_result = $result;
$this->store();
return $result;
}
/**
* Determines whether the schedule should execute given the provided
* timestamp.
*
* @param mixed $now Defines the temporal fix point
* @return bool Whether the schedule should execute or not.
*/
public function shouldExecute($now = null)
{
return ($now ?: time()) >= $this->next_execution;
}
/**
* Calculates the next execution for this schedule.
*
* For schedules of type 'once' the check solely tests whether the
* timestamp has already passed and will return false in that case.
* Otherwise the defined timestamp will be returned.
*
* For schedules of type 'periodic' the next execution
* is calculated by increasing the current timestamp and testing
* whether all conditions match. This is not the best method to test
* and should be optimized sooner or later.
*
* @param mixed $now Defines the temporal fix point
* @return int Timestamp of calculated next execution
* @throws RuntimeException When calculation takes too long (you should
* check the conditions for validity in that case)
*/
public function calculateNextExecution($now = null)
{
$now = $now ?: time();
if ($this->type === 'once') {
return $now <= $this->next_execution
? $this->next_execution
: false;
}
$result = $now;
$result -= $result % 60;
$i = 366 * 24 * 60; // Maximum: A year
$offset = 60;
do {
$result += $offset;
// TODO: Performance - Adjust result according to conditions
// See http://coderzone.org/library/PHP-PHP-Cron-Parser-Class_1084.htm
$valid = $this->testTimestamp($result, $this->minute, 'i')
&& $this->testTimestamp($result, $this->hour, 'H')
&& $this->testTimestamp($result, $this->day, 'd')
&& $this->testTimestamp($result, $this->month, 'm')
&& $this->testTimestamp($result, $this->day_of_week, 'N');
} while (!$valid && $i-- > 0);
if ($i <= 0) {
throw new RuntimeException('No result, current: ' . date('d.m.Y H:i', $result));
}
$this->next_execution = $result;
return $result;
}
/**
* Tests a timestamp against the passed condition.
*
* @param int $timestamp The timestamp to test
* @param mixed $condition Can be either null for "don't care", a positive
* number for an exact moment or a negative number
* for a repeating moment
* @param String $format Format for date() to extract a portion of the
* timestamp
*/
protected function testTimestamp($timestamp, $condition, $format)
{
if ($condition === null) {
return true;
}
$probe = (int) date($format, $timestamp);
$condition = (int) $condition;
if ($condition < 0) {
return ($probe % abs($condition)) === 0;
}
return $probe === $condition;
}
}