Merge pull request #5944 from WoltLab/comment-backend-overhaul
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / plugin / EventListenerPackageInstallationPlugin.class.php
CommitLineData
11ade432 1<?php
76844a15 2
11ade432 3namespace wcf\system\package\plugin;
76844a15 4
e0497659 5use wcf\data\event\listener\EventListenerEditor;
efbc545e 6use wcf\data\event\listener\EventListenerList;
b401cd0d 7use wcf\system\cache\builder\EventListenerCacheBuilder;
efbc545e
MS
8use wcf\system\devtools\pip\IDevtoolsPipEntryList;
9use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
10use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
bc538e1c 11use wcf\system\event\EventHandler;
67f97df0 12use wcf\system\event\IEvent;
efbc545e
MS
13use wcf\system\event\listener\IParameterizedEventListener;
14use wcf\system\form\builder\container\FormContainer;
15use wcf\system\form\builder\field\BooleanFormField;
16use wcf\system\form\builder\field\ClassNameFormField;
17use wcf\system\form\builder\field\IntegerFormField;
8a53406e 18use wcf\system\form\builder\field\ItemListFormField;
35d95be3 19use wcf\system\form\builder\field\option\OptionFormField;
efbc545e
MS
20use wcf\system\form\builder\field\SingleSelectionFormField;
21use wcf\system\form\builder\field\TextFormField;
35d95be3 22use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField;
efbc545e
MS
23use wcf\system\form\builder\field\validation\FormFieldValidationError;
24use wcf\system\form\builder\field\validation\FormFieldValidator;
25use wcf\system\form\builder\IFormDocument;
11ade432 26use wcf\system\WCF;
4f2d3f58 27use wcf\util\StringUtil;
11ade432
AE
28
29/**
a17de04e 30 * Installs, updates and deletes event listeners.
76844a15
TD
31 *
32 * @author Matthias Schmidt, Marcel Werk
33 * @copyright 2001-2021 WoltLab GmbH
34 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11ade432 35 */
76844a15 36class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements
bf6fd85c 37 IGuiPackageInstallationPlugin,
38 IUniqueNameXMLPackageInstallationPlugin
76844a15 39{
e5025186
TD
40 use TXmlGuiPackageInstallationPlugin {
41 setEntryData as private traitSetEntryData;
42 editEntry as private traitEditEntry;
43 }
76844a15
TD
44
45 /**
46 * @inheritDoc
47 */
48 public $className = EventListenerEditor::class;
49
50 /**
51 * @inheritDoc
52 */
53 public $tagName = 'eventlistener';
54
55 /**
56 * @inheritDoc
57 */
58 protected function handleDelete(array $items)
59 {
60 $sql = "DELETE FROM wcf" . WCF_N . "_" . $this->tableName . "
61 WHERE packageID = ?
62 AND environment = ?
63 AND eventClassName = ?
64 AND eventName = ?
65 AND inherit = ?
66 AND listenerClassName = ?";
67 $legacyStatement = WCF::getDB()->prepareStatement($sql);
68
69 $sql = "DELETE FROM wcf" . WCF_N . "_" . $this->tableName . "
70 WHERE packageID = ?
71 AND listenerName = ?";
72 $statement = WCF::getDB()->prepareStatement($sql);
73
74 foreach ($items as $item) {
75 if (!isset($item['attributes']['name'])) {
76 $legacyStatement->execute([
77 $this->installation->getPackageID(),
78 $item['elements']['environment'] ?? 'user',
79 $item['elements']['eventclassname'],
80 $item['elements']['eventname'],
81 $item['elements']['inherit'] ?? 0,
82 $item['elements']['listenerclassname'],
83 ]);
84 } else {
85 $statement->execute([
86 $this->installation->getPackageID(),
87 $item['attributes']['name'],
88 ]);
89 }
90 }
91 }
92
93 /**
94 * @inheritDoc
95 */
96 protected function prepareImport(array $data)
97 {
98 $nice = isset($data['elements']['nice']) ? \intval($data['elements']['nice']) : 0;
99 if ($nice < -128) {
100 $nice = -128;
101 } elseif ($nice > 127) {
102 $nice = 127;
103 }
104
bc538e1c
MS
105 $eventName = EventHandler::DEFAULT_EVENT_NAME;
106 if (!empty($data['elements']['eventname'])) {
107 $eventName = StringUtil::normalizeCsv($data['elements']['eventname']);
108 }
109
76844a15
TD
110 return [
111 'environment' => $data['elements']['environment'] ?? 'user',
112 'eventClassName' => $data['elements']['eventclassname'],
bc538e1c 113 'eventName' => $eventName,
76844a15
TD
114 'inherit' => isset($data['elements']['inherit']) ? \intval($data['elements']['inherit']) : 0,
115 'listenerClassName' => $data['elements']['listenerclassname'],
4c4c9696 116 'listenerName' => $data['attributes']['name'],
76844a15
TD
117 'niceValue' => $nice,
118 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '',
119 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '',
120 ];
121 }
122
76844a15
TD
123 /**
124 * @inheritDoc
125 */
126 protected function findExistingItem(array $data)
127 {
4c4c9696
TD
128 $sql = "SELECT *
129 FROM wcf" . WCF_N . "_" . $this->tableName . "
130 WHERE packageID = ?
131 AND listenerName = ?";
132 $parameters = [
133 $this->installation->getPackageID(),
134 $data['listenerName'],
135 ];
76844a15
TD
136
137 return [
138 'sql' => $sql,
139 'parameters' => $parameters,
140 ];
141 }
142
143 /**
144 * @inheritDoc
145 */
146 public function uninstall()
147 {
148 parent::uninstall();
149
150 // clear cache immediately
151 EventListenerCacheBuilder::getInstance()->reset();
152 }
153
bf6fd85c 154 /**
155 * @inheritDoc
156 */
157 public function getNameByData(array $data): string
158 {
159 return $data['listenerName'];
160 }
161
76844a15
TD
162 /**
163 * @inheritDoc
164 * @since 3.1
165 */
166 public static function getSyncDependencies()
167 {
168 return [];
169 }
170
171 /**
172 * @inheritDoc
173 * @since 5.2
174 */
175 protected function addFormFields(IFormDocument $form)
176 {
177 /** @var FormContainer $dataContainer */
178 $dataContainer = $form->getNodeById('data');
179
180 $dataContainer->appendChildren([
181 TextFormField::create('listenerName')
182 ->label('wcf.acp.pip.eventListener.listenerName')
183 ->description('wcf.acp.pip.eventListener.listenerName.description')
184 ->required()
185 ->addValidator(new FormFieldValidator('format', static function (TextFormField $formField) {
186 if (\preg_match('~^[a-z][A-z0-9]*$~', $formField->getSaveValue()) !== 1) {
187 $formField->addValidationError(
188 new FormFieldValidationError(
189 'format',
190 'wcf.acp.pip.eventListener.listenerName.error.format'
191 )
192 );
193 }
194 }))
195 ->addValidator(new FormFieldValidator('uniqueness', function (TextFormField $formField) {
196 if (
197 $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE
198 || $this->editedEntry->getAttribute('name') !== $formField->getValue()
199 ) {
200 $eventListenerList = new EventListenerList();
201 $eventListenerList->getConditionBuilder()->add('listenerName = ?', [$formField->getValue()]);
202 $eventListenerList->getConditionBuilder()->add(
203 'packageID = ?',
204 [$this->installation->getPackageID()]
205 );
206
207 if ($eventListenerList->countObjects() > 0) {
208 $formField->addValidationError(
209 new FormFieldValidationError(
210 'notUnique',
211 'wcf.acp.pip.eventListener.listenerName.error.notUnique'
212 )
213 );
214 }
215 }
216 })),
217
218 ClassNameFormField::create('eventClassName')
219 ->objectProperty('eventclassname')
220 ->label('wcf.acp.pip.eventListener.eventClassName')
221 ->description('wcf.acp.pip.eventListener.eventClassName.description')
222 ->required()
223 ->instantiable(false),
224
225 ItemListFormField::create('eventName')
226 ->objectProperty('eventname')
227 ->label('wcf.acp.pip.eventListener.eventName')
bc538e1c 228 ->description('wcf.acp.pip.eventListener.eventName.description'),
76844a15
TD
229
230 ClassNameFormField::create('listenerClassName')
231 ->objectProperty('listenerclassname')
232 ->label('wcf.acp.pip.eventListener.listenerClassName')
233 ->required()
42d37b5a 234 ->addValidator(new FormFieldValidator('callable', static function (ClassNameFormField $formField) {
67f97df0
MS
235 $listenerClassName = $formField->getValue();
236 /** @var TextFormField $eventClassNameField */
237 $eventClassNameField = $formField->getDocument()->getNodeById('eventClassName');
238 $eventClassName = $eventClassNameField->getValue();
42d37b5a 239
67f97df0 240 if (\is_subclass_of($eventClassName, IEvent::class)) {
42d37b5a 241 if (!\is_callable(new $listenerClassName())) {
67f97df0
MS
242 $formField->addValidationError(
243 new FormFieldValidationError(
244 'noCallable',
245 'wcf.acp.pip.eventListener.listenerClassName.error.noCallable',
246 [
247 'listenerClassName' => $listenerClassName,
248 ]
249 )
250 );
251 }
252 } elseif (!\is_subclass_of($listenerClassName, IParameterizedEventListener::class)) {
253 $formField->addValidationError(
254 new FormFieldValidationError(
255 'interface',
256 'wcf.form.field.className.error.interface',
257 [
258 'interface' => IParameterizedEventListener::class,
259 ]
260 )
261 );
262 }
263 })),
76844a15
TD
264
265 SingleSelectionFormField::create('environment')
266 ->label('wcf.acp.pip.eventListener.environment')
267 ->description('wcf.acp.pip.eventListener.environment.description')
268 ->options([
269 'admin' => 'admin',
270 'user' => 'user',
271 'all' => 'all',
272 ])
273 ->value('user'),
274
275 BooleanFormField::create('inherit')
276 ->label('wcf.acp.pip.eventListener.inherit')
277 ->description('wcf.acp.pip.eventListener.inherit.description'),
278
279 IntegerFormField::create('niceValue')
280 ->objectProperty('nice')
281 ->label('wcf.acp.pip.eventListener.niceValue')
282 ->description('wcf.acp.pip.eventListener.niceValue.description')
283 ->nullable()
284 ->minimum(-128)
285 ->maximum(127),
286
287 OptionFormField::create()
288 ->description('wcf.acp.pip.eventListener.options.description')
289 ->packageIDs(\array_merge(
290 [$this->installation->getPackage()->packageID],
291 \array_keys($this->installation->getPackage()->getAllRequiredPackages())
e5025186
TD
292 ))
293 ->available($form->getFormMode() !== IFormDocument::FORM_MODE_CREATE),
76844a15
TD
294
295 UserGroupOptionFormField::create()
296 ->description('wcf.acp.pip.eventListener.permissions.description')
297 ->packageIDs(\array_merge(
298 [$this->installation->getPackage()->packageID],
299 \array_keys($this->installation->getPackage()->getAllRequiredPackages())
e5025186
TD
300 ))
301 ->available($form->getFormMode() !== IFormDocument::FORM_MODE_CREATE),
76844a15
TD
302 ]);
303 }
304
e5025186
TD
305 /**
306 * Shows options and permissions if already specified.
307 */
308 public function setEntryData($identifier, IFormDocument $document): bool
309 {
310 $options = $document->getNodeById('options');
311 \assert($options instanceof OptionFormField);
312 $permissions = $document->getNodeById('permissions');
313 \assert($permissions instanceof UserGroupOptionFormField);
314
315 $result = $this->traitSetEntryData($identifier, $document);
316
317 if ($result) {
318 if (!$options->getValue()) {
319 $options->available(false);
320 }
321 if (!$permissions->getValue()) {
322 $permissions->available(false);
323 }
324 }
325
326 return $result;
327 }
328
329 /**
330 * Shows options and permissions if already specified.
331 */
332 public function editEntry(IFormDocument $form, $identifier)
333 {
334 $options = $form->getNodeById('options');
335 \assert($options instanceof OptionFormField);
336 $permissions = $form->getNodeById('permissions');
337 \assert($permissions instanceof UserGroupOptionFormField);
338
339 $result = $this->traitEditEntry($form, $identifier);
340
341 if (!$options->getValue()) {
342 $options->available(false);
343 }
344 if (!$permissions->getValue()) {
345 $permissions->available(false);
346 }
347
348 return $result;
349 }
350
76844a15
TD
351 /**
352 * @inheritDoc
353 * @since 5.2
354 */
355 protected function fetchElementData(\DOMElement $element, $saveData)
356 {
bc538e1c
MS
357 $eventName = EventHandler::DEFAULT_EVENT_NAME;
358 $eventNameElements = $element->getElementsByTagName('eventname');
359 if ($eventNameElements->length) {
360 $eventName = StringUtil::normalizeCsv($eventNameElements->item(0)->nodeValue);
361 }
362
76844a15
TD
363 $data = [
364 'eventClassName' => $element->getElementsByTagName('eventclassname')->item(0)->nodeValue,
bc538e1c 365 'eventName' => $eventName,
76844a15
TD
366 'listenerClassName' => $element->getElementsByTagName('listenerclassname')->item(0)->nodeValue,
367 'listenerName' => $element->getAttribute('name'),
368 'packageID' => $this->installation->getPackage()->packageID,
369 ];
370
371 foreach (['environment', 'inherit', 'options', 'permissions'] as $optionalElementProperty) {
372 $optionalElement = $element->getElementsByTagName($optionalElementProperty)->item(0);
373 if ($optionalElement !== null) {
374 $data[$optionalElementProperty] = $optionalElement->nodeValue;
375 } elseif ($saveData) {
376 switch ($optionalElementProperty) {
377 case 'environment':
378 $data[$optionalElementProperty] = 'user';
379 break;
380
381 case 'inherit':
382 $data[$optionalElementProperty] = 0;
383 break;
384
385 case 'options':
386 case 'permissions':
387 $data[$optionalElementProperty] = '';
388 break;
389 }
390 }
391 }
392
393 $niceValue = $element->getElementsByTagName('nice')->item(0);
394 if ($niceValue !== null) {
395 $data['niceValue'] = $niceValue->nodeValue;
396 } elseif ($saveData) {
397 $data['niceValue'] = 0;
398 }
399
400 return $data;
401 }
402
403 /**
404 * @inheritDoc
405 * @since 5.2
406 */
407 public function getElementIdentifier(\DOMElement $element)
408 {
409 return $element->getAttribute('name');
410 }
411
412 /**
413 * @inheritDoc
414 * @since 5.2
415 */
416 protected function setEntryListKeys(IDevtoolsPipEntryList $entryList)
417 {
418 $entryList->setKeys([
419 'listenerName' => 'wcf.acp.pip.eventListener.listenerName',
420 'eventClassName' => 'wcf.acp.pip.eventListener.eventClassName',
421 'eventName' => 'wcf.acp.pip.eventListener.eventName',
422 ]);
423 }
424
425 /**
426 * @inheritDoc
427 * @since 5.2
428 */
429 protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form)
430 {
431 $data = $form->getData()['data'];
432
433 $eventListener = $document->createElement($this->tagName);
434 $eventListener->setAttribute('name', $data['listenerName']);
435
436 $this->appendElementChildren(
437 $eventListener,
438 [
439 'eventclassname',
bc538e1c 440 'eventname' => '',
76844a15
TD
441 'listenerclassname',
442 'environment' => 'user',
443 'inherit' => 0,
444 'nice' => null,
445 'options' => '',
446 'permissions' => '',
447 ],
448 $form
449 );
450
451 return $eventListener;
452 }
11ade432 453}