Add GUI for eventListener package installation plugin
authorMatthias Schmidt <gravatronics@live.com>
Fri, 13 Jul 2018 08:34:20 +0000 (10:34 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Fri, 13 Jul 2018 08:34:20 +0000 (10:34 +0200)
See #2545

wcfsetup/install/files/lib/system/package/plugin/EventListenerPackageInstallationPlugin.class.php
wcfsetup/install/lang/en.xml

index 505fdaf5d5bf835f4fa76d2ba625d00a007911a6..e6e24ef32ef23ee156bb91f9725e1387477eaf78 100644 (file)
@@ -3,20 +3,37 @@ declare(strict_types=1);
 namespace wcf\system\package\plugin;
 use wcf\data\event\listener\EventListener;
 use wcf\data\event\listener\EventListenerEditor;
+use wcf\data\event\listener\EventListenerList;
 use wcf\system\cache\builder\EventListenerCacheBuilder;
-use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin;
+use wcf\system\devtools\pip\IDevtoolsPipEntryList;
+use wcf\system\devtools\pip\IGuiPackageInstallationPlugin;
+use wcf\system\devtools\pip\TXmlGuiPackageInstallationPlugin;
+use wcf\system\event\listener\IParameterizedEventListener;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\BooleanFormField;
+use wcf\system\form\builder\field\ClassNameFormField;
+use wcf\system\form\builder\field\IntegerFormField;
+use wcf\system\form\builder\field\OptionFormField;
+use wcf\system\form\builder\field\SingleSelectionFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\field\UserGroupOptionFormField;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\field\validation\FormFieldValidator;
+use wcf\system\form\builder\IFormDocument;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes event listeners.
  * 
- * @author     Marcel Werk
+ * @author     Matthias Schmidt, Marcel Werk
  * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
+class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
+       use TXmlGuiPackageInstallationPlugin;
+       
        /**
         * @inheritDoc
         */
@@ -161,9 +178,193 @@ class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallat
        
        /**
         * @inheritDoc
+        * @since       3.1
         */
        public static function getSyncDependencies() {
                return [];
        }
        
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function addFormFields(IFormDocument $form) {
+               /** @var FormContainer $dataContainer */
+               $dataContainer = $form->getNodeById('data');
+               
+               $dataContainer->appendChildren([
+                       TextFormField::create('listenerName')
+                               ->label('wcf.acp.pip.eventListener.listenerName')
+                               ->description('wcf.acp.pip.eventListener.listenerName.description')
+                               ->required()
+                               ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) {
+                                       if (preg_match('~^[a-z][A-z0-9]*$~', $formField->getSaveValue()) !== 1) {
+                                               $formField->addValidationError(
+                                                       new FormFieldValidationError(
+                                                               'format',
+                                                               'wcf.acp.pip.eventListener.listenerName.error.format'
+                                                       )
+                                               );
+                                       }
+                               }))
+                               ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) {
+                                       if (
+                                               $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE ||
+                                               $this->editedEntry->getAttribute('name') !== $formField->getValue()
+                                       ) {
+                                               $eventListenerList = new EventListenerList();
+                                               $eventListenerList->getConditionBuilder()->add('listenerName = ?', [$formField->getValue()]);
+                                               
+                                               if ($eventListenerList->countObjects() > 0) {
+                                                       $formField->addValidationError(
+                                                               new FormFieldValidationError(
+                                                                       'notUnique',
+                                                                       'wcf.acp.pip.eventListener.listenerName.error.notUnique'
+                                                               )
+                                                       );
+                                               }
+                                       }
+                               })),
+                       
+                       ClassNameFormField::create('eventClassName')
+                               ->objectProperty('eventclassname')
+                               ->label('wcf.acp.pip.eventListener.eventClassName')
+                               ->description('wcf.acp.pip.eventListener.eventClassName.description')
+                               ->required()
+                               ->instantiable(false),
+                       
+                       TextFormField::create('eventName')
+                               ->objectProperty('eventname')
+                               ->label('wcf.acp.pip.eventListener.eventName')
+                               ->description('wcf.acp.pip.eventListener.eventName.description')
+                               ->required(),
+                       
+                       ClassNameFormField::create('listenerClassName')
+                               ->objectProperty('listenerclassname')
+                               ->label('wcf.acp.pip.eventListener.listenerClassName')
+                               ->required()
+                               ->implementedInterface(IParameterizedEventListener::class),
+                       
+                       SingleSelectionFormField::create('environment')
+                               ->label('wcf.acp.pip.eventListener.environment')
+                               ->description('wcf.acp.pip.eventListener.environment.description')
+                               ->options([
+                                       'admin' => 'admin',
+                                       'user' => 'user'
+                               ])
+                               ->value('user'),
+                       
+                       BooleanFormField::create('inherit')
+                               ->label('wcf.acp.pip.eventListener.inherit')
+                               ->description('wcf.acp.pip.eventListener.inherit.description'),
+                       
+                       IntegerFormField::create('niceValue')
+                               ->objectProperty('nice')
+                               ->label('wcf.acp.pip.eventListener.niceValue')
+                               ->description('wcf.acp.pip.eventListener.niceValue.description')
+                               ->nullable()
+                               ->minimum(-128)
+                               ->maximum(127),
+                       
+                       OptionFormField::create()
+                               ->description('wcf.acp.pip.eventListener.options.description')
+                               ->packageIDs(array_merge(
+                                       [$this->installation->getPackage()->packageID],
+                                       array_keys($this->installation->getPackage()->getAllRequiredPackages())
+                               )),
+                       
+                       UserGroupOptionFormField::create()
+                               ->description('wcf.acp.pip.eventListener.permissions.description')
+                               ->packageIDs(array_merge(
+                                       [$this->installation->getPackage()->packageID],
+                                       array_keys($this->installation->getPackage()->getAllRequiredPackages())
+                               ))
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function getElementData(\DOMElement $element, bool $saveData = false): array {
+               $data = [
+                       'eventClassName' => $element->getElementsByTagName('eventclassname')->item(0)->nodeValue,
+                       'eventName' => StringUtil::normalizeCsv($element->getElementsByTagName('eventname')->item(0)->nodeValue),
+                       'listenerClassName' => $element->getElementsByTagName('listenerclassname')->item(0)->nodeValue,
+                       'listenerName' => $element->getAttribute('name'),
+                       'packageID' => $this->installation->getPackage()->packageID
+               ];
+               
+               foreach (['environment', 'inherit', 'nice', 'options', 'permissions'] as $optionalElementProperty) {
+                       $optionalElement = $element->getElementsByTagName($optionalElementProperty)->item(0);
+                       if ($optionalElement !== null) {
+                               $data[$optionalElementProperty] = $optionalElement->nodeValue;
+                       }
+               }
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function getElementIdentifier(\DOMElement $element): string {
+               return $element->getAttribute('name');
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
+               $entryList->setKeys([
+                       'listenerName' => 'wcf.acp.pip.eventListener.listenerName',
+                       'eventClassName' => 'wcf.acp.pip.eventListener.eventClassName',
+                       'eventName' => 'wcf.acp.pip.eventListener.eventName'
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function sortDocument(\DOMDocument $document) {
+               $this->sortImportDelete($document);
+               
+               $compareFunction = function(\DOMElement $element1, \DOMElement $element2) {
+                       return strcmp(
+                               $element1->getAttribute('name'),
+                               $element2->getAttribute('name')
+                       );
+               };
+               
+               $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction);
+               $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction);
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function writeEntry(\DOMDocument $document, IFormDocument $form): \DOMElement {
+               $data = $form->getData()['data'];
+               
+               $eventListener = $document->createElement($this->tagName);
+               $eventListener->setAttribute('name', $data['listenerName']);
+               
+               foreach (['eventclassname', 'eventname', 'listenerclassname'] as $property) {
+                       $eventListener->appendChild($document->createElement($property, $data[$property]));
+               }
+               
+               foreach (['environment', 'inherit', 'nice', 'options', 'permissions'] as $optionalProperty) {
+                       if (!empty($data[$optionalProperty])) {
+                               $eventListener->appendChild($document->createElement($optionalProperty, (string)$data[$optionalProperty]));
+                       }
+               }
+               
+               $document->getElementsByTagName('import')->item(0)->appendChild($eventListener);
+               
+               return $eventListener;
+       }
 }
index 0b7146d899b1a07772fe2d6ac3c598dc8338f5ad..fd4e7237551d83184f25b2f3e4d182660da3f606 100644 (file)
@@ -2032,6 +2032,23 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pip.page.contentCustomURL"><![CDATA[Custom URL]]></item>
                <item name="wcf.acp.pip.page.contentMetaDescription"><![CDATA[Meta Description]]></item>
                <item name="wcf.acp.pip.page.contentMetaKeywords"><![CDATA[Meta Keywords]]></item>
+               <item name="wcf.acp.pip.eventListener.listenerName"><![CDATA[Event Listener Identifier]]></item>
+               <item name="wcf.acp.pip.eventListener.listenerName.description"><![CDATA[Unique textual identifier of the event listener that may only contain letters and must start with a lowercase letter.]]></item>
+               <item name="wcf.acp.pip.eventListener.listenerName.error.format"><![CDATA[The entered identifier is invalid.]]></item>
+               <item name="wcf.acp.pip.eventListener.listenerName.error.notUnique"><![CDATA[The entered identifier is already used by another event listener.]]></item>
+               <item name="wcf.acp.pip.eventListener.eventClassName"><![CDATA[PHP Event Class]]></item>
+               <item name="wcf.acp.pip.eventListener.eventClassName.description"><![CDATA[The entered class (without leading backslash) fires the event. Alternatively, the entered class inherits from the the class firing the event.]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName"><![CDATA[Event Name]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName.description"><![CDATA[Name of the event of the relevant class the event listener is listening to.]]></item>
+               <item name="wcf.acp.pip.eventListener.listenerClassName"><![CDATA[PHP Event Listener Class]]></item>
+               <item name="wcf.acp.pip.eventListener.environment"><![CDATA[Environment]]></item>
+               <item name="wcf.acp.pip.eventListener.environment.description"><![CDATA[The environment determines whether the event listener is executed in the frontend (<code>user</code>) or the ACP (<code>admin</code>).]]></item>
+               <item name="wcf.acp.pip.eventListener.inherit"><![CDATA[Inherit Event Listener]]></item>
+               <item name="wcf.acp.pip.eventListener.inherit.description"><![CDATA[If an event listener is inherited, it is not only triggered if the event is fired by the class entered above, but it is also fired by classes inheriting from the entered class.]]></item>
+               <item name="wcf.acp.pip.eventListener.niceValue"><![CDATA[Nice Value]]></item>
+               <item name="wcf.acp.pip.eventListener.niceValue.description"><![CDATA[The nice value is used to determine the order in which event listeners for the same event are executed. Event listeners with lower nice value are executed first.]]></item>
+               <item name="wcf.acp.pip.eventListener.options.description"><![CDATA[At least one of the entered options has to be enabled for the event listener to be executed. Options of non-required packages will be reported as non-existing.]]></item>
+               <item name="wcf.acp.pip.eventListener.permissions.description"><![CDATA[The active user must be granted at least one of the entered permissions for the event listener to be executed. Permissions of non-required packages will be reported as non-existing.]]></item>
        </category>
        
        <category name="wcf.acp.rebuildData">