Commit | Line | Data |
---|---|---|
11ade432 | 1 | <?php |
76844a15 | 2 | |
11ade432 | 3 | namespace wcf\system\package\plugin; |
76844a15 | 4 | |
e0497659 | 5 | use wcf\data\event\listener\EventListenerEditor; |
efbc545e | 6 | use wcf\data\event\listener\EventListenerList; |
b401cd0d | 7 | use wcf\system\cache\builder\EventListenerCacheBuilder; |
efbc545e MS |
8 | use wcf\system\devtools\pip\IDevtoolsPipEntryList; |
9 | use wcf\system\devtools\pip\IGuiPackageInstallationPlugin; | |
10 | use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin; | |
bc538e1c | 11 | use wcf\system\event\EventHandler; |
67f97df0 | 12 | use wcf\system\event\IEvent; |
efbc545e MS |
13 | use wcf\system\event\listener\IParameterizedEventListener; |
14 | use wcf\system\form\builder\container\FormContainer; | |
15 | use wcf\system\form\builder\field\BooleanFormField; | |
16 | use wcf\system\form\builder\field\ClassNameFormField; | |
17 | use wcf\system\form\builder\field\IntegerFormField; | |
8a53406e | 18 | use wcf\system\form\builder\field\ItemListFormField; |
35d95be3 | 19 | use wcf\system\form\builder\field\option\OptionFormField; |
efbc545e MS |
20 | use wcf\system\form\builder\field\SingleSelectionFormField; |
21 | use wcf\system\form\builder\field\TextFormField; | |
35d95be3 | 22 | use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField; |
efbc545e MS |
23 | use wcf\system\form\builder\field\validation\FormFieldValidationError; |
24 | use wcf\system\form\builder\field\validation\FormFieldValidator; | |
25 | use wcf\system\form\builder\IFormDocument; | |
11ade432 | 26 | use wcf\system\WCF; |
4f2d3f58 | 27 | use 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 | 36 | class 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 | } |