Commit | Line | Data |
---|---|---|
158bd3ca | 1 | <?php |
a9229942 | 2 | |
158bd3ca | 3 | namespace wcf\data\cronjob; |
a9229942 TD |
4 | |
5 | use wcf\data\AbstractDatabaseObjectAction; | |
b78fb283 | 6 | use wcf\data\cronjob\log\CronjobLogEditor; |
a9229942 | 7 | use wcf\data\IToggleAction; |
75e0d7eb | 8 | use wcf\data\TDatabaseObjectToggle; |
a9e46e67 | 9 | use wcf\data\user\User; |
b8050a71 | 10 | use wcf\system\cronjob\CronjobScheduler; |
e4499881 | 11 | use wcf\system\cronjob\ICronjob; |
3631f7bd | 12 | use wcf\system\exception\PermissionDeniedException; |
2bc9f31d | 13 | use wcf\system\WCF; |
baa07e3c | 14 | use wcf\util\DateUtil; |
158bd3ca TD |
15 | |
16 | /** | |
17 | * Executes cronjob-related actions. | |
a9229942 TD |
18 | * |
19 | * @author Tim Duesterhus, Alexander Ebert | |
20 | * @copyright 2001-2019 WoltLab GmbH | |
21 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
22 | * @package WoltLabSuite\Core\Data\Cronjob | |
23 | * | |
24 | * @method Cronjob create() | |
25 | * @method CronjobEditor[] getObjects() | |
26 | * @method CronjobEditor getSingleObject() | |
158bd3ca | 27 | */ |
a9229942 TD |
28 | class CronjobAction extends AbstractDatabaseObjectAction implements IToggleAction |
29 | { | |
30 | use TDatabaseObjectToggle; | |
31 | ||
32 | /** | |
33 | * @inheritDoc | |
34 | */ | |
35 | protected $className = CronjobEditor::class; | |
36 | ||
37 | /** | |
38 | * @inheritDoc | |
39 | */ | |
40 | protected $permissionsCreate = ['admin.management.canManageCronjob']; | |
41 | ||
42 | /** | |
43 | * @inheritDoc | |
44 | */ | |
45 | protected $permissionsDelete = ['admin.management.canManageCronjob']; | |
46 | ||
47 | /** | |
48 | * @inheritDoc | |
49 | */ | |
50 | protected $permissionsUpdate = ['admin.management.canManageCronjob']; | |
51 | ||
52 | /** | |
53 | * @inheritDoc | |
54 | */ | |
55 | protected $allowGuestAccess = ['executeCronjobs']; | |
56 | ||
57 | /** | |
58 | * @inheritDoc | |
59 | */ | |
60 | protected $requireACP = ['create', 'delete', 'update', 'toggle', 'execute']; | |
61 | ||
62 | /** | |
63 | * @inheritDoc | |
64 | */ | |
65 | public function validateDelete() | |
66 | { | |
67 | parent::validateDelete(); | |
68 | ||
69 | foreach ($this->getObjects() as $cronjob) { | |
70 | if (!$cronjob->isDeletable()) { | |
71 | throw new PermissionDeniedException(); | |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | /** | |
77 | * @inheritDoc | |
78 | */ | |
79 | public function validateUpdate() | |
80 | { | |
81 | parent::validateUpdate(); | |
82 | ||
83 | foreach ($this->getObjects() as $cronjob) { | |
84 | if (!$cronjob->isEditable()) { | |
85 | throw new PermissionDeniedException(); | |
86 | } | |
87 | } | |
88 | } | |
89 | ||
90 | /** | |
91 | * @inheritDoc | |
92 | */ | |
93 | public function validateToggle() | |
94 | { | |
95 | parent::validateUpdate(); | |
96 | ||
97 | foreach ($this->getObjects() as $cronjob) { | |
98 | if (!$cronjob->canBeDisabled()) { | |
99 | throw new PermissionDeniedException(); | |
100 | } | |
101 | } | |
102 | } | |
103 | ||
104 | /** | |
105 | * Validates the 'execute' action. | |
106 | */ | |
107 | public function validateExecute() | |
108 | { | |
109 | parent::validateUpdate(); | |
110 | } | |
111 | ||
112 | /** | |
113 | * Executes cronjobs. | |
114 | */ | |
115 | public function execute() | |
116 | { | |
117 | $return = []; | |
118 | ||
119 | // switch session owner to 'system' during execution of cronjobs | |
120 | $actualUser = WCF::getUser(); | |
121 | WCF::getSession()->changeUser(new User(null, ['userID' => 0, 'username' => 'System']), true); | |
122 | WCF::getSession()->disableUpdate(); | |
123 | ||
124 | try { | |
125 | foreach ($this->getObjects() as $key => $cronjob) { | |
126 | // mark them as pending | |
127 | $cronjob->update(['state' => Cronjob::PENDING]); | |
128 | } | |
129 | ||
130 | foreach ($this->getObjects() as $cronjob) { | |
131 | // it now time for executing | |
132 | $cronjob->update(['state' => Cronjob::EXECUTING]); | |
133 | $className = $cronjob->className; | |
134 | ||
135 | /** @var ICronjob $executable */ | |
136 | $executable = new $className(); | |
137 | ||
138 | // execute cronjob | |
139 | $exception = null; | |
140 | ||
141 | // check if all required options are set for cronjob to be executed | |
142 | // note: a general log is created to avoid confusion why a cronjob | |
143 | // apparently is not executed while that is indeed the correct internal | |
144 | // behavior | |
145 | if ($cronjob->validateOptions()) { | |
146 | try { | |
147 | $executable->execute(new Cronjob($cronjob->cronjobID)); | |
148 | } catch (\Exception $exception) { | |
149 | } | |
150 | } | |
151 | ||
152 | CronjobLogEditor::create([ | |
153 | 'cronjobID' => $cronjob->cronjobID, | |
154 | 'execTime' => TIME_NOW, | |
155 | 'success' => $exception ? 0 : 1, | |
156 | 'error' => $exception ? $exception->getMessage() : '', | |
157 | ]); | |
158 | ||
159 | // calculate next exec-time | |
160 | $nextExec = $cronjob->getNextExec(); | |
161 | $data = [ | |
162 | 'lastExec' => TIME_NOW, | |
163 | 'nextExec' => $nextExec, | |
164 | 'afterNextExec' => $cronjob->getNextExec($nextExec + 120), | |
165 | ]; | |
166 | ||
167 | // cronjob failed | |
168 | if ($exception) { | |
169 | if ($cronjob->failCount < Cronjob::MAX_FAIL_COUNT) { | |
170 | $data['failCount'] = $cronjob->failCount + 1; | |
171 | } | |
172 | ||
173 | // cronjob failed too often: disable it | |
174 | if ($cronjob->failCount + 1 == Cronjob::MAX_FAIL_COUNT) { | |
175 | $data['isDisabled'] = 1; | |
176 | } | |
177 | } // if no error: reset fail counter | |
178 | else { | |
179 | $data['failCount'] = 0; | |
180 | ||
181 | // if cronjob has been disabled because of too many | |
182 | // failed executions, enable it again | |
183 | if ($cronjob->failCount == Cronjob::MAX_FAIL_COUNT && $cronjob->isDisabled) { | |
184 | $data['isDisabled'] = 0; | |
185 | } | |
186 | } | |
187 | ||
188 | $cronjob->update($data); | |
189 | ||
190 | // build the return value | |
191 | if ($exception === null && !$cronjob->isDisabled) { | |
192 | $dateTime = DateUtil::getDateTimeByTimestamp($nextExec); | |
193 | $return[$cronjob->cronjobID] = [ | |
194 | 'time' => $nextExec, | |
195 | 'formatted' => \str_replace( | |
196 | '%time%', | |
197 | DateUtil::format($dateTime, DateUtil::TIME_FORMAT), | |
198 | \str_replace( | |
199 | '%date%', | |
200 | DateUtil::format($dateTime, DateUtil::DATE_FORMAT), | |
201 | WCF::getLanguage()->get('wcf.date.dateTimeFormat') | |
202 | ) | |
203 | ), | |
204 | ]; | |
205 | } | |
206 | ||
207 | // we are finished | |
208 | $cronjob->update(['state' => Cronjob::READY]); | |
209 | ||
210 | // throw exception again to show error message | |
211 | if ($exception) { | |
212 | throw $exception; | |
213 | } | |
214 | } | |
215 | } finally { | |
216 | // switch session back to the actual user | |
217 | WCF::getSession()->changeUser($actualUser, true); | |
218 | } | |
219 | ||
220 | return $return; | |
221 | } | |
222 | ||
223 | /** | |
224 | * Validates the 'executeCronjobs' action. | |
225 | */ | |
226 | public function validateExecuteCronjobs() | |
227 | { | |
228 | // does nothing | |
229 | } | |
230 | ||
231 | /** | |
232 | * Executes open cronjobs. | |
233 | */ | |
234 | public function executeCronjobs() | |
235 | { | |
236 | // switch session owner to 'system' during execution of cronjobs | |
237 | WCF::getSession()->changeUser(new User(null, ['userID' => 0, 'username' => 'System']), true); | |
238 | WCF::getSession()->disableUpdate(); | |
239 | ||
240 | CronjobScheduler::getInstance()->executeCronjobs(); | |
241 | } | |
dcb3a44c | 242 | } |