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
*/
/**
* @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;
+ }
}
<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">