Resolve language item-related PIP GUI todos
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / plugin / CronjobPackageInstallationPlugin.class.php
1 <?php
2 namespace wcf\system\package\plugin;
3 use wcf\data\cronjob\Cronjob;
4 use wcf\data\cronjob\CronjobEditor;
5 use wcf\system\cronjob\ICronjob;
6 use wcf\system\devtools\pip\IDevtoolsPipEntryList;
7 use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
8 use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
9 use wcf\system\exception\SystemException;
10 use wcf\system\form\builder\container\IFormContainer;
11 use wcf\system\form\builder\field\BooleanFormField;
12 use wcf\system\form\builder\field\ClassNameFormField;
13 use wcf\system\form\builder\field\OptionFormField;
14 use wcf\system\form\builder\field\TextFormField;
15 use wcf\system\form\builder\field\validation\FormFieldValidationError;
16 use wcf\system\form\builder\field\validation\FormFieldValidator;
17 use wcf\system\form\builder\IFormDocument;
18 use wcf\system\language\LanguageFactory;
19 use wcf\system\WCF;
20 use wcf\util\CronjobUtil;
21 use wcf\util\StringUtil;
22
23 /**
24 * Installs, updates and deletes cronjobs.
25 *
26 * @author Alexander Ebert, Matthias Schmidt
27 * @copyright 2001-2018 WoltLab GmbH
28 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
29 * @package WoltLabSuite\Core\Acp\Package\Plugin
30 */
31 class CronjobPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
32 use TXmlGuiPackageInstallationPlugin;
33
34 /**
35 * @inheritDoc
36 */
37 public $className = CronjobEditor::class;
38
39 /**
40 * @inheritDoc
41 */
42 protected function getElement(\DOMXPath $xpath, array &$elements, \DOMElement $element) {
43 if ($element->tagName == 'description') {
44 if (!isset($elements['description'])) {
45 $elements['description'] = [];
46 }
47
48 $elements['description'][$element->getAttribute('language')] = $element->nodeValue;
49 }
50 else {
51 parent::getElement($xpath, $elements, $element);
52 }
53 }
54
55 /**
56 * @inheritDoc
57 */
58 protected function handleDelete(array $items) {
59 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
60 WHERE className = ?
61 AND packageID = ?";
62 $legacyStatement = WCF::getDB()->prepareStatement($sql);
63
64 $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
65 WHERE cronjobName = ?
66 AND packageID = ?";
67 $statement = WCF::getDB()->prepareStatement($sql);
68
69 foreach ($items as $item) {
70 if (!isset($item['attributes']['name'])) {
71 $legacyStatement->execute([
72 $item['elements']['classname'],
73 $this->installation->getPackageID()
74 ]);
75 }
76 else {
77 $statement->execute([
78 $item['attributes']['name'],
79 $this->installation->getPackageID()
80 ]);
81 }
82 }
83 }
84
85 /**
86 * @inheritDoc
87 */
88 protected function prepareImport(array $data) {
89 return [
90 'canBeDisabled' => isset($data['elements']['canbedisabled']) ? intval($data['elements']['canbedisabled']) : 1,
91 'canBeEdited' => isset($data['elements']['canbeedited']) ? intval($data['elements']['canbeedited']) : 1,
92 'className' => isset($data['elements']['classname']) ? $data['elements']['classname'] : '',
93 'cronjobName' => isset($data['attributes']['name']) ? $data['attributes']['name'] : '',
94 'description' => isset($data['elements']['description']) ? $data['elements']['description'] : '',
95 'isDisabled' => isset($data['elements']['isdisabled']) ? intval($data['elements']['isdisabled']) : 0,
96 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
97 'startDom' => $data['elements']['startdom'],
98 'startDow' => $data['elements']['startdow'],
99 'startHour' => $data['elements']['starthour'],
100 'startMinute' => $data['elements']['startminute'],
101 'startMonth' => $data['elements']['startmonth']
102 ];
103 }
104
105 /**
106 * @inheritDoc
107 */
108 protected function validateImport(array $data) {
109 CronjobUtil::validate($data['startMinute'], $data['startHour'], $data['startDom'], $data['startMonth'], $data['startDow']);
110 }
111
112 /**
113 * @inheritDoc
114 */
115 protected function import(array $row, array $data) {
116 // if a cronjob is updated without a name given, keep the old automatically
117 // assigned name
118 if (!empty($row) && !$data['cronjobName']) {
119 unset($data['cronjobName']);
120 }
121
122 /** @var Cronjob $cronjob */
123 $cronjob = parent::import($row, $data);
124
125 // update cronjob name
126 if (!$cronjob->cronjobName) {
127 $cronjobEditor = new CronjobEditor($cronjob);
128 $cronjobEditor->update([
129 'cronjobName' => Cronjob::AUTOMATIC_NAME_PREFIX.$cronjob->cronjobID
130 ]);
131
132 $cronjob = new Cronjob($cronjob->cronjobID);
133 }
134
135 return $cronjob;
136 }
137
138 /**
139 * @inheritDoc
140 */
141 protected function findExistingItem(array $data) {
142 if (!$data['cronjobName']) return null;
143
144 $sql = "SELECT *
145 FROM wcf".WCF_N."_".$this->tableName."
146 WHERE packageID = ?
147 AND cronjobName = ?";
148 $parameters = [
149 $this->installation->getPackageID(),
150 $data['cronjobName']
151 ];
152
153 return [
154 'sql' => $sql,
155 'parameters' => $parameters
156 ];
157 }
158
159 /**
160 * @inheritDoc
161 */
162 protected function prepareCreate(array &$data) {
163 parent::prepareCreate($data);
164
165 $data['nextExec'] = TIME_NOW;
166 }
167
168 /**
169 * @inheritDoc
170 * @since 3.1
171 */
172 public static function getSyncDependencies() {
173 return [];
174 }
175
176 /**
177 * @inheritDoc
178 * @since 3.2
179 */
180 protected function addFormFields(IFormDocument $form) {
181 /** @var IFormContainer $dataContainer */
182 $dataContainer = $form->getNodeById('data');
183
184 $dataContainer->appendChildren([
185 TextFormField::create('cronjobName')
186 ->objectProperty('name')
187 ->label('wcf.acp.pip.cronjob.cronjobName')
188 ->description('wcf.acp.pip.cronjob.cronjobName.description')
189 ->required(),
190
191 TextFormField::create('description')
192 ->label('wcf.global.description')
193 ->description('wcf.acp.pip.cronjob.description.description')
194 ->i18n()
195 ->languageItemPattern('__NONE__'),
196
197 ClassNameFormField::create()
198 ->objectProperty('classname')
199 ->implementedInterface(ICronjob::class)
200 ->required(),
201
202 OptionFormField::create()
203 ->description('wcf.acp.pip.cronjob.options.description'),
204
205 BooleanFormField::create('isDisabled')
206 ->objectProperty('isdisabled')
207 ->label('wcf.acp.pip.cronjob.isDisabled')
208 ->description('wcf.acp.pip.cronjob.isDisabled.description'),
209
210 BooleanFormField::create('canBeEdited')
211 ->objectProperty('canbeedited')
212 ->label('wcf.acp.pip.cronjob.canBeEdited')
213 ->description('wcf.acp.pip.cronjob.canBeEdited.description')
214 ->value(true),
215
216 BooleanFormField::create('canBeDisabled')
217 ->objectProperty('canbedisabled')
218 ->label('wcf.acp.pip.cronjob.canBeDisabled')
219 ->description('wcf.acp.pip.cronjob.canBeDisabled.description')
220 ->value(true)
221 ]);
222
223 foreach (['startDom', 'startDow', 'startHour', 'startMinute', 'startMonth'] as $timeProperty) {
224 $dataContainer->insertBefore(
225 TextFormField::create($timeProperty)
226 ->objectProperty(strtolower($timeProperty))
227 ->label('wcf.acp.cronjob.' . $timeProperty)
228 ->description("wcf.acp.cronjob.{$timeProperty}.description")
229 ->required()
230 ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) use ($timeProperty) {
231 try {
232 CronjobUtil::validateAttribute($timeProperty, $formField->getSaveValue());
233 }
234 catch (SystemException $e) {
235 $formField->addValidationError(
236 new FormFieldValidationError(
237 'format',
238 "wcf.acp.pip.cronjob.{$timeProperty}.error.format"
239 )
240 );
241 }
242 })),
243 'options'
244 );
245 }
246 }
247
248 /**
249 * @inheritDoc
250 * @since 3.2
251 */
252 protected function doGetElementData(\DOMElement $element, $saveData) {
253 $data = [
254 'className' => $element->getElementsByTagName('classname')->item(0)->nodeValue,
255 'cronjobName' => $element->getAttribute('name'),
256 'description' => [],
257 'packageID' => $this->installation->getPackage()->packageID,
258 'startDom' => $element->getElementsByTagName('startdom')->item(0)->nodeValue,
259 'startDow' => $element->getElementsByTagName('startdow')->item(0)->nodeValue,
260 'startHour' => $element->getElementsByTagName('starthour')->item(0)->nodeValue,
261 'startMinute' => $element->getElementsByTagName('startminute')->item(0)->nodeValue,
262 'startMonth' => $element->getElementsByTagName('startmonth')->item(0)->nodeValue
263 ];
264
265 $canBeDisabled = $element->getElementsByTagName('canbedisabled')->item(0);
266 if ($canBeDisabled !== null) {
267 $data['canBeDisabled'] = $canBeDisabled->nodeValue;
268 }
269
270 $descriptionElements = $element->getElementsByTagName('description');
271 $descriptions = [];
272
273 /** @var \DOMElement $description */
274 foreach ($descriptionElements as $description) {
275 $descriptions[$description->getAttribute('language')] = $description->nodeValue;
276 }
277
278 foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
279 if (!empty($descriptions)) {
280 if (isset($descriptions[$language->languageCode])) {
281 $data['description'][$language->languageID] = $descriptions[$language->languageCode];
282 }
283 else if (isset($descriptions[''])) {
284 $data['description'][$language->languageID] = $descriptions[''];
285 }
286 else if (isset($descriptions['en'])) {
287 $data['description'][$language->languageID] = $descriptions['en'];
288 }
289 else if (isset($descriptions[WCF::getLanguage()->getFixedLanguageCode()])) {
290 $data['description'][$language->languageID] = $descriptions[WCF::getLanguage()->getFixedLanguageCode()];
291 }
292 else {
293 $data['description'][$language->languageID] = reset($descriptions);
294 }
295 }
296 else {
297 $data['description'][$language->languageID] = '';
298 }
299 }
300
301 $canBeEdited = $element->getElementsByTagName('canbeedited')->item(0);
302 if ($canBeEdited !== null) {
303 $data['canBeDisabled'] = $canBeEdited->nodeValue;
304 }
305
306 $isDisabled = $element->getElementsByTagName('isdisabled')->item(0);
307 if ($isDisabled !== null) {
308 $data['canBeDisabled'] = $isDisabled->nodeValue;
309 }
310
311 return $data;
312 }
313
314 /**
315 * @inheritDoc
316 * @since 3.2
317 */
318 public function getElementIdentifier(\DOMElement $element) {
319 return $element->getAttribute('name');
320 }
321
322 /**
323 * @inheritDoc
324 * @since 3.2
325 */
326 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
327 $entryList->setKeys([
328 'cronjobName' => 'wcf.acp.pip.cronjob.cronjobName',
329 'className' => 'wcf.form.field.className'
330 ]);
331 }
332
333 /**
334 * @inheritDoc
335 * @since 3.2
336 */
337 protected function doCreateXmlElement(\DOMDocument $document, IFormDocument $form) {
338 $data = $form->getData();
339 $formData = $form->getData()['data'];
340
341 $cronjob = $document->createElement($this->tagName);
342 $cronjob->setAttribute('name', $formData['name']);
343 $cronjob->appendChild($document->createElement('classname', $formData['classname']));
344
345 if (isset($formData['description'])) {
346 if ($formData['description'] !== '') {
347 $cronjob->appendChild($document->createElement('description', $formData['description']));
348 }
349 }
350 else if (isset($data['description_i18n'])) {
351 /** @var \DOMElement $firstDescription */
352 $firstDescription = null;
353 foreach ($data['description_i18n'] as $languageItem => $description) {
354 if ($description !== '') {
355 $descriptionElement = $document->createElement('description', $description);
356 $languageCode = LanguageFactory::getInstance()->getLanguage($languageItem)->languageCode;
357 if ($languageCode !== 'en') {
358 $descriptionElement->setAttribute('language', $languageCode);
359 $cronjob->appendChild($descriptionElement);
360 }
361 else if ($firstDescription === null) {
362 $cronjob->appendChild($descriptionElement);
363 }
364 else {
365 // default description should be shown first
366 $cronjob->insertBefore($descriptionElement, $firstDescription);
367 }
368
369 if ($firstDescription === null) {
370 $firstDescription = $descriptionElement;
371 }
372 }
373 }
374 }
375
376 $this->appendElementChildren(
377 $cronjob,
378 [
379 'startmonth',
380 'startdom',
381 'startdow',
382 'starthour',
383 'startminute',
384 'options' => '',
385 'canbeedited' => 1,
386 'canbedisabled' => 1,
387 'isdisabled' => 0
388 ],
389 $form
390 );
391
392 return $cronjob;
393 }
394 }