From 738709f8a711ca99bb239a05e6971b0cc4acb840 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Tue, 31 Jul 2018 16:46:46 +0200 Subject: [PATCH] Add work in progress state for option PIP GUI Matthias Schmidt committed See #2545 --- ...TXmlGuiPackageInstallationPlugin.class.php | 61 +- ...tOptionPackageInstallationPlugin.class.php | 633 +++++++++++++++++- .../OptionPackageInstallationPlugin.class.php | 127 +++- wcfsetup/install/lang/en.xml | 38 ++ 4 files changed, 854 insertions(+), 5 deletions(-) diff --git a/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php index bb4831618f..fb7c8e8e29 100644 --- a/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/devtools/pip/TXmlGuiPackageInstallationPlugin.class.php @@ -470,5 +470,64 @@ XML; * @param IFormDocument $form * @return \DOMElement */ - abstract protected function writeEntry(\DOMDocument $document, IFormDocument $form); + abstract protected function writeEntry(\DOMDocument $document, IFormDocument $form); + + /** + * Returns a sort function for DOM elements using the list of attributes + * and child elements by whose values the DOM elements are sorted. + * + * @param array $sortParameters + * @return \Closure + */ + public static function getSortFunction(array $sortParameters) { + return function(\DOMElement $element1, \DOMElement $element2) use ($sortParameters) { + foreach ($sortParameters as $sortParameter) { + if (is_array($sortParameter)) { + $isAttribute = !empty($sortParameter['isAttribute']); + $name = $sortParameter['name']; + $missingLast = !empty($sortParameter['missingLast']); + } + else { + $isAttribute = false; + $name = $sortParameter; + $missingLast = true; + } + + $value1 = $value2 = null; + if ($isAttribute) { + $value1 = $element1->getAttribute($name); + $value2 = $element2->getAttribute($name); + } + else { + $value1Node = $element1->getElementsByTagName($name)->item(0); + if ($value1Node !== null) { + $value1 = $value1Node->nodeValue; + } + + $value2Node = $element2->getElementsByTagName($name)->item(0); + if ($value2Node !== null) { + $value2 = $value2Node->nodeValue; + } + } + + if ($value1 !== null) { + if ($value2 !== null) { + $compare = $value1 <=> $value2; + + if ($compare !== 0) { + return $compare; + } + } + else { + return $missingLast ? -1 : 1; + } + } + else if ($value2 !== null) { + return $missingLast ? 1 : -1; + } + } + + return -1; + }; + } } diff --git a/wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php index 8614294637..3a2689e994 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php @@ -1,20 +1,62 @@ * @package WoltLabSuite\Core\System\Package\Plugin */ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { + // we do no implement `IGuiPackageInstallationPlugin` but instead just + // provide the default implementation to ensure backwards compatibility + // with third-party packages containing classes that extend this abstract + // class + use TXmlGuiPackageInstallationPlugin; + + /** + * list of option types with i18n support + * @var string[] + */ + public $i18nOptionTypes = []; + + /** + * list of option types with a pre-defined list of options via `selectOptions` + * @var string[] + */ + public $selectOptionOptionTypes = []; + /** * @inheritDoc */ @@ -324,8 +366,597 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag /** * @inheritDoc + * @since 3.1 */ public static function getSyncDependencies() { return []; } + + /** + * @inheritDoc + * @since 3.2 + */ + public function addFormFields(IFormDocument $form) { + /** @var IFormContainer $dataContainer */ + $dataContainer = $form->getNodeById('data'); + + switch ($this->entryType) { + case 'categories': + $dataContainer->appendChildren([ + TextFormField::create('categoryName') + ->label('wcf.acp.pip.abstractOption.categories.categoryName') + ->description('wcf.acp.pip.' . $this->tagName . '.categories.categoryName.description') + ->required(), + + TextFormField::create('parentCategoryName') + ->label('wcf.acp.pip.abstractOption.categories.parentCategoryName'), + // TODO: select option + + IntegerFormField::create('showOrder') + ->label('wcf.acp.pip.abstractOption.categories.showOrder') + ->description('wcf.acp.pip.abstractOption.categories.showOrder.description') + ->nullable(), + + OptionFormField::create() + ->description('wcf.acp.pip.abstractOption.categories.options.description') + ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV) + ->packageIDs(array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )), + + UserGroupOptionFormField::create() + ->description('wcf.acp.pip.abstractOption.categories.permissions.description') + ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV) + ->packageIDs(array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )) + ]); + + break; + + case 'options': + $dataContainer->appendChildren([ + TextFormField::create('optionName') + ->objectProperty('name') + ->label('wcf.acp.pip.abstractOption.options.optionName') + ->description('wcf.acp.pip.abstractOption.categories.options.description') + ->required(), + + SingleSelectionFormField::create('categoryName') + ->objectProperty('categoryname') + ->label('wcf.acp.pip.abstractOption.options.categoryName') + ->required() + ->filterable() + ->options(function(): array { + $classNamePieces = explode('\\', $this->className); + // remove class name and convert editor classname into DBO classname + $className = substr(array_pop($classNamePieces), 0, -6); + + // add additional `category` sub-namespace and re-add class name + $classNamePieces[] = 'category'; + $classNamePieces[] = $className . 'CategoryList'; + + $listClassname = implode('\\', $classNamePieces); + + /** @var DatabaseObjectList $categoryList */ + $categoryList = new $listClassname(); + $categoryList->getConditionBuilder()->add('packageID IN (?)', [array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )]); + $categoryList->readObjects(); + + $categories = []; + + /** @var OptionCategory $category */ + foreach ($categoryList as $category) { + $categories[$category->categoryName] = $category; + } + + ksort($categories); + + $getDepth = function(/** @var OptionCategory $category */$category) use ($categories) { + $depth = 0; + + while (isset($categories[$category->parentCategoryName])) { + $depth++; + + $category = $categories[$category->parentCategoryName]; + } + + return $depth; + }; + + $options = []; + foreach ($categories as $category) { + $options[] = [ + 'depth' => $getDepth($category), + 'label' => $category->categoryName, + 'value' => $category->categoryName + ]; + } + + return $options; + }, true), + + SingleSelectionFormField::create('optionType') + ->objectProperty('optiontype') + ->label('wcf.acp.pip.abstractOption.options.optionType') + ->description('wcf.acp.pip.' . $this->tagName . '.options.optionType.description') + ->required() + ->options(function(): array { + $classnamePieces = explode('\\', get_class()); + $pipPrefix = str_replace('OptionPackageInstallationPlugin', '', $classnamePieces); + + $options = []; + + // consider all applications for potential object types + foreach (ApplicationHandler::getInstance()->getApplications() as $application) { + $optionDir = $application->getPackage()->getAbsolutePackageDir() . 'lib/system/option'; + $directoryUtil = DirectoryUtil::getInstance($optionDir); + + foreach ($directoryUtil->getFileObjects() as $fileObject) { + if ($fileObject->isFile()) { + $optionTypePrefix = ''; + + // determine additional sub-namespace + // example: `user` in `wcf\system\option\user` + $additionalSubNamespace = ltrim(str_replace([$optionDir, '/'], ['', '\\'], $fileObject->getPath()), '\\'); + if ($additionalSubNamespace !== '') { + $optionTypePrefix = implode('', array_map('ucfirst', explode('\\', $additionalSubNamespace))); + + // ignore additional sub-namespaced option types if they do not + // belong to the pip + if ($optionTypePrefix !== $pipPrefix) { + continue; + } + } + + $namespace = $application->getAbbreviation() . '\\system\\option' . str_replace([$optionDir, '/'], ['', '\\'], $fileObject->getPath()); + $unqualifiedClassname = str_replace('.class.php', '', $fileObject->getFilename()); + $classname = $namespace . '\\' . $unqualifiedClassname; + + if (!is_subclass_of($classname, IOptionType::class) || !(new \ReflectionClass($classname))->isInstantiable()) { + continue; + } + + $optionType = str_replace( $optionTypePrefix . 'OptionType.class.php', '', $fileObject->getFilename()); + + // only make first letter lowercase if the first two letters are not uppercase + // relevant cases: `URL` and the `WBB` prefix + if (!preg_match('~^[A-Z]{2}~', $optionType)) { + $optionType = lcfirst($optionType); + } + + if (is_subclass_of($classname, II18nOptionType::class)) { + $this->i18nOptionTypes[] = $optionType; + } + + if (is_subclass_of($classname, ISelectOptionOptionType::class)) { + $this->selectOptionOptionTypes[] = $optionType; + } + + $options[] = $optionType; + } + } + } + + natcasesort($options); + + return array_combine($options, $options); + }), + + MultilineTextFormField::create('defaultValue') + ->objectProperty('defaultvalue') + ->label('wcf.acp.pip.abstractOption.options.defaultValue') + ->description('wcf.acp.pip.abstractOption.options.defaultValue.description') + ->rows(5), + + TextFormField::create('validationPattern') + ->objectProperty('validationpattern') + ->label('wcf.acp.pip.abstractOption.options.validationPattern') + ->description('wcf.acp.pip.abstractOption.options.validationPattern.description') + ->addValidator(new FormFieldValidator('regex', function(TextFormField $formField) { + if ($formField->getSaveValue() !== '') { + if (!Regex::compile($formField->getSaveValue())->isValid()) { + $formField->addValidationError( + new FormFieldValidationError( + 'invalid', + 'wcf.acp.pip.abstractOption.options.validationPattern.error.invalid' + ) + ); + } + } + })), + + MultilineTextFormField::create('enableOptions') + ->objectProperty('enableoptions') + ->label('wcf.acp.pip.abstractOption.options.enableOptions') + ->description('wcf.acp.pip.abstractOption.options.enableOptions.description') + ->rows(5), + + IntegerFormField::create('showOrder') + ->objectProperty('showorder') + ->label('wcf.acp.pip.abstractOption.options.showOrder') + ->description('wcf.acp.pip.abstractOption.options.showOrder.description'), + + OptionFormField::create() + ->description('wcf.acp.pip.abstractOption.options.options.description') + ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV) + ->packageIDs(array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )), + + UserGroupOptionFormField::create() + ->description('wcf.acp.pip.abstractOption.options.permissions.description') + ->saveValueType(OptionFormField::SAVE_VALUE_TYPE_CSV) + ->packageIDs(array_merge( + [$this->installation->getPackage()->packageID], + array_keys($this->installation->getPackage()->getAllRequiredPackages()) + )) + ]); + + /** @var SingleSelectionFormField $optionType */ + $optionType = $form->getNodeById('optionType'); + + // add option-specific fields + $dataContainer->appendChildren([ + IntegerFormField::create('minValue') + ->objectProperty('minvalue') + ->label('wcf.acp.pip.abstractOption.options.optionType.integer.minValue') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['integer']) + ), + + IntegerFormField::create('maxValue') + ->objectProperty('maxvalue') + ->label('wcf.acp.pip.abstractOption.options.optionType.integer.maxValue') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['integer']) + ), + + TextFormField::create('suffix') + ->label('wcf.acp.pip.abstractOption.options.optionType.integer.suffix') + ->description('wcf.acp.pip.abstractOption.options.optionType.integer.suffix.description') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['integer']) + ), + + IntegerFormField::create('minLength') + ->objectProperty('minlength') + ->label('wcf.acp.pip.abstractOption.options.optionType.text.minLength') + ->minimum(0) + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['password', 'text', 'textarea', 'URL']) + ), + + IntegerFormField::create('maxLength') + ->objectProperty('maxlength') + ->label('wcf.acp.pip.abstractOption.options.optionType.text.maxLength') + ->minimum(1) + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['password', 'text', 'textarea', 'URL']) + ), + + BooleanFormField::create('isSortable') + ->objectProperty('issortable') + ->label('wcf.acp.pip.abstractOption.options.optionType.useroptions.isSortable') + ->description('wcf.acp.pip.abstractOption.options.optionType.useroptions.isSortable.description') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['useroptions']) + ), + + BooleanFormField::create('allowEmptyValue') + ->objectProperty('allowemptyvalue') + ->label('wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue') + ->description('wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue.description') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['captchaSelect']) + ), + + BooleanFormField::create('allowEmptyValue_select') + ->objectProperty('allowEmptyValue') + ->label('wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue') + ->description('wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue.description') + ->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values(['select']) + ), + ]); + + break; + + default: + throw new \LogicException('Unreachable'); + } + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function getElementData(\DOMElement $element, $saveData = false) { + $data = [ + 'packageID' => $this->installation->getPackage()->packageID + ]; + + switch ($this->entryType) { + case 'categories': + $data['categoryName'] = $element->getAttribute('name'); + + $parent = $element->getElementsByTagName('parent')->item(0); + if ($parent !== null) { + $data['parentCategoryName'] = $parent->nodeValue; + } + + foreach (['options', 'permissions'] as $optionalPropertyName) { + $optionalProperty = $element->getElementsByTagName($optionalPropertyName)->item(0); + if ($optionalProperty !== null) { + $data[$optionalPropertyName] = StringUtil::normalizeCsv($optionalProperty->nodeValue); + } + } + + $showOrder = $element->getElementsByTagName('showorder')->item(0); + if ($showOrder !== null) { + $data['showOrder'] = $showOrder->nodeValue; + } + + break; + + case 'options': + $data['optionName'] = $element->getAttribute('name'); + $data['categoryName'] = $element->getElementsByTagName('categoryname')->item(0)->nodeValue; + $data['optionType'] = $element->getElementsByTagName('optiontype')->item(0)->nodeValue; + + foreach (['enableOptions', 'validationPattern', 'showOrder'] as $optionalPropertyName) { + $optionalProperty = $element->getElementsByTagName(strtolower($optionalPropertyName))->item(0); + if ($optionalProperty !== null) { + $data[$optionalPropertyName] = $optionalProperty->nodeValue; + } + } + + foreach (['options', 'permissions'] as $optionalPropertyName) { + $optionalProperty = $element->getElementsByTagName($optionalPropertyName)->item(0); + if ($optionalProperty !== null) { + $data[$optionalPropertyName] = StringUtil::normalizeCsv($optionalProperty->nodeValue); + } + } + + break; + + default: + throw new \LogicException('Unreachable'); + } + + return $data; + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function getElementIdentifier(\DOMElement $element) { + return $element->getAttribute('name'); + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function getEntryTypes() { + return ['options', 'categories']; + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function saveObject(\DOMElement $newElement, \DOMElement $oldElement = null) { + switch ($this->entryType) { + case 'categories': + $this->saveCategory($this->getElementData($newElement, true)); + + break; + + case 'options': + $optionData = $this->getElementData($newElement, true); + + $this->saveOption($optionData, $optionData['categoryName']); + + break; + } + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) { + switch ($this->entryType) { + case 'categories': + $entryList->setKeys([ + 'categoryName' => 'wcf.acp.pip.abstractOption.categories.categoryName', + 'parentCategoryName' => 'wcf.acp.pip.abstractOption.categories.parentCategoryName' + ]); + break; + + case 'options': + $entryList->setKeys([ + 'optionName' => 'wcf.acp.pip.abstractOption.options.optionName', + 'categoryName' => 'wcf.acp.pip.abstractOption.options.categoryName', + 'optionType' => 'wcf.acp.pip.abstractOption.options.optionType' + ]); + break; + + default: + throw new \LogicException('Unreachable'); + } + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function sortDocument(\DOMDocument $document) { + $this->sortImportDelete($document); + + // `` before `` + $compareFunction = function(\DOMElement $element1, \DOMElement $element2) { + if ($element1->nodeName === 'categories') { + return -1; + } + else if ($element2->nodeName === 'categories') { + return 1; + } + + return 0; + }; + + $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction); + + // TODO: use options + + $xpath = new \DOMXPath($document); + $xpath->registerNamespace('ns', $document->documentElement->getAttribute('xmlns')); + + $this->sortChildNodes( + $xpath->query('/ns:data/ns:import/ns:categories'), + static::getSortFunction([ + 'showorder', + [ + 'missingLast' => false, + 'name' => 'parent' + ], + [ + 'isAttribute' => 1, + 'name' => 'name' + ] + ]) + ); + + return; + + $importElements = $xpath->query('/ns:data/ns:import/ns:categories'); + + $compareFunction = static::getSortFunction([ + 'showorder', + 'parent', + [ + 'isAttribute' => 1, + 'name' => 'name' + ] + ]); + + $importElements = $xpath->query('/ns:data/ns:import/ns:options'); + + $compareFunction = function(\DOMElement $element1, \DOMElement $element2) { + $category1 = $element1->getElementsByTagName('categoryname')->item(0); + $category2 = $element2->getElementsByTagName('categoryname')->item(0); + + if ($category1 !== null) { + if ($category2 !== null) { + return strcmp($category1->nodeValue, $category2->nodeValue); + } + + return -1; + } + else if ($category2 !== null) { + return 1; + } + + return strcmp($element1->getAttribute('name'), $element2->getAttribute('name')); + }; + + $this->sortChildNodes($importElements, $compareFunction); + $this->sortChildNodes($document->getElementsByTagName('delete'), function(\DOMElement $element1, \DOMElement $element2) { + if ($element1->nodeName === 'optioncategory') { + if ($element2->nodeName === 'optioncategory') { + return strcmp($element1->nodeName, $element2->nodeName); + } + else { + return -1; + } + } + else if ($element2->nodeName === 'optioncategory') { + return 1; + } + + // two options + return strcmp($element1->nodeName, $element2->nodeName); + }); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function writeEntry(\DOMDocument $document, IFormDocument $form) { + $formData = $form->getData()['data']; + + $xpath = new \DOMXPath($document); + $xpath->registerNamespace('ns', $document->documentElement->getAttribute('xmlns')); + + switch ($this->entryType) { + case 'categories': + $category = $document->createElement('category'); + + $document->getElementsByTagName('import')->item(0)->appendChild($category); + + foreach (['parent', 'showorder', 'options', 'permissions'] as $field) { + if (isset($formData[$field]) && $formData[$field] !== '') { + $category->appendChild($document->createElement($field, (string) $formData[$field])); + } + } + + $xpath->query('/ns:data/ns:import/ns:categories')->item(0)->appendChild($category); + + return $category; + + case 'options': + $option = $document->createElement($this->tagName); + $option->setAttribute('name', $formData['name']); + + foreach (['categoryname', 'optiontype'] as $field) { + $option->appendChild($document->createElement($field, (string) $formData[$field])); + } + + $fields = [ + 'validationpattern' => '', + 'enableoptions' => '', + 'showorder' => 0, + 'options' => '', + 'permissions' => '' + ]; + foreach ($fields as $field => $defaultValue) { + if (isset($formData[$field]) && $formData[$field] !== $defaultValue) { + $option->appendChild($document->createElement($field, (string) $formData[$field])); + } + } + + $xpath->query('/ns:data/ns:import/ns:options')->item(0)->appendChild($option); + + return $option; + + default: + throw new \LogicException('Unreachable'); + } + } } diff --git a/wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php index 871269e905..4f8f562dfa 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/OptionPackageInstallationPlugin.class.php @@ -3,20 +3,34 @@ namespace wcf\system\package\plugin; use wcf\data\option\Option; use wcf\data\option\OptionEditor; use wcf\data\package\Package; -use wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin; +use wcf\system\devtools\pip\IGuiPackageInstallationPlugin; use wcf\system\exception\SystemException; +use wcf\system\form\builder\container\IFormContainer; +use wcf\system\form\builder\field\BooleanFormField; +use wcf\system\form\builder\field\dependency\NonEmptyFormFieldDependency; +use wcf\system\form\builder\field\dependency\ValueFormFieldDependency; +use wcf\system\form\builder\field\MultilineTextFormField; +use wcf\system\form\builder\field\SingleSelectionFormField; +use wcf\system\form\builder\IFormDocument; use wcf\system\WCF; use wcf\util\StringUtil; /** * Installs, updates and deletes options. * - * @author Alexander Ebert + * TODO: Finalize GUI implementation + * + * @author Alexander Ebert, Matthias Schmidt * @copyright 2001-2018 WoltLab GmbH * @license GNU Lesser General Public License * @package WoltLabSuite\Core\System\Package\Plugin */ -class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { +class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IGuiPackageInstallationPlugin { + /** + * @inheritDoc + */ + public $className = OptionEditor::class; + /** * @inheritDoc */ @@ -112,4 +126,111 @@ class OptionPackageInstallationPlugin extends AbstractOptionPackageInstallationP $optionEditor->update($data); } } + + /** + * @inheritDoc + * @since 3.2 + */ + public function addFormFields(IFormDocument $form) { + parent::addFormFields($form); + + /** @var IFormContainer $dataContainer */ + $dataContainer = $form->getNodeById('data'); + + switch ($this->entryType) { + case 'options': + $selectOptions = MultilineTextFormField::create('selectOptions') + ->objectProperty('selectoptions') + ->label('wcf.acp.pip.abstractOption.options.selectOptions') + ->description('wcf.acp.pip.option.options.selectOptions.description') + ->rows(5); + + $dataContainer->insertBefore($selectOptions, 'enableOptions'); + + $dataContainer->appendChildren([ + BooleanFormField::create('hidden') + ->label('wcf.acp.pip.option.options.hidden') + ->description('wcf.acp.pip.option.options.hidden.description'), + + BooleanFormField::create('supportI18n') + ->objectProperty('supporti18n') + ->label('wcf.acp.pip.option.options.supportI18n') + ->description('wcf.acp.pip.option.options.supportI18n.description'), + + BooleanFormField::create('requireI18n') + ->objectProperty('requirei18n') + ->label('wcf.acp.pip.option.options.requireI18n') + ->description('wcf.acp.pip.option.options.requireI18n.description'), + ]); + + /** @var SingleSelectionFormField $supportI18n */ + $optionType = $form->getNodeById('optionType'); + + /** @var BooleanFormField $supportI18n */ + $supportI18n = $form->getNodeById('supportI18n'); + + $selectOptions->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values($this->selectOptionOptionTypes) + ); + + $supportI18n->addDependency( + ValueFormFieldDependency::create('optionType') + ->field($optionType) + ->values($this->i18nOptionTypes) + ); + + $form->getNodeById('requireI18n')->addDependency( + NonEmptyFormFieldDependency::create('supportI18n') + ->field($supportI18n) + ); + break; + } + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function getElementData(\DOMElement $element, $saveData = false) { + $data = parent::getElementData($element, $saveData); + + switch ($this->entryType) { + case 'options': + foreach (['selectOptions', 'hidden', 'supportI18n', 'requireI18n'] as $optionalPropertyName) { + $optionalProperty = $element->getElementsByTagName(strtolower($optionalPropertyName))->item(0); + if ($optionalProperty !== null) { + $data[$optionalPropertyName] = $optionalProperty->nodeValue; + } + } + + break; + } + + return $data; + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function writeEntry(\DOMDocument $document, IFormDocument $form) { + $option = parent::writeEntry($document, $form); + + $formData = $form->getData()['data']; + + switch ($this->entryType) { + case 'options': + foreach (['selectoptions' => '', 'hidden' => 0, 'supporti18n' => 0, 'requirei18n' => 0] as $field => $defaultValue) { + if (isset($formData[$field]) && $formData[$field] !== $defaultValue) { + $option->appendChild($document->createElement($field, (string) $formData[$field])); + } + } + + break; + } + + return $option; + } } diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index c0ccdf1e7b..6eb416608f 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -2080,6 +2080,44 @@ If you have already bought the licenses for the listed apps, th + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wcf.acp.option.suffix.{literal}{$suffix}{/literal}) is shown behind the input field.]]> + + -- 2.20.1