Merge branch '3.0' into 3.1
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / cronjob / CronjobAction.class.php
1 <?php
2 namespace wcf\data\cronjob;
3 use wcf\data\cronjob\log\CronjobLogEditor;
4 use wcf\data\user\User;
5 use wcf\data\AbstractDatabaseObjectAction;
6 use wcf\data\IToggleAction;
7 use wcf\system\cronjob\CronjobScheduler;
8 use wcf\system\cronjob\ICronjob;
9 use wcf\system\exception\PermissionDeniedException;
10 use wcf\system\WCF;
11 use wcf\util\DateUtil;
12
13 /**
14 * Executes cronjob-related actions.
15 *
16 * @author Tim Duesterhus, Alexander Ebert
17 * @copyright 2001-2018 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
19 * @package WoltLabSuite\Core\Data\Cronjob
20 *
21 * @method Cronjob create()
22 * @method CronjobEditor[] getObjects()
23 * @method CronjobEditor getSingleObject()
24 */
25 class CronjobAction extends AbstractDatabaseObjectAction implements IToggleAction {
26 /**
27 * @inheritDoc
28 */
29 protected $className = CronjobEditor::class;
30
31 /**
32 * @inheritDoc
33 */
34 protected $permissionsCreate = ['admin.management.canManageCronjob'];
35
36 /**
37 * @inheritDoc
38 */
39 protected $permissionsDelete = ['admin.management.canManageCronjob'];
40
41 /**
42 * @inheritDoc
43 */
44 protected $permissionsUpdate = ['admin.management.canManageCronjob'];
45
46 /**
47 * @inheritDoc
48 */
49 protected $allowGuestAccess = ['executeCronjobs'];
50
51 /**
52 * @inheritDoc
53 */
54 protected $requireACP = ['create', 'delete', 'update', 'toggle', 'execute'];
55
56 /**
57 * @inheritDoc
58 */
59 public function validateDelete() {
60 parent::validateDelete();
61
62 foreach ($this->getObjects() as $cronjob) {
63 if (!$cronjob->isDeletable()) {
64 throw new PermissionDeniedException();
65 }
66 }
67 }
68
69 /**
70 * @inheritDoc
71 */
72 public function validateUpdate() {
73 parent::validateUpdate();
74
75 foreach ($this->getObjects() as $cronjob) {
76 if (!$cronjob->isEditable()) {
77 throw new PermissionDeniedException();
78 }
79 }
80 }
81
82 /**
83 * @inheritDoc
84 */
85 public function validateToggle() {
86 parent::validateUpdate();
87
88 foreach ($this->getObjects() as $cronjob) {
89 if (!$cronjob->canBeDisabled()) {
90 throw new PermissionDeniedException();
91 }
92 }
93 }
94
95 /**
96 * @inheritDoc
97 */
98 public function toggle() {
99 foreach ($this->getObjects() as $cronjob) {
100 $cronjob->update([
101 'isDisabled' => $cronjob->isDisabled ? 0 : 1
102 ]);
103 }
104 }
105
106 /**
107 * Validates the 'execute' action.
108 */
109 public function validateExecute() {
110 parent::validateUpdate();
111 }
112
113 /**
114 * Executes cronjobs.
115 */
116 public function execute() {
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 }
149 catch (\Exception $exception) { }
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 }
178 // if no error: reset fail counter
179 else {
180 $data['failCount'] = 0;
181
182 // if cronjob has been disabled because of too many
183 // failed executions, enable it again
184 if ($cronjob->failCount == Cronjob::MAX_FAIL_COUNT && $cronjob->isDisabled) {
185 $data['isDisabled'] = 0;
186 }
187 }
188
189 $cronjob->update($data);
190
191 // build the return value
192 if ($exception === null && !$cronjob->isDisabled) {
193 $dateTime = DateUtil::getDateTimeByTimestamp($nextExec);
194 $return[$cronjob->cronjobID] = [
195 'time' => $nextExec,
196 'formatted' => str_replace(
197 '%time%',
198 DateUtil::format($dateTime, DateUtil::TIME_FORMAT),
199 str_replace(
200 '%date%',
201 DateUtil::format($dateTime, DateUtil::DATE_FORMAT),
202 WCF::getLanguage()->get('wcf.date.dateTimeFormat')
203 )
204 )
205 ];
206 }
207
208 // we are finished
209 $cronjob->update(['state' => Cronjob::READY]);
210
211 // throw exception again to show error message
212 if ($exception) {
213 throw $exception;
214 }
215 }
216 }
217 finally {
218 // switch session back to the actual user
219 WCF::getSession()->changeUser($actualUser, true);
220 }
221
222 return $return;
223 }
224
225 /**
226 * Validates the 'executeCronjobs' action.
227 */
228 public function validateExecuteCronjobs() {
229 // does nothing
230 }
231
232 /**
233 * Executes open cronjobs.
234 */
235 public function executeCronjobs() {
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 }