Merge branch '5.3'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / cronjob / CronjobAction.class.php
1 <?php
2
3 namespace wcf\data\cronjob;
4
5 use wcf\data\AbstractDatabaseObjectAction;
6 use wcf\data\cronjob\log\CronjobLogEditor;
7 use wcf\data\IToggleAction;
8 use wcf\data\TDatabaseObjectToggle;
9 use wcf\data\user\User;
10 use wcf\system\cronjob\CronjobScheduler;
11 use wcf\system\cronjob\ICronjob;
12 use wcf\system\exception\PermissionDeniedException;
13 use wcf\system\WCF;
14 use wcf\util\DateUtil;
15
16 /**
17 * Executes cronjob-related actions.
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()
27 */
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 }
242 }