From 4c884f7edddca764846fa6452a67166d6a9410ce Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 6 May 2018 13:35:29 +0200 Subject: [PATCH] Add GUI for template listener package installation plugin See #2545 --- .../data/acp/template/ACPTemplate.class.php | 21 ++ .../files/lib/data/package/Package.class.php | 33 ++ ...istenerPackageInstallationPlugin.class.php | 310 +++++++++++++++++- wcfsetup/install/lang/en.xml | 12 + 4 files changed, 373 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/data/acp/template/ACPTemplate.class.php b/wcfsetup/install/files/lib/data/acp/template/ACPTemplate.class.php index 06bd9d8e3d..9f421aeffb 100644 --- a/wcfsetup/install/files/lib/data/acp/template/ACPTemplate.class.php +++ b/wcfsetup/install/files/lib/data/acp/template/ACPTemplate.class.php @@ -2,6 +2,7 @@ declare(strict_types=1); namespace wcf\data\acp\template; use wcf\data\DatabaseObject; +use wcf\data\package\PackageCache; /** * Represents an ACP template. @@ -21,4 +22,24 @@ class ACPTemplate extends DatabaseObject { * @inheritDoc */ protected static $databaseTableIndexName = 'templateID'; + + /** + * Returns the path to this template. + * + * @return string + * @since 3.2 + */ + public function getPath() { + return PackageCache::getInstance()->getPackage($this->packageID)->getAbsolutePackageDir() . 'acp/templates/' . $this->templateName . '.tpl'; + } + + /** + * Returns the source of this template. + * + * @return string + * @since 3.2 + */ + public function getSource() { + return @file_get_contents($this->getPath()); + } } diff --git a/wcfsetup/install/files/lib/data/package/Package.class.php b/wcfsetup/install/files/lib/data/package/Package.class.php index a573b08755..8d1876b2c8 100644 --- a/wcfsetup/install/files/lib/data/package/Package.class.php +++ b/wcfsetup/install/files/lib/data/package/Package.class.php @@ -29,6 +29,13 @@ use wcf\util\FileUtil; * @property-read string $authorURL external url to the website of the package author */ class Package extends DatabaseObject { + /** + * recursive list of packages that were given as required packages during installation + * @var Package[] + * @since 3.2 + */ + protected $allRequiredPackages; + /** * list of packages that this package requires * @var Package[] @@ -138,6 +145,32 @@ class Package extends DatabaseObject { return $this->requiredPackages; } + /** + * Returns the recursive list of packages which are required by this package. + * The returned packages are the packages given in the tag + * in the package.xml of this package and recursively repeats that for all of + * those required packages. + * + * @return Package[] + * @since 3.2 + */ + public function getAllRequiredPackages() { + if ($this->allRequiredPackages === null) { + $this->allRequiredPackages = $this->getRequiredPackages(); + $packagesToCheck = $this->allRequiredPackages; + + /** @var Package $checkedPackage */ + while (($checkedPackage = array_pop($packagesToCheck))) { + $newRequiredPackages = array_diff($checkedPackage->getRequiredPackages(), $this->allRequiredPackages); + + $this->allRequiredPackages += $newRequiredPackages; + $packagesToCheck = array_merge($packagesToCheck, $newRequiredPackages); + } + } + + return $this->allRequiredPackages; + } + /** * Returns true if current user can uninstall this package. * diff --git a/wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php index 6ce8a75ae5..7642d1ba85 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/TemplateListenerPackageInstallationPlugin.class.php @@ -1,21 +1,40 @@ * @package WoltLabSuite\Core\System\Package\Plugin */ -class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { +class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin { + use TXmlGuiPackageInstallationPlugin; + /** * @inheritDoc */ @@ -102,8 +121,293 @@ class TemplateListenerPackageInstallationPlugin extends AbstractXMLPackageInstal /** * @inheritDoc + * @since 3.1 */ public static function getSyncDependencies() { return []; } + + /** + * @inheritDoc + * @since 3.2 + */ + public function addFormFields(IFormDocument $form) { + $ldq = preg_quote(WCF::getTPL()->getCompiler()->getLeftDelimiter(), '~'); + $rdq = preg_quote(WCF::getTPL()->getCompiler()->getRightDelimiter(), '~'); + + $getEvents = function($templateList) use ($ldq, $rdq) { + $templateEvents = []; + /** @var ACPTemplate|Template $template */ + foreach ($templateList as $template) { + if (preg_match_all("~{$ldq}event\ name\=\'(?[\w]+)\'{$rdq}~", $template->getSource(), $matches)) { + $templates[$template->templateName] = $template->templateName; + + foreach ($matches['event'] as $event) { + if (!isset($templateEvents[$template->templateName])) { + $templateEvents[$template->templateName] = []; + } + + $templateEvents[$template->templateName][] = $event; + } + } + } + + foreach ($templateEvents as &$events) { + sort($events); + } + unset($events); + + return $templateEvents; + }; + + $templateList = new TemplateList(); + $templateList->getConditionBuilder()->add( + 'template.packageID IN (?)', + [array_keys($this->installation->getPackage()->getAllRequiredPackages())] + ); + $templateList->getConditionBuilder()->add('template.templateGroupID IS NULL'); + $templateList->sqlOrderBy = 'template.templateName ASC'; + $templateList->readObjects(); + + $templateEvents = $getEvents($templateList); + + $acpTemplateList = new ACPTemplateList(); + $acpTemplateList->getConditionBuilder()->add( + 'acp_template.packageID IN (?)', + [array_keys($this->installation->getPackage()->getAllRequiredPackages())] + ); + $acpTemplateList->sqlOrderBy = 'acp_template.templateName ASC'; + $acpTemplateList->readObjects(); + + $acpTemplateEvents = $getEvents($acpTemplateList); + + $form->getNodeById('data')->appendChildren([ + TextFormField::create('name') + ->label('wcf.acp.pip.templateListener.name') + ->description('wcf.acp.pip.templateListener.name.description') + ->required() + ->addValidator(new FormFieldValidator('format', function(TextFormField $formField) { + if (!preg_match('~^[a-z][A-z]+$~', $formField->getValue())) { + $formField->addValidationError( + new FormFieldValidationError( + 'format', + 'wcf.acp.pip.templateListener.name.error.format' + ) + ); + } + })), + + SingleSelectionFormField::create('templateName') + ->attribute('data-tag', 'templatename') + ->label('wcf.acp.pip.templateListener.templateName') + ->description('wcf.acp.pip.templateListener.templateName.description') + ->required() + ->options(array_combine(array_keys($templateEvents), array_keys($templateEvents))) + ->filterable(), + + SingleSelectionFormField::create('acpTemplateName') + ->attribute('data-tag', 'templatename') + ->label('wcf.acp.pip.templateListener.templateName') + ->description('wcf.acp.pip.templateListener.templateName.description') + ->required() + ->options(array_combine(array_keys($acpTemplateEvents), array_keys($acpTemplateEvents))) + ->filterable() + ]); + + foreach ($templateEvents as $templateName => $events) { + $form->getNodeById('data')->appendChild( + SingleSelectionFormField::create($templateName . '_eventName') + ->attribute('data-tag', 'eventname') + ->label('wcf.acp.pip.templateListener.eventName') + ->description('wcf.acp.pip.templateListener.eventName.description') + ->required() + ->options(array_combine($events, $events)) + ->addDependency( + ValueFormFieldDependency::create('templateName') + ->field($form->getNodeById('templateName')) + ->values([$templateName]) + ) + ); + } + + foreach ($acpTemplateEvents as $templateName => $events) { + $form->getNodeById('data')->appendChild( + SingleSelectionFormField::create('acp_' . $templateName . '_eventName') + ->attribute('data-tag', 'eventname') + ->label('wcf.acp.pip.templateListener.eventName') + ->description('wcf.acp.pip.templateListener.eventName.description') + ->required() + ->options(array_combine($events, $events)) + ->addDependency( + ValueFormFieldDependency::create('acpTemplateName') + ->field($form->getNodeById('acpTemplateName')) + ->values([$templateName]) + ) + ); + } + + $form->getNodeById('data')->appendChildren([ + SingleSelectionFormField::create('environment') + ->label('wcf.acp.pip.templateListener.environment') + ->description('wcf.acp.pip.templateListener.environment.description') + ->required() + ->options([ + 'admin' => 'admin', + 'user' => 'user' + ]) + ->value('user') + ->addValidator(new FormFieldValidator('uniqueness', function(SingleSelectionFormField $formField) { + $listenerList = new TemplateListenerList(); + $listenerList->getConditionBuilder()->add( + 'name = ?', + [$formField->getDocument()->getNodeById('name')->getSaveValue()] + ); + + if ($formField->getSaveValue() === 'admin') { + $templateName = $formField->getDocument()->getNodeById('acpTemplateName')->getSaveValue(); + $eventName = $formField->getDocument()->getNodeById('acp_' . $templateName . '_eventName')->getSaveValue(); + } + else { + $templateName = $formField->getDocument()->getNodeById('templateName')->getSaveValue(); + $eventName = $formField->getDocument()->getNodeById($templateName . '_eventName')->getSaveValue(); + } + + $listenerList->getConditionBuilder()->add('templateName = ?', [$templateName]); + + $listenerList->getConditionBuilder()->add('eventName = ?', [$eventName]); + $listenerList->getConditionBuilder()->add('environment = ?', [$formField->getSaveValue()]); + })), + + // TODO: use field with code support + MultilineTextFormField::create('templateCode') + ->attribute('data-tag', 'templatecode') + ->label('wcf.acp.pip.templateListener.templateCode') + ->description('wcf.acp.pip.templateListener.templateCode.description') + ->required() + ]); + + $form->getNodeById('templateName')->addDependency( + ValueFormFieldDependency::create('environment') + ->field($form->getNodeById('environment')) + ->values(['user']) + ); + $form->getNodeById('acpTemplateName')->addDependency( + ValueFormFieldDependency::create('environment') + ->field($form->getNodeById('environment')) + ->values(['admin']) + ); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function getElementData(\DOMElement $element): array { + return [ + 'environment' => $element->getElementsByTagName('environment')->item(0)->nodeValue, + 'eventName' => $element->getElementsByTagName('eventname')->item(0)->nodeValue, + 'name' => $element->getAttribute('name'), + 'packageID' => $this->installation->getPackage()->packageID, + 'templateCode' => $element->getElementsByTagName('templatecode')->item(0)->nodeValue, + 'templateName' => $element->getElementsByTagName('templatename')->item(0)->nodeValue + ]; + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function getElementIdentifier(\DOMElement $element): string { + return sha1( + $element->getElementsByTagName('templatename')->item(0)->nodeValue . '/' . + $element->getElementsByTagName('eventname')->item(0)->nodeValue . '/' . + $element->getElementsByTagName('environment')->item(0)->nodeValue . '/' . + $element->getAttribute('name') + ); + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function getEntryList(): IDevtoolsPipEntryList { + $xml = $this->getProjectXml(); + $xpath = $xml->xpath(); + + $entryList = new DevtoolsPipEntryList(); + $entryList->setKeys([ + 'name' => 'wcf.acp.pip.templateListener.name', + 'templateName' => 'wcf.acp.pip.templateListener.templateName', + 'eventName' => 'wcf.acp.pip.templateListener.eventName', + 'environment' => 'wcf.acp.pip.templateListener.environment' + ]); + + /** @var \DOMElement $element */ + foreach ($this->getImportElements($xpath) as $element) { + $entryList->addEntry($this->getElementIdentifier($element), array_intersect_key($this->getElementData($element), $entryList->getKeys())); + } + + return $entryList; + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function sortDocument(\DOMDocument $document) { + $this->sortImportDelete($document); + + $compareFunction = function(\DOMElement $element1, \DOMElement $element2) { + $templateName1 = $element1->getElementsByTagName('templatename')->item(0)->nodeValue; + $templateName2 = $element2->getElementsByTagName('templatename')->item(0)->nodeValue; + + if ($templateName1 !== $templateName2) { + return strcmp($templateName1, $templateName2); + } + + $eventName1 = $element1->getElementsByTagName('eventname')->item(0)->nodeValue; + $eventName2 = $element2->getElementsByTagName('eventname')->item(0)->nodeValue; + + if ($eventName1 !== $eventName2) { + return strcmp($eventName1, $eventName2); + } + + return strcmp( + $element1->getElementsByTagName('environment')->item(0)->nodeValue, + $element2->getElementsByTagName('environment')->item(0)->nodeValue + ); + }; + + $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction); + $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function writeEntry(\DOMDocument $document, IFormDocument $form): \DOMElement { + $listener = $document->createElement($this->tagName); + $listener->setAttribute('name', $form->getNodeById('name')->getSaveValue()); + + $environment = $form->getNodeById('environment')->getSaveValue(); + if ($environment === 'user') { + $templateName = $form->getNodeById('templateName')->getSaveValue(); + + $listener->appendChild($document->createElement('templatename', $templateName)); + $listener->appendChild($document->createElement('eventname', $form->getNodeById($templateName . '_eventName')->getSaveValue())); + } + else { + $templateName = $form->getNodeById('acpTemplateName')->getSaveValue(); + + $listener->appendChild($document->createElement('templatename', $templateName)); + $listener->appendChild($document->createElement('eventname', $form->getNodeById('acp_' . $templateName . '_eventName')->getSaveValue())); + } + $listener->appendChild($document->createElement('templatecode', 'getNodeById('templateCode')->getSaveValue())) . ']]>')); + $listener->appendChild($document->createElement('environment', $environment)); + + $document->getElementsByTagName('import')->item(0)->appendChild($listener); + + return $listener; + } } diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index b09dc58191..0927235f52 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1882,6 +1882,18 @@ When prompted for the notification URL for the instant payment notifications, pl + + user) or the ACP (admin).]]> + + + + + + + + + + -- 2.20.1