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