Add GUI support for user group option package installation plugin
authorMatthias Schmidt <gravatronics@live.com>
Fri, 12 Oct 2018 17:10:20 +0000 (19:10 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Fri, 12 Oct 2018 17:10:20 +0000 (19:10 +0200)
See #2545

wcfsetup/install/files/lib/system/form/builder/field/OptionFormField.class.php
wcfsetup/install/files/lib/system/package/plugin/AbstractOptionPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/UserGroupOptionPackageInstallationPlugin.class.php
wcfsetup/install/lang/en.xml

index 4c46f1c33309f39e4810167ec68c8ca4cbdf841b..a3905a9e7812d8b447e01c525cbe560238a0eea3 100644 (file)
@@ -72,8 +72,18 @@ class OptionFormField extends ItemListFormField {
                parent::validate();
                
                if (empty($this->getValidationErrors()) && is_array($this->getValue()) && !empty($this->getValue())) {
+                       // ignore `module_attachment`, see https://github.com/WoltLab/WCF/issues/2531
+                       $options = $this->getValue();
+                       if (($index = array_search('module_attachment', $options)) !== false) {
+                               unset($options[$index]);
+                       }
+                       
+                       if (empty($options)) {
+                               return;
+                       }
+                       
                        $conditionBuilder = new PreparedStatementConditionBuilder();
-                       $conditionBuilder->add('optionName IN (?)', [$this->getValue()]);
+                       $conditionBuilder->add('optionName IN (?)', [$options]);
                        if (!empty($this->getPackageIDs())) {
                                $conditionBuilder->add('packageID IN (?)', [$this->getPackageIDs()]);
                        }
@@ -85,7 +95,7 @@ class OptionFormField extends ItemListFormField {
                        $statement->execute($conditionBuilder->getParameters());
                        $availableOptions = $statement->fetchAll(\PDO::FETCH_COLUMN);
                        
-                       $unknownOptions = array_diff($this->getValue(), $availableOptions);
+                       $unknownOptions = array_diff($options, $availableOptions);
                        
                        if (!empty($unknownOptions)) {
                                $this->addValidationError(
index 428011fabf1057a143a7dc4a4aa5e541cde6dace..7c8bdb6cb57b7e81e28917fcd0b00d56728afca2 100644 (file)
@@ -447,11 +447,14 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                                break;
                        
                        case 'options':
+                               $classnamePieces = explode('\\', get_class($this));
+                               $pipPrefix = str_replace('PackageInstallationPlugin', '', array_pop($classnamePieces));
+                               
                                $dataContainer->appendChildren([
                                        TextFormField::create('optionName')
                                                ->objectProperty('name')
                                                ->label('wcf.acp.pip.abstractOption.options.optionName')
-                                               ->description('wcf.acp.pip.abstractOption.categories.options.description')
+                                               ->description('wcf.acp.pip.abstractOption.options.optionName.description')
                                                ->required(),
                                        
                                        SingleSelectionFormField::create('categoryName')
@@ -490,7 +493,7 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                                        SingleSelectionFormField::create('optionType')
                                                ->objectProperty('optiontype')
                                                ->label('wcf.acp.pip.abstractOption.options.optionType')
-                                               ->description('wcf.acp.pip.' . $this->tagName . '.options.optionType.description')
+                                               ->description('wcf.acp.pip.' . lcfirst($pipPrefix) . '.options.optionType.description')
                                                ->required()
                                                ->options(function(): array {
                                                        $classnamePieces = explode('\\', get_class());
@@ -774,6 +777,32 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                                throw new \LogicException('Unreachable');
                }
                
+               if ($saveData) {
+                       $lowercaseData = [
+                               'packageID' => $this->installation->getPackage()->packageID
+                       ];
+                       
+                       $lowercaseFields = [
+                               'categoryName',
+                               'optionName',
+                               'optionType',
+                               'defaultValue',
+                               'enableOptions',
+                               'validationPattern',
+                               'showOrder',
+                               'parentCategoryName',
+                               'showOrder'
+                       ];
+                       
+                       foreach ($lowercaseFields as $name) {
+                               if (isset($data[$name])) {
+                                       $lowercaseData[strtolower($name)] = $data[$name];
+                               }
+                       }
+                       
+                       return $lowercaseData;
+               }
+               
                return $data;
        }
        
@@ -854,7 +883,7 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                        case 'options':
                                $optionData = $this->getElementData($newElement, true);
                                
-                               $this->saveOption($optionData, $optionData['categoryName']);
+                               $this->saveOption($optionData, $optionData['categoryname']);
                                
                                break;
                }
@@ -1023,7 +1052,7 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                                        'options' => '',
                                        'permissions' => '',
                                        
-                                       // object-type specific elements
+                                       // option type-specific elements
                                        'minvalue' => null,
                                        'maxvalue' => null,
                                        'suffix' => '',
@@ -1034,7 +1063,7 @@ abstract class AbstractOptionPackageInstallationPlugin extends AbstractXMLPackag
                                ];
                                foreach ($fields as $field => $defaultValue) {
                                        if (isset($formData[$field]) && $formData[$field] !== $defaultValue) {
-                                               $option->appendChild($document->createElement($field, (string) $formData[$field]));
+                                               $option->appendChild($document->createElement($field, StringUtil::unifyNewlines((string) $formData[$field])));
                                        }
                                }
                                
index 8942b265238641013d379be68d9ae48f1a1483a3..dda019f57f8068baf7f7acec7a983bed83d2834c 100644 (file)
@@ -1,10 +1,20 @@
 <?php
 namespace wcf\system\package\plugin;
+use wcf\data\option\category\OptionCategory;
+use wcf\data\option\Option;
 use wcf\data\user\group\option\UserGroupOption;
 use wcf\data\user\group\option\UserGroupOptionEditor;
 use wcf\data\user\group\UserGroup;
-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\ValueFormFieldDependency;
+use wcf\system\form\builder\field\MultilineTextFormField;
+use wcf\system\form\builder\field\SingleSelectionFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\option\user\group\UserGroupOptionHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -16,7 +26,7 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Package\Plugin
  */
-class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
+class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
        /**
         * list of group ids by type
         * @var integer[][]
@@ -28,6 +38,11 @@ class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInst
         */
        public $tableName = 'user_group_option';
        
+       /**
+        * @inheritDoc
+        */
+       public $tagName = 'option';
+       
        /**
         * list of names of tags which aren't considered as additional data
         * @var string[]
@@ -189,4 +204,143 @@ class UserGroupOptionPackageInstallationPlugin extends AbstractOptionPackageInst
                
                return $this->groupIDs;
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function addFormFields(IFormDocument $form) {
+               parent::addFormFields($form);
+               
+               if ($this->entryType === 'options') {
+                       /** @var IFormContainer $dataContainer */
+                       $dataContainer = $form->getNodeById('data');
+                       
+                       /** @var SingleSelectionFormField $optionType */
+                       $optionType = $form->getNodeById('optionType');
+                       
+                       $dataContainer->appendChildren([
+                               MultilineTextFormField::create('adminDefaultValue')
+                                       ->objectProperty('admindefaultvalue')
+                                       ->label('wcf.acp.pip.userGroupOption.options.adminDefaultValue')
+                                       ->description('wcf.acp.pip.userGroupOption.options.adminDefaultValue.description')
+                                       ->rows(5),
+                               
+                               MultilineTextFormField::create('modDefaultValue')
+                                       ->objectProperty('moddefaultvalue')
+                                       ->label('wcf.acp.pip.userGroupOption.options.modDefaultValue')
+                                       ->description('wcf.acp.pip.userGroupOption.options.modDefaultValue.description')
+                                       ->rows(5),
+                               
+                               BooleanFormField::create('usersOnly')
+                                       ->objectProperty('usersonly')
+                                       ->label('wcf.acp.pip.userGroupOption.options.usersOnly')
+                                       ->description('wcf.acp.pip.userGroupOption.options.usersOnly.description'),
+                               
+                               BooleanFormField::create('excludedInTinyBuild')
+                                       ->label('wcf.acp.pip.userGroupOption.options.excludedInTinyBuild')
+                                       ->description('wcf.acp.pip.userGroupOption.options.excludedInTinyBuild.description'),
+                               
+                               TextFormField::create('wildcard')
+                                       ->label('wcf.acp.pip.userGroupOption.options.wildcard')
+                                       ->description('wcf.acp.pip.userGroupOption.options.wildcard.description')
+                                       ->addDependency(
+                                               ValueFormFieldDependency::create('optionType')
+                                                       ->field($optionType)
+                                                       ->values(['textarea'])
+                                       )
+                       ]);
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function getElementData(\DOMElement $element, $saveData = false) {
+               $data = parent::getElementData($element, $saveData);
+               
+               switch ($this->entryType) {
+                       case 'options':
+                               foreach (['adminDefaultValue', 'modDefaultValue', 'usersOnly', 'excludedInTinyBuild', 'wildcard'] as $optionalPropertyName) {
+                                       $optionalProperty = $element->getElementsByTagName(strtolower($optionalPropertyName))->item(0);
+                                       if ($optionalProperty !== null) {
+                                               if ($saveData && $optionalPropertyName !== 'excludedInTinyBuild') {
+                                                       $data[strtolower($optionalPropertyName)] = $optionalProperty->nodeValue;
+                                               }
+                                               else {
+                                                       $data[$optionalPropertyName] = $optionalProperty->nodeValue;
+                                               }
+                                       }
+                               }
+                               
+                               break;
+               }
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function getSortOptionHandler() {
+               // reuse UserGroupOptionHandler
+               return new class(true) extends UserGroupOptionHandler {
+                       /**
+                        * @inheritDoc
+                        */
+                       protected function checkCategory(OptionCategory $category) {
+                               // we do not care for category checks here
+                               return true;
+                       }
+                       
+                       /**
+                        * @inheritDoc
+                        */
+                       protected function checkOption(Option $option) {
+                               // we do not care for option checks here
+                               return true;
+                       }
+                       
+                       /**
+                        * @inheritDoc
+                        */
+                       public function getCategoryOptions($categoryName = '', $inherit = true) {
+                               // we just need to ensure that the category is not empty
+                               return [new Option(null, [])];
+                       }
+               };
+       }
+       
+       /**
+        * @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':
+                               $fields = [
+                                       'admindefaultvalue' => '',
+                                       'moddefaultvalue' => '',
+                                       'usersonly' => 0,
+                                       'excludedInTinyBuild' => 0,
+                                       'wildcard' => ''
+                               ];
+                               
+                               foreach ($fields as $field => $defaultValue) {
+                                       if (isset($formData[$field]) && $formData[$field] !== $defaultValue) {
+                                               $option->appendChild($document->createElement($field, (string) $formData[$field]));
+                                       }
+                               }
+                               
+                               break;
+               }
+               
+               return $option;
+       }
 }
index 11e2f9e309d71df66f296cd345c68b54f1649a5f..5b23ca02eaa81f5da91095dba1af325ddb92b7c9 100644 (file)
@@ -2137,7 +2137,7 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pip.option.options.supportI18n.description"><![CDATA[If selected, the option supports different values for each available language.]]></item>
                <item name="wcf.acp.pip.option.options.requireI18n"><![CDATA[Require Multilingual Input]]></item>
                <item name="wcf.acp.pip.option.options.requireI18n.description"><![CDATA[If selected, the option’s value must be explicitly set for each available language.]]></item>
-               <item name="wcf.acp.pip.option.options.optionType.description"><![CDATA[The option types determine the interface with which the value of the option is set and which (types of) values the option may have.]]></item>
+               <item name="wcf.acp.pip.option.options.optionType.description"><![CDATA[The option type determines the interface with which the value of the option is set and which (types of) values the option may have.]]></item>
                <item name="wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue"><![CDATA[Allow Empty Value]]></item>
                <item name="wcf.acp.pip.abstractOption.options.optionType.select.allowEmptyValue.description"><![CDATA[If selected, the value of the option may be empty.]]></item>
                <item name="wcf.acp.pip.abstractOption.options.optionType.useroptions.isSortable"><![CDATA[User Options Are Sortable]]></item>
@@ -2188,6 +2188,20 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pip.smiley.smileyPath.error.fileDoesNotExist"><![CDATA[The entered file does not exist.]]></item>
                <item name="wcf.acp.pip.smiley.smileyPath2x"><![CDATA[HD Smiley Path]]></item>
                <item name="wcf.acp.pip.smiley.smileyPath2x.description"><![CDATA[The entered path has to be relative to <code>{'WCF_DIR'|constant}</code>.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.usersOnly"><![CDATA[Members Only]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.usersOnly.description"><![CDATA[The user group option is only available for registered members-only user groups.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.optionType.description"><![CDATA[The option type determines the interface with which the value of the user group option is set and which (types of) values the option may have. Additionally, the option type also determines how the final option value is determined if a user is in multiple user groups with different option values.]]></item>
+               <item name="wcf.acp.pip.abstractOption.options.optionName.description"><![CDATA[The option name is used to update options and to identify options in PHP code.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.adminDefaultValue"><![CDATA[Administrator Default Value]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.adminDefaultValue.description"><![CDATA[The administrator default value is used for administrator user groups. Administrator user groups are user groups that can edit every user group.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.modDefaultValue"><![CDATA[Moderator Default Value]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.modDefaultValue.description"><![CDATA[The moderator default value is used for moderator user groups. Moderator user groups are user groups that can access the moderation.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.usersOnly"><![CDATA[Members Only]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.usersOnly.description"><![CDATA[The user group option value cannot be set for the guests user group.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.excludedInTinyBuild"><![CDATA[Disable for Accelerated Guest View]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.excludedInTinyBuild.description"><![CDATA[If the accelared guest view is enabled, the user group option’s value is “Never” for guests regardless of the actual value set for the guests user group.]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.wildcard"><![CDATA[Wildcard]]></item>
+               <item name="wcf.acp.pip.userGroupOption.options.wildcard.description"><![CDATA[If a wildcard is specified and if one line of the user group option value only contains this wildcare, the entire option value will be replaced by the wildcard.]]></item>
        </category>
        
        <category name="wcf.acp.reactionType">