Add work in progress state for box PIP GUI
authorMatthias Schmidt <gravatronics@live.com>
Tue, 31 Jul 2018 14:40:41 +0000 (16:40 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 31 Jul 2018 14:40:41 +0000 (16:40 +0200)
See #2545

wcfsetup/install/files/acp/templates/__boxPipGui.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/system/box/AbstractDatabaseObjectListBoxController.class.php
wcfsetup/install/files/lib/system/package/plugin/BoxPackageInstallationPlugin.class.php
wcfsetup/install/lang/en.xml

diff --git a/wcfsetup/install/files/acp/templates/__boxPipGui.tpl b/wcfsetup/install/files/acp/templates/__boxPipGui.tpl
new file mode 100644 (file)
index 0000000..8b7ce83
--- /dev/null
@@ -0,0 +1,25 @@
+<script data-relocate="true">
+       require(['Language'], function(Language) {
+               Language.addObject({
+                       'wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere': '{lang}wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere{/lang}',
+                       'wcf.acp.pip.box.visibilityExceptions.visibleEverywhere': '{lang}wcf.acp.pip.box.visibilityExceptions.visibleEverywhere{/lang}'
+               });
+               
+               var visibleEverywhere = elById('visibleEverywhere');
+               var visibilityExceptionsLabel = elBySel('#visibilityExceptionsContainer > dt > label');
+               
+               function updateVisibilityExceptions() {
+                       if (visibleEverywhere.checked) {
+                               visibilityExceptionsLabel.innerHTML = Language.get('wcf.acp.pip.box.visibilityExceptions.visibleEverywhere');
+                       }
+                       else {
+                               visibilityExceptionsLabel.innerHTML = Language.get('wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere');
+                       }
+               }
+               
+               visibleEverywhere.addEventListener('change', updateVisibilityExceptions);
+               elById('visibleEverywhere_no').addEventListener('change', updateVisibilityExceptions);
+               
+               updateVisibilityExceptions();
+       });
+</script>
index 839402c2a7ebf624167abcef8258c2aabce99db9..cf685694343b958081b7de9aeb198bd70e2d3b3b 100644 (file)
@@ -8,6 +8,12 @@ use wcf\system\condition\ConditionHandler;
 use wcf\system\condition\IObjectListCondition;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\UserInputException;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
+use wcf\system\form\builder\field\IntegerFormField;
+use wcf\system\form\builder\field\SingleSelectionFormField;
+use wcf\system\form\builder\field\SortOrderFormField;
+use wcf\system\form\builder\IFormDocument;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -131,6 +137,60 @@ abstract class AbstractDatabaseObjectListBoxController extends AbstractBoxContro
                parent::__construct();
        }
        
+       /**
+        * Adds fields to the given PIP GUI form to create a box for this controller.
+        * 
+        * @param       IFormDocument   $form
+        * @param       string          $objectType
+        * @since       3.2
+        */
+       public function addPipGuiFormFields(IFormDocument $form, $objectType) {
+               /** @var FormContainer $dataContainer */
+               $dataContainer = $form->getNodeById('dataTabData');
+               
+               /** @var SingleSelectionFormField $objectType */
+               $objectTypeField = $dataContainer->getNodeById('objectType');
+               
+               $prefix = str_replace('.', '_', $objectType) . '_';
+               
+               if (!empty($this->validSortFields)) {
+                       $dataContainer->appendChildren([
+                               SingleSelectionFormField::create($prefix . 'sortField')
+                                       ->objectProperty('sortField')
+                                       ->label('wcf.acp.box.controller.sortField')
+                                       ->description('wcf.acp.box.controller.sortField.description')
+                                       ->options(array_combine($this->validSortFields, $this->validSortFields))
+                                       ->addDependency(
+                                               ValueFormFieldDependency::create('boxType')
+                                                       ->field($objectTypeField)
+                                                       ->values([$objectType])
+                                       ),
+                               
+                               SortOrderFormField::create($prefix . 'sortOrder')
+                                       ->addDependency(
+                                               ValueFormFieldDependency::create('boxType')
+                                                       ->field($objectTypeField)
+                                                       ->values([$objectType])
+                                       )
+                       ]);
+               }
+               
+               if ($this->defaultLimit !== null) {
+                       $dataContainer->appendChild(
+                               IntegerFormField::create($prefix . 'limit')
+                                       ->label('wcf.acp.box.controller.limit')
+                                       ->description('wcf.acp.box.controller.limit.description')
+                                       ->minimum($this->minimumLimit)
+                                       ->maximum($this->maximumLimit)
+                                       ->addDependency(
+                                               ValueFormFieldDependency::create('boxType')
+                                                       ->field($objectTypeField)
+                                                       ->values([$objectType])
+                                       )
+                       );
+               }
+       }
+       
        /**
         * @inheritDoc
         */
index d2784b1c5c684d9ca5bbb8750f268af602cb19db..9e9eee481b5d36d9330efa58f899ce7980b28fb5 100644 (file)
@@ -2,22 +2,49 @@
 namespace wcf\system\package\plugin;
 use wcf\data\box\Box;
 use wcf\data\box\BoxEditor;
+use wcf\data\box\BoxList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\page\PageNode;
+use wcf\data\page\PageNodeTree;
+use wcf\system\box\AbstractDatabaseObjectListBoxController;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
-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\exception\SystemException;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\container\TabFormContainer;
+use wcf\system\form\builder\container\TabMenuFormContainer;
+use wcf\system\form\builder\field\BooleanFormField;
+use wcf\system\form\builder\field\dependency\ValueFormFieldDependency;
+use wcf\system\form\builder\field\ItemListFormField;
+use wcf\system\form\builder\field\MultilineTextFormField;
+use wcf\system\form\builder\field\MultipleSelectionFormField;
+use wcf\system\form\builder\field\RadioButtonFormField;
+use wcf\system\form\builder\field\SingleSelectionFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\form\builder\field\TitleFormField;
+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\language\LanguageFactory;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Installs, updates and deletes boxes.
  * 
- * @author     Alexander Ebert
+ * TODO: Finalize GUI implementation
+ * 
+ * @author     Alexander Ebert, Matthias Schmidt
  * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  * @since      3.0
  */
-class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin {
+class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin {
+       use TXmlGuiPackageInstallationPlugin;
+       
        /**
         * @inheritDoc
         */
@@ -408,8 +435,312 @@ class BoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
        
        /**
         * @inheritDoc
+        * @since       3.1
         */
        public static function getSyncDependencies() {
                return ['language', 'objectType'];
        }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function getAdditionalTemplateCode() {
+               return WCF::getTPL()->fetch('__boxPipGui');
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function addFormFields(IFormDocument $form) {
+               $tabContainter = TabMenuFormContainer::create('tabMenu');
+               $form->appendChild($tabContainter);
+               
+               $dataTab = TabFormContainer::create('dataTab')
+                       ->label('wcf.global.form.data');
+               $tabContainter->appendChild($dataTab);
+               $dataContainer = FormContainer::create('dataTabData');
+               $dataTab->appendChild($dataContainer);
+               
+               $contentTab = TabFormContainer::create('contentTab')
+                       ->label('wcf.acp.pip.box.content');
+               $tabContainter->appendChild($contentTab);
+               $contentContainer = FormContainer::create('contentTabContent');
+               $contentTab->appendChild($contentContainer);
+               
+               $dataContainer->appendChildren([
+                       TextFormField::create('identifier')
+                               ->label('wcf.acp.pip.box.identifier')
+                               ->description('wcf.acp.pip.box.identifier.description')
+                               ->required()
+                               ->addValidator(ObjectTypePackageInstallationPlugin::getObjectTypeAlikeValueValidator(
+                                       'wcf.acp.pip.box.identifier',
+                                       4
+                               ))
+                               ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) {
+                                       if (
+                                               $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE ||
+                                               $this->editedEntry->getAttribute('identifier') !== $formField->getValue()
+                                       ) {
+                                               $pageList = new BoxList();
+                                               $pageList->getConditionBuilder()->add('identifier = ?', [$formField->getValue()]);
+                                               
+                                               if ($pageList->countObjects() > 0) {
+                                                       $formField->addValidationError(
+                                                               new FormFieldValidationError(
+                                                                       'notUnique',
+                                                                       'wcf.acp.pip.box.identifier.error.notUnique'
+                                                               )
+                                                       );
+                                               }
+                                       }
+                               })),
+                       
+                       TextFormField::create('name')
+                               ->label('wcf.acp.pip.box.name')
+                               ->description('wcf.acp.pip.box.name.description')
+                               ->required()
+                               ->i18n()
+                               ->i18nRequired()
+                               ->languageItemPattern('__NONE__'),
+                       
+                       RadioButtonFormField::create('boxType')
+                               ->label('wcf.acp.pip.box.boxType')
+                               ->description('wcf.acp.pip.box.boxType.description')
+                               ->options(array_combine(Box::$availableBoxTypes, Box::$availableBoxTypes)),
+                       
+                       SingleSelectionFormField::create('objectType')
+                               ->label('wcf.acp.pip.box.objectType')
+                               ->description('wcf.acp.pip.box.objectType.description')
+                               ->required()
+                               ->options(function() {
+                                       $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.boxController');
+                                       
+                                       $options = [];
+                                       foreach ($objectTypes as $objectType) {
+                                               $options[$objectType->objectType] = $objectType->objectType;
+                                       }
+                                       
+                                       asort($options);
+                                       
+                                       return $options;
+                               }),
+                       
+                       SingleSelectionFormField::create('position')
+                               ->label('wcf.acp.pip.box.position')
+                               ->options(array_combine(Box::$availablePositions, Box::$availablePositions)),
+                       
+                       BooleanFormField::create('showHeader')
+                               ->label('wcf.acp.pip.box.showHeader'),
+                       
+                       BooleanFormField::create('visibleEverywhere')
+                               ->label('wcf.acp.pip.box.visibleEverywhere'),
+                       
+                       MultipleSelectionFormField::create('visibilityExceptions')
+                               ->label('wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere')
+                               ->filterable()
+                               ->options(function() {
+                                       $pageNodeList = (new PageNodeTree())->getNodeList();
+                                       
+                                       $nestedOptions = [];
+                                       /** @var PageNode $pageNode */
+                                       foreach ($pageNodeList as $pageNode) {
+                                               $nestedOptions[] = [
+                                                       'depth' => $pageNode->getDepth() - 1,
+                                                       'label' => $pageNode->name,
+                                                       'value' => $pageNode->identifier
+                                               ];
+                                       }
+                                       
+                                       return $nestedOptions;
+                               }, true),
+                       
+                       ItemListFormField::create('cssClassName')
+                               ->label('wcf.acp.pip.box.cssClassName')
+                               ->description('wcf.acp.pip.box.cssClassName.description')
+                               ->saveValueType(ItemListFormField::SAVE_VALUE_TYPE_SSV)
+               ]);
+               
+               $contentContainer->appendChildren([
+                       TitleFormField::create('title')
+                               ->label('wcf.acp.pip.box.content.title')
+                               ->required()
+                               ->i18n()
+                               ->i18nRequired()
+                               ->languageItemPattern('__NONE__'),
+                       
+                       MultilineTextFormField::create('contentContent')
+                               ->objectProperty('content')
+                               ->label('wcf.acp.pip.box.content.content')
+                               ->required()
+                               ->i18n()
+                               ->i18nRequired()
+                               ->languageItemPattern('__NONE__')
+               ]);
+               
+               // add box controller-specific form fields 
+               foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.boxController') as $objectType) {
+                       if (is_subclass_of($objectType->className, AbstractDatabaseObjectListBoxController::class)) {
+                               /** @var AbstractDatabaseObjectListBoxController $boxController */
+                               $boxController = new $objectType->className;
+                               
+                               $boxController->addPipGuiFormFields($form, $objectType->objectType);
+                       }
+               }
+               
+               // add dependencies
+               
+               /** @var SingleSelectionFormField $boxType */
+               $boxType = $dataContainer->getNodeById('boxType');
+               
+               $dataContainer->getNodeById('objectType')->addDependency(
+                       ValueFormFieldDependency::create('boxType')
+                               ->field($boxType)
+                               ->values(['system'])
+               );
+               
+               $contentContainer->getNodeById('contentContent')->addDependency(
+                       ValueFormFieldDependency::create('pageType')
+                               ->field($boxType)
+                               ->values(['system'])
+                               ->negate()
+               );
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function getElementData(\DOMElement $element, $saveData = false) {
+               $data = [
+                       'boxType' => $element->getElementsByTagName('boxType')->item(0)->nodeValue,
+                       'content' => [],
+                       'identifier' => $element->getAttribute('identifier'),
+                       'name' => [],
+                       'originIsSystem' => 1,
+                       'packageID' => $this->installation->getPackageID(),
+                       'position' => $element->getElementsByTagName('position')->item(0)->nodeValue,
+                       'title' => []
+               ];
+               
+               /** @var \DOMElement $name */
+               foreach ($element->getElementsByTagName('name') as $name) {
+                       $data['name'][LanguageFactory::getInstance()->getLanguageByCode($name->getAttribute('language'))->languageID] = $name->nodeValue;
+               }
+               
+               foreach (['objectType'] as $optionalElementName) {
+                       $optionalElement = $element->getElementsByTagName($optionalElementName)->item(0);
+                       if ($optionalElement !== null) {
+                               $data[$optionalElementName] = $optionalElement->nodeValue;
+                       }
+               }
+               
+               // TODO: rest
+               
+               return $data;
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       public function getElementIdentifier(\DOMElement $element) {
+               return $element->getAttribute('identifier');
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) {
+               $entryList->setKeys([
+                       'identifier' => 'wcf.acp.pip.box.identifier',
+                       'boxType' => 'wcf.acp.pip.box.boxType'
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function sortDocument(\DOMDocument $document) {
+               $this->sortImportDelete($document);
+               
+               $compareFunction = function(\DOMElement $element1, \DOMElement $element2) {
+                       return strcmp(
+                               $element1->getAttribute('identifier'),
+                               $element2->getAttribute('identifier')
+                       );
+               };
+               
+               $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction);
+               $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction);
+       }
+       
+       /**
+        * @inheritDoc
+        * @since       3.2
+        */
+       protected function writeEntry(\DOMDocument $document, IFormDocument $form) {
+               $formData = $form->getData();
+               
+               if ($formData['data']['identifier'] === 'com.woltlab.wcf.MainMenu') {
+                       $formData['data']['boxPosition'] = 'mainMenu';
+               }
+               
+               $box = $document->createElement($this->tagName);
+               $box->setAttribute('identifier', $formData['data']['identifier']);
+               
+               foreach ($formData['name_i18n'] as $languageID => $name) {
+                       $nameElement = $document->createElement('name', $this->getAutoCdataValue($name));
+                       $nameElement->setAttribute('language', LanguageFactory::getInstance()->getLanguage($languageID)->languageCode);
+                       
+                       $box->appendChild($nameElement);
+               }
+               
+               $box->appendChild($document->createElement('boxType', $formData['data']['boxType']));
+               
+               foreach ($formData['data'] as $property => $value) {
+                       if (!in_array($property, self::$reservedTags) && $value !== null) {
+                               $box->appendChild($document->createElement($property, (string)$value));
+                       }
+               }
+               
+               foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                       $content = null;
+                       
+                       foreach (['title', 'content'] as $property) {
+                               if (!empty($formData[$property . '_i18n'][$language->languageID])) {
+                                       if ($content === null) {
+                                               $content = $document->createElement('content');
+                                               $content->setAttribute('language', $language->languageCode);
+                                               
+                                               $box->appendChild($content);
+                                       }
+                                       
+                                       if ($property === 'content') {
+                                               $contentContent = $document->createElement('content');
+                                               $contentContent->appendChild(
+                                                       $document->createCDATASection(
+                                                               StringUtil::escapeCDATA(StringUtil::unifyNewlines(
+                                                                       $formData[$property . '_i18n'][$language->languageID]
+                                                               ))
+                                                       )
+                                               );
+                                               
+                                               $content->appendChild($contentContent);
+                                       }
+                                       else {
+                                               $content->appendChild(
+                                                       $document->createElement(
+                                                               $property,
+                                                               $formData[$property . '_i18n'][$language->languageID]
+                                                       )
+                                               );
+                                       }
+                               }
+                       }
+               }
+       }
 }
index a1065d9d93bbd0bc0a9ee7c4f8b1ba8077c70387..c0ccdf1e7bcdeb44b9eec9ece8424eabc529f60a 100644 (file)
@@ -2059,6 +2059,27 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pip.clipboardAction.showOrder.description"><![CDATA[The entered value determines in which order the clipboard actions for the same clipboard action class are shown.]]></item>
                <item name="wcf.acp.pip.clipboardAction.pages"><![CDATA[Pages]]></item>
                <item name="wcf.acp.pip.clipboardAction.pages.description"><![CDATA[The clipboard action is available on the entered pages.]]></item>
+               <item name="wcf.acp.pip.box.position"><![CDATA[Position]]></item>
+               <item name="wcf.acp.pip.box.boxType"><![CDATA[Box Type]]></item>
+               <item name="wcf.acp.pip.box.boxType.description"><![CDATA[The content of “text” boxes can be edited by administrators using the built-in WYSIWYG editor. “html” boxes addditionally allow HTML code and “tpl” boxes also template scripting. The content of “system” boxes cannot be edited by administrators as their content is provided by the boxes' controller.]]></item>
+               <item name="wcf.acp.pip.box.objectType"><![CDATA[Box Controller]]></item>
+               <item name="wcf.acp.pip.box.objectType.description"><![CDATA[The box controller provides the content of the box.]]></item>
+               <item name="wcf.acp.pip.box.identifier"><![CDATA[Box Identifier]]></item>
+               <item name="wcf.acp.pip.box.identifier.description"><![CDATA[The box identifier is used to update boxes. The identifier consists of least four segments separated by dots. Each segment must not be empty and may only contain letters, numbers, underscores, and dashes. In general, the first part of the menu identifier is the package identifier. Example: <code>com.foo.bar.Box</code>]]></item>
+               <item name="wcf.acp.pip.box.identifier.error.invalidSegments"><![CDATA[The following segments are invalid: {implode from=$invalidSegments key=segmentNumber item=segment}{if $segment !== ''}<code>{$segment}</code>{else}(empty){/if} (segment {#$segmentNumber + 1}){/implode}.]]></item>
+               <item name="wcf.acp.pip.box.identifier.error.notUnique"><![CDATA[The entered identifier is already used by another box.]]></item>
+               <item name="wcf.acp.pip.box.identifier.error.tooFewSegments"><![CDATA[The identifier only contains {#$segmentCount} segment{if $segmentCount > 1}s{/if}.]]></item>
+               <item name="wcf.acp.pip.box.name"><![CDATA[Name]]></item>
+               <item name="wcf.acp.pip.box.name.description"><![CDATA[The box name is displayed in the ACP on the box list.]]></item>
+               <item name="wcf.acp.pip.box.showHeader"><![CDATA[Show Header]]></item>
+               <item name="wcf.acp.pip.box.visibleEverywhere"><![CDATA[Box Visible Everywhere]]></item>
+               <item name="wcf.acp.pip.box.visibilityExceptions.hiddenEverywhere"><![CDATA[Pages on Which the Box Will be Explicitly <strong>Visible</strong>]]></item>
+               <item name="wcf.acp.pip.box.visibilityExceptions.visibleEverywhere"><![CDATA[Pages on Which the Box Will be Explicitly <strong>Hidden</strong>]]></item>
+               <item name="wcf.acp.pip.box.cssClassName"><![CDATA[Box CSS Classes]]></item>
+               <item name="wcf.acp.pip.box.cssClassName.description"><![CDATA[The entered comma-separated CSS classes are assigned to the box element.]]></item>
+               <item name="wcf.acp.pip.box.content"><![CDATA[Box Contents]]></item>
+               <item name="wcf.acp.pip.box.content.title"><![CDATA[Box Title]]></item>
+               <item name="wcf.acp.pip.box.content.content"><![CDATA[Box Content]]></item>
        </category>
        
        <category name="wcf.acp.rebuildData">