| 1 | <?php |
| 2 | namespace wcf\system\package\plugin; |
| 3 | use wcf\data\event\listener\EventListener; |
| 4 | use wcf\data\event\listener\EventListenerEditor; |
| 5 | use wcf\data\event\listener\EventListenerList; |
| 6 | use wcf\system\cache\builder\EventListenerCacheBuilder; |
| 7 | use wcf\system\devtools\pip\IDevtoolsPipEntryList; |
| 8 | use wcf\system\devtools\pip\IGuiPackageInstallationPlugin; |
| 9 | use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin; |
| 10 | use wcf\system\event\listener\IParameterizedEventListener; |
| 11 | use wcf\system\form\builder\container\FormContainer; |
| 12 | use wcf\system\form\builder\field\BooleanFormField; |
| 13 | use wcf\system\form\builder\field\ClassNameFormField; |
| 14 | use wcf\system\form\builder\field\IntegerFormField; |
| 15 | use wcf\system\form\builder\field\ItemListFormField; |
| 16 | use wcf\system\form\builder\field\option\OptionFormField; |
| 17 | use wcf\system\form\builder\field\SingleSelectionFormField; |
| 18 | use wcf\system\form\builder\field\TextFormField; |
| 19 | use wcf\system\form\builder\field\user\group\option\UserGroupOptionFormField; |
| 20 | use wcf\system\form\builder\field\validation\FormFieldValidationError; |
| 21 | use wcf\system\form\builder\field\validation\FormFieldValidator; |
| 22 | use wcf\system\form\builder\IFormDocument; |
| 23 | use wcf\system\WCF; |
| 24 | use wcf\util\StringUtil; |
| 25 | |
| 26 | /** |
| 27 | * Installs, updates and deletes event listeners. |
| 28 | * |
| 29 | * @author Matthias Schmidt, Marcel Werk |
| 30 | * @copyright 2001-2021 WoltLab GmbH |
| 31 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
| 32 | * @package WoltLabSuite\Core\System\Package\Plugin |
| 33 | */ |
| 34 | class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin { |
| 35 | use TXmlGuiPackageInstallationPlugin; |
| 36 | |
| 37 | /** |
| 38 | * @inheritDoc |
| 39 | */ |
| 40 | public $className = EventListenerEditor::class; |
| 41 | |
| 42 | /** |
| 43 | * @inheritDoc |
| 44 | */ |
| 45 | public $tagName = 'eventlistener'; |
| 46 | |
| 47 | /** |
| 48 | * @inheritDoc |
| 49 | */ |
| 50 | protected function handleDelete(array $items) { |
| 51 | $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName." |
| 52 | WHERE packageID = ? |
| 53 | AND environment = ? |
| 54 | AND eventClassName = ? |
| 55 | AND eventName = ? |
| 56 | AND inherit = ? |
| 57 | AND listenerClassName = ?"; |
| 58 | $legacyStatement = WCF::getDB()->prepareStatement($sql); |
| 59 | |
| 60 | $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName." |
| 61 | WHERE packageID = ? |
| 62 | AND listenerName = ?"; |
| 63 | $statement = WCF::getDB()->prepareStatement($sql); |
| 64 | |
| 65 | foreach ($items as $item) { |
| 66 | if (!isset($item['attributes']['name'])) { |
| 67 | $legacyStatement->execute([ |
| 68 | $this->installation->getPackageID(), |
| 69 | isset($item['elements']['environment']) ? $item['elements']['environment'] : 'user', |
| 70 | $item['elements']['eventclassname'], |
| 71 | $item['elements']['eventname'], |
| 72 | isset($item['elements']['inherit']) ? $item['elements']['inherit'] : 0, |
| 73 | $item['elements']['listenerclassname'] |
| 74 | ]); |
| 75 | } |
| 76 | else { |
| 77 | $statement->execute([ |
| 78 | $this->installation->getPackageID(), |
| 79 | $item['attributes']['name'] |
| 80 | ]); |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * @inheritDoc |
| 87 | */ |
| 88 | protected function prepareImport(array $data) { |
| 89 | $nice = isset($data['elements']['nice']) ? intval($data['elements']['nice']) : 0; |
| 90 | if ($nice < -128) $nice = -128; |
| 91 | else if ($nice > 127) $nice = 127; |
| 92 | |
| 93 | return [ |
| 94 | 'environment' => isset($data['elements']['environment']) ? $data['elements']['environment'] : 'user', |
| 95 | 'eventClassName' => $data['elements']['eventclassname'], |
| 96 | 'eventName' => StringUtil::normalizeCsv($data['elements']['eventname']), |
| 97 | 'inherit' => isset($data['elements']['inherit']) ? intval($data['elements']['inherit']) : 0, |
| 98 | 'listenerClassName' => $data['elements']['listenerclassname'], |
| 99 | 'listenerName' => isset($data['attributes']['name']) ? $data['attributes']['name'] : '', |
| 100 | 'niceValue' => $nice, |
| 101 | 'options' => isset($data['elements']['options']) ? StringUtil::normalizeCsv($data['elements']['options']) : '', |
| 102 | 'permissions' => isset($data['elements']['permissions']) ? StringUtil::normalizeCsv($data['elements']['permissions']) : '' |
| 103 | ]; |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * @inheritDoc |
| 108 | */ |
| 109 | protected function import(array $row, array $data) { |
| 110 | // if an event listener is updated without a name given, keep the |
| 111 | // old automatically assigned name |
| 112 | if (!empty($row) && !$data['listenerName']) { |
| 113 | unset($data['listenerName']); |
| 114 | } |
| 115 | |
| 116 | /** @var EventListener $eventListener */ |
| 117 | $eventListener = parent::import($row, $data); |
| 118 | |
| 119 | // update event listener name |
| 120 | if (!$eventListener->listenerName) { |
| 121 | $eventListenerEditor = new EventListenerEditor($eventListener); |
| 122 | $eventListenerEditor->update([ |
| 123 | 'listenerName' => EventListener::AUTOMATIC_NAME_PREFIX.$eventListener->listenerID |
| 124 | ]); |
| 125 | |
| 126 | $eventListener = new EventListener($eventListener->listenerID); |
| 127 | } |
| 128 | |
| 129 | return $eventListener; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * @inheritDoc |
| 134 | */ |
| 135 | protected function findExistingItem(array $data) { |
| 136 | if (!$data['listenerName']) { |
| 137 | $sql = "SELECT * |
| 138 | FROM wcf".WCF_N."_".$this->tableName." |
| 139 | WHERE packageID = ? |
| 140 | AND environment = ? |
| 141 | AND eventClassName = ? |
| 142 | AND eventName = ? |
| 143 | AND listenerClassName = ?"; |
| 144 | $parameters = [ |
| 145 | $this->installation->getPackageID(), |
| 146 | $data['environment'], |
| 147 | $data['eventClassName'], |
| 148 | $data['eventName'], |
| 149 | $data['listenerClassName'] |
| 150 | ]; |
| 151 | } |
| 152 | else { |
| 153 | $sql = "SELECT * |
| 154 | FROM wcf".WCF_N."_".$this->tableName." |
| 155 | WHERE packageID = ? |
| 156 | AND listenerName = ?"; |
| 157 | $parameters = [ |
| 158 | $this->installation->getPackageID(), |
| 159 | $data['listenerName'] |
| 160 | ]; |
| 161 | } |
| 162 | |
| 163 | return [ |
| 164 | 'sql' => $sql, |
| 165 | 'parameters' => $parameters |
| 166 | ]; |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * @inheritDoc |
| 171 | */ |
| 172 | public function uninstall() { |
| 173 | parent::uninstall(); |
| 174 | |
| 175 | // clear cache immediately |
| 176 | EventListenerCacheBuilder::getInstance()->reset(); |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * @inheritDoc |
| 181 | * @since 3.1 |
| 182 | */ |
| 183 | public static function getSyncDependencies() { |
| 184 | return []; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * @inheritDoc |
| 189 | * @since 5.2 |
| 190 | */ |
| 191 | protected function addFormFields(IFormDocument $form) { |
| 192 | /** @var FormContainer $dataContainer */ |
| 193 | $dataContainer = $form->getNodeById('data'); |
| 194 | |
| 195 | $dataContainer->appendChildren([ |
| 196 | TextFormField::create('listenerName') |
| 197 | ->label('wcf.acp.pip.eventListener.listenerName') |
| 198 | ->description('wcf.acp.pip.eventListener.listenerName.description') |
| 199 | ->required() |
| 200 | ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) { |
| 201 | if (preg_match('~^[a-z][A-z0-9]*$~', $formField->getSaveValue()) !== 1) { |
| 202 | $formField->addValidationError( |
| 203 | new FormFieldValidationError( |
| 204 | 'format', |
| 205 | 'wcf.acp.pip.eventListener.listenerName.error.format' |
| 206 | ) |
| 207 | ); |
| 208 | } |
| 209 | })) |
| 210 | ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) { |
| 211 | if ( |
| 212 | $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE || |
| 213 | $this->editedEntry->getAttribute('name') !== $formField->getValue() |
| 214 | ) { |
| 215 | $eventListenerList = new EventListenerList(); |
| 216 | $eventListenerList->getConditionBuilder()->add('listenerName = ?', [$formField->getValue()]); |
| 217 | $eventListenerList->getConditionBuilder()->add('packageID = ?', [$this->installation->getPackageID()]); |
| 218 | |
| 219 | if ($eventListenerList->countObjects() > 0) { |
| 220 | $formField->addValidationError( |
| 221 | new FormFieldValidationError( |
| 222 | 'notUnique', |
| 223 | 'wcf.acp.pip.eventListener.listenerName.error.notUnique' |
| 224 | ) |
| 225 | ); |
| 226 | } |
| 227 | } |
| 228 | })), |
| 229 | |
| 230 | ClassNameFormField::create('eventClassName') |
| 231 | ->objectProperty('eventclassname') |
| 232 | ->label('wcf.acp.pip.eventListener.eventClassName') |
| 233 | ->description('wcf.acp.pip.eventListener.eventClassName.description') |
| 234 | ->required() |
| 235 | ->instantiable(false), |
| 236 | |
| 237 | ItemListFormField::create('eventName') |
| 238 | ->objectProperty('eventname') |
| 239 | ->label('wcf.acp.pip.eventListener.eventName') |
| 240 | ->description('wcf.acp.pip.eventListener.eventName.description') |
| 241 | ->required(), |
| 242 | |
| 243 | ClassNameFormField::create('listenerClassName') |
| 244 | ->objectProperty('listenerclassname') |
| 245 | ->label('wcf.acp.pip.eventListener.listenerClassName') |
| 246 | ->required() |
| 247 | ->implementedInterface(IParameterizedEventListener::class), |
| 248 | |
| 249 | SingleSelectionFormField::create('environment') |
| 250 | ->label('wcf.acp.pip.eventListener.environment') |
| 251 | ->description('wcf.acp.pip.eventListener.environment.description') |
| 252 | ->options([ |
| 253 | 'admin' => 'admin', |
| 254 | 'user' => 'user', |
| 255 | 'all' => 'all', |
| 256 | ]) |
| 257 | ->value('user'), |
| 258 | |
| 259 | BooleanFormField::create('inherit') |
| 260 | ->label('wcf.acp.pip.eventListener.inherit') |
| 261 | ->description('wcf.acp.pip.eventListener.inherit.description'), |
| 262 | |
| 263 | IntegerFormField::create('niceValue') |
| 264 | ->objectProperty('nice') |
| 265 | ->label('wcf.acp.pip.eventListener.niceValue') |
| 266 | ->description('wcf.acp.pip.eventListener.niceValue.description') |
| 267 | ->nullable() |
| 268 | ->minimum(-128) |
| 269 | ->maximum(127), |
| 270 | |
| 271 | OptionFormField::create() |
| 272 | ->description('wcf.acp.pip.eventListener.options.description') |
| 273 | ->packageIDs(array_merge( |
| 274 | [$this->installation->getPackage()->packageID], |
| 275 | array_keys($this->installation->getPackage()->getAllRequiredPackages()) |
| 276 | )), |
| 277 | |
| 278 | UserGroupOptionFormField::create() |
| 279 | ->description('wcf.acp.pip.eventListener.permissions.description') |
| 280 | ->packageIDs(array_merge( |
| 281 | [$this->installation->getPackage()->packageID], |
| 282 | array_keys($this->installation->getPackage()->getAllRequiredPackages()) |
| 283 | )) |
| 284 | ]); |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * @inheritDoc |
| 289 | * @since 5.2 |
| 290 | */ |
| 291 | protected function fetchElementData(\DOMElement $element, $saveData) { |
| 292 | $data = [ |
| 293 | 'eventClassName' => $element->getElementsByTagName('eventclassname')->item(0)->nodeValue, |
| 294 | 'eventName' => StringUtil::normalizeCsv($element->getElementsByTagName('eventname')->item(0)->nodeValue), |
| 295 | 'listenerClassName' => $element->getElementsByTagName('listenerclassname')->item(0)->nodeValue, |
| 296 | 'listenerName' => $element->getAttribute('name'), |
| 297 | 'packageID' => $this->installation->getPackage()->packageID |
| 298 | ]; |
| 299 | |
| 300 | foreach (['environment', 'inherit', 'options', 'permissions'] as $optionalElementProperty) { |
| 301 | $optionalElement = $element->getElementsByTagName($optionalElementProperty)->item(0); |
| 302 | if ($optionalElement !== null) { |
| 303 | $data[$optionalElementProperty] = $optionalElement->nodeValue; |
| 304 | } |
| 305 | else if ($saveData) { |
| 306 | switch ($optionalElementProperty) { |
| 307 | case 'environment': |
| 308 | $data[$optionalElementProperty] = 'user'; |
| 309 | break; |
| 310 | |
| 311 | case 'inherit': |
| 312 | $data[$optionalElementProperty] = 0; |
| 313 | break; |
| 314 | |
| 315 | case 'options': |
| 316 | case 'permissions': |
| 317 | $data[$optionalElementProperty] = ''; |
| 318 | break; |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | $niceValue = $element->getElementsByTagName('nice')->item(0); |
| 324 | if ($niceValue !== null) { |
| 325 | $data['niceValue'] = $niceValue->nodeValue; |
| 326 | } |
| 327 | else if ($saveData) { |
| 328 | $data['niceValue'] = 0; |
| 329 | } |
| 330 | |
| 331 | return $data; |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * @inheritDoc |
| 336 | * @since 5.2 |
| 337 | */ |
| 338 | public function getElementIdentifier(\DOMElement $element) { |
| 339 | return $element->getAttribute('name'); |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * @inheritDoc |
| 344 | * @since 5.2 |
| 345 | */ |
| 346 | protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) { |
| 347 | $entryList->setKeys([ |
| 348 | 'listenerName' => 'wcf.acp.pip.eventListener.listenerName', |
| 349 | 'eventClassName' => 'wcf.acp.pip.eventListener.eventClassName', |
| 350 | 'eventName' => 'wcf.acp.pip.eventListener.eventName' |
| 351 | ]); |
| 352 | } |
| 353 | |
| 354 | /** |
| 355 | * @inheritDoc |
| 356 | * @since 5.2 |
| 357 | */ |
| 358 | protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form) { |
| 359 | $data = $form->getData()['data']; |
| 360 | |
| 361 | $eventListener = $document->createElement($this->tagName); |
| 362 | $eventListener->setAttribute('name', $data['listenerName']); |
| 363 | |
| 364 | $this->appendElementChildren( |
| 365 | $eventListener, |
| 366 | [ |
| 367 | 'eventclassname', |
| 368 | 'eventname', |
| 369 | 'listenerclassname', |
| 370 | 'environment' => 'user', |
| 371 | 'inherit' => 0, |
| 372 | 'nice' => null, |
| 373 | 'options' => '', |
| 374 | 'permissions' => '' |
| 375 | ], |
| 376 | $form |
| 377 | ); |
| 378 | |
| 379 | return $eventListener; |
| 380 | } |
| 381 | } |