From: Matthias Schmidt Date: Sun, 10 Jun 2018 08:45:51 +0000 (+0200) Subject: Add GUI support for acp menu package installation plugin X-Git-Tag: 5.2.0_Alpha_1~720 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=ed57b5c9228f5ed534f4d292ca0ae977edc90802;p=GitHub%2FWoltLab%2FWCF.git Add GUI support for acp menu package installation plugin See #2545 --- diff --git a/wcfsetup/install/files/lib/system/package/plugin/ACPMenuPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/ACPMenuPackageInstallationPlugin.class.php index 340c1d8c63..23d08c80a9 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/ACPMenuPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/ACPMenuPackageInstallationPlugin.class.php @@ -1,17 +1,32 @@ * @package WoltLabSuite\Core\System\Package\Plugin */ -class ACPMenuPackageInstallationPlugin extends AbstractMenuPackageInstallationPlugin { +class ACPMenuPackageInstallationPlugin extends AbstractMenuPackageInstallationPlugin implements IGuiPackageInstallationPlugin { /** * @inheritDoc */ @@ -29,10 +44,246 @@ class ACPMenuPackageInstallationPlugin extends AbstractMenuPackageInstallationPl } /** - * @see \wcf\system\package\plugin\IPackageInstallationPlugin::getDefaultFilename() + * @inheritDoc * @since 3.0 */ public static function getDefaultFilename() { return 'acpMenu.xml'; } + + /** + * @inheritDoc + * @since 3.2 + */ + public function addFormFields(IFormDocument $form) { + parent::addFormFields($form); + + /** @var IFormContainer $dataContainer */ + $dataContainer = $form->getNodeById('data'); + + // add parent menu item options + + $acpMenuStructureData = $this->getACPMenuStructureData(); + $acpMenuStructure = $acpMenuStructureData['structure']; + $menuItemLevels = ['' => 0] + $acpMenuStructureData['levels']; + + // only consider menu items until the third level (thus only parent + // menu items until the second level) as potential parent menu items + $acpMenuStructure = array_filter($acpMenuStructure, function(string $parentMenuItem) use ($menuItemLevels): bool { + return $menuItemLevels[$parentMenuItem] <= 2; + }, ARRAY_FILTER_USE_KEY); + + // icons are only available for menu items on the first or fourth level + // thus the parent menu item must be on zeroth level (no parent menu item) + // or on the third level + $iconParentMenuItems = array_keys(array_filter($menuItemLevels, function(int $value): bool { + return $value === 0 || $value == 3; + })); + + $buildOptions = function(string $parent = '', int $level = 0) use ($acpMenuStructure, &$buildOptions): array { + $options = []; + foreach ($acpMenuStructure[$parent] as $menuItem) { + $options[$menuItem->menuItem] = str_repeat('    ', $level) . WCF::getLanguage()->get($menuItem->menuItem); + + if (isset($acpMenuStructure[$menuItem->menuItem])) { + $options += $buildOptions($menuItem->menuItem, $level + 1); + } + } + + return $options; + }; + + /** @var SingleSelectionFormField $parentMenuItemFormField */ + $parentMenuItemFormField = $dataContainer->getNodeById('parentMenuItem'); + $parentMenuItemFormField + ->options(['' => 'wcf.global.noSelection'] + $buildOptions()) + ->value(''); + + // add menu icon form field + + // TODO: if an `IconFormField` class should be added, use that class instead + $dataContainer->appendChild(SingleSelectionFormField::create('icon') + ->label('wcf.acp.pip.acpMenu.icon') + ->description('wcf.acp.pip.acpMenu.icon.description') + ->filterable() + ->options(function(): array { + $icons = array_map(function(string $icon): string { + return 'fa-' . $icon; + }, StyleHandler::getInstance()->getIcons()); + + return ['' => 'wcf.global.noSelection'] + array_combine($icons, $icons); + }) + ->addDependency( + ValueFormFieldDependency::create('parentMenuItem') + ->field($parentMenuItemFormField) + ->values($iconParentMenuItems) + )); + + // add additional data to default fields + + /** @var TextFormField $menuItemFormField */ + $menuItemFormField = $form->getNodeById('menuItem'); + $menuItemFormField + ->description('wcf.acp.pip.acpMenu.menuItem.description') + ->addValidator(FormFieldValidatorUtil::getRegularExpressionValidator( + '[a-z]+\.acp\.menu\.link(\.[A-z0-9])+', + 'wcf.acp.pip.acpMenu.menuItem' + )) + ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) { + if ( + $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE || + $this->editedEntry->getAttribute('name') !== $formField->getValue() + ) { + $menuItemName = new ACPMenuItemList(); + $menuItemName->getConditionBuilder()->add('menuItem = ?', [$formField->getValue()]); + + if ($menuItemName->countObjects() > 0) { + $formField->addValidationError( + new FormFieldValidationError( + 'notUnique', + 'wcf.acp.pip.abstractMenu.menuItem.error.notUnique' + ) + ); + } + } + })); + + /** @var TextFormField $menuItemControllerFormField */ + $menuItemControllerFormField = $form->getNodeById('menuItemController'); + $menuItemControllerFormField->addValidator(new FormFieldValidator('acpController', function(TextFormField $formField) { + // the controller must be an ACP controller + if ($formField->getSaveValue() !== '' && !preg_match("~^[a-z]+\\\\acp\\\\~", $formField->getSaveValue())) { + $formField->addValidationError( + new FormFieldValidationError( + 'noAcpController', + 'wcf.acp.pip.acpMenu.menuItemController.error.noAcpController' + ) + ); + } + })); + + // add dependencies to default fields + + // menu items on the first and second level do not support links, + // thus the parent menu item must be at least on the second level + // for the menu item to support links + $menuItemsSupportingLinks = array_keys(array_filter($menuItemLevels, function(int $menuItemLevel): bool { + return $menuItemLevel >= 2; + })); + + foreach (['menuItemController', 'menuItemLink'] as $nodeId) { + /** @var TextFormField $formField */ + $formField = $form->getNodeById($nodeId); + $formField->addDependency( + ValueFormFieldDependency::create('parentMenuItem') + ->field($parentMenuItemFormField) + ->values($menuItemsSupportingLinks) + ); + } + } + + /** + * Returns data on the structure of the acp menu. + * + * @return array + */ + protected function getACPMenuStructureData(): array { + $acpMenuItemList = new ACPMenuItemList(); + $acpMenuItemList->getConditionBuilder()->add('packageID IN (?)', [array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )]); + $acpMenuItemList->sqlOrderBy = 'parentMenuItem ASC, showOrder ASC'; + $acpMenuItemList->readObjects(); + + /** @var ACPMenuItem[] $acpMenuItems */ + $acpMenuItems = []; + /** @var ACPMenuItem[][] $acpMenuStructure */ + $acpMenuStructure = []; + foreach ($acpMenuItemList as $menuItem) { + if (!isset($acpMenuStructure[$menuItem->parentMenuItem])) { + $acpMenuStructure[$menuItem->parentMenuItem] = []; + } + + $acpMenuStructure[$menuItem->parentMenuItem][$menuItem->menuItem] = $menuItem; + $acpMenuItems[$menuItem->menuItem] = $menuItem; + } + + $menuItemLevels = []; + foreach ($acpMenuStructure as $parentMenuItemName => $menuItems) { + $menuItemsLevel = 1; + + while (($parentMenuItem = $acpMenuItems[$parentMenuItemName] ?? null)) { + $menuItemsLevel++; + $parentMenuItemName = $parentMenuItem->parentMenuItem; + } + + foreach ($menuItems as $menuItem) { + $menuItemLevels[$menuItem->menuItem] = $menuItemsLevel; + } + } + + return [ + 'levels' => $menuItemLevels, + 'structure' => $acpMenuStructure + ]; + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function sortDocument(\DOMDocument $document) { + $acpMenuStructureData = $this->getACPMenuStructureData(); + /** @var ACPMenuItem[][] $menuItemStructure */ + $menuItemStructure = $acpMenuStructureData['structure']; + + $this->sortImportDelete($document); + + // build array containing the ACP menu items saved in the database + // in the order as they would be displayed in the ACP + $buildPositions = function(string $parent = '') use ($menuItemStructure, &$buildPositions): array { + $positions = []; + foreach ($menuItemStructure[$parent] as $menuItem) { + // only consider menu items of the current package for positions + if ($menuItem->packageID === $this->installation->getPackageID()) { + $positions[] = $menuItem->menuItem; + } + + if (isset($menuItemStructure[$menuItem->menuItem])) { + $positions = array_merge($positions, $buildPositions($menuItem->menuItem)); + } + } + + return $positions; + }; + + // flip positions array so that the keys are the menu item names + // and the values become the positions so that the array values + // can be used in the sort function + $positions = array_flip($buildPositions()); + + $compareFunction = function(\DOMElement $element1, \DOMElement $element2) use ($positions) { + return $positions[$element1->getAttribute('name')] <=> $positions[$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 { + $formData = $form->getData()['data']; + + $menuItem = parent::writeEntry($document, $form); + + if (isset($formData['icon'])) { + $menuItem->appendChild($document->createElement('icon', $formData['icon'])); + } + + return $menuItem; + } } diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index f020e4aaa6..c139e23aa2 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1913,6 +1913,11 @@ When prompted for the notification URL for the instant payment notifications, pl + + + {literal}{app}.acp.menu.link.{additionalIdentifiers}{/literal} where {literal}{app}{/literal} and {literal}{additionalIdentifiers}{/literal} have to be replaced with the appropriate values. {literal}{additionalIdentifiers}{/literal} may only contain letters, numbers, ands dots.]]> + + {literal}{app}{/literal}\acp\ where {literal}{app}{/literal} is the abbreviation of the relevant app.]]>