From e5c4e69b9c79ef42aaf6f24194b3b896103c6dc6 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 9 Sep 2018 10:02:58 +0200 Subject: [PATCH] Add GUI support for bbcode package installation plugin See #2545 --- .../templates/__bbcodeAttributesFormField.tpl | 231 +++++++++++ .../Field/Dependency/Container/Default.js | 4 + .../BBCodeAttributesFormField.class.php | 87 +++++ .../BBCodePackageInstallationPlugin.class.php | 364 +++++++++++++++++- wcfsetup/install/lang/en.xml | 32 +- 5 files changed, 714 insertions(+), 4 deletions(-) create mode 100644 wcfsetup/install/files/acp/templates/__bbcodeAttributesFormField.tpl create mode 100644 wcfsetup/install/files/lib/system/form/builder/field/bbcode/BBCodeAttributesFormField.class.php diff --git a/wcfsetup/install/files/acp/templates/__bbcodeAttributesFormField.tpl b/wcfsetup/install/files/acp/templates/__bbcodeAttributesFormField.tpl new file mode 100644 index 0000000000..e04fc99110 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/__bbcodeAttributesFormField.tpl @@ -0,0 +1,231 @@ +{capture assign='attributeTemplate'} +
+

{lang __literal=true}wcf.acp.bbcode.numberedAttribute{/lang}

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+
+ +
+
+ +
+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+
+ + {event name='attributeFields'} +
+{/capture} + + + +{foreach from=$field->getValue() key=attributeNumber item=attributeData name=bbcodeAttributes} +
+

{lang}wcf.acp.bbcode.numberedAttribute{/lang}

+ +
+
+ +
+
+ +
+
+ + {assign var='__attributeValidationError' value=null} + {foreach from=$field->getValidationErrors() item=validationError} + {if $validationError->getType() === $field->getPrefixedId()|concat:'_validationPattern_':$attributeNumber} + {assign var='__attributeValidationError' value=$validationError} + {/if} + {/foreach} + +
+ +
+
+ + {if $__attributeValidationError !== null} + {@$__attributeValidationError->getMessage()} + {/if} +
+ + +
+
+ +
+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+
+ +
+
+ +
+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+ {lang}wcf.acp.bbcode.attribute.useText.description{/lang} +
+
+ + {event name='attributeFields'} +
+{/foreach} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default.js index c2ca135b5c..112a6ee778 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default.js @@ -23,6 +23,10 @@ define(['./Abstract', 'Core', '../Manager'], function(Abstract, Core, Dependency * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer */ checkContainer: function() { + if (elDataBool(this._container, 'ignore-dependencies')) { + return; + } + // only consider containers that have not been hidden by their own dependencies if (DependencyManager.isHiddenByDependencies(this._container)) { return; diff --git a/wcfsetup/install/files/lib/system/form/builder/field/bbcode/BBCodeAttributesFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/bbcode/BBCodeAttributesFormField.class.php new file mode 100644 index 0000000000..131899bc02 --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/field/bbcode/BBCodeAttributesFormField.class.php @@ -0,0 +1,87 @@ + + * @package WoltLabSuite\Core\System\Form\Builder\Field\BBCode + * @since 3.2 + */ +class BBCodeAttributesFormField extends AbstractFormField { + use TDefaultIdFormField; + + /** + * @inheritDoc + */ + protected $templateName = '__bbcodeAttributesFormField'; + + /** + * @inheritDoc + */ + protected $__value = []; + + /** + * @inheritDoc + */ + protected static function getDefaultId() { + return 'attributes'; + } + + /** + * @inheritDoc + */ + public function readValue() { + if ($this->getDocument()->hasRequestData($this->getPrefixedId()) && is_array($this->getDocument()->getRequestData($this->getPrefixedId()))) { + $this->__value = $this->getDocument()->getRequestData($this->getPrefixedId()); + } + } + + /** + * @inheritDoc + */ + public function validate() { + foreach ($this->getValue() as $attributeNumber => $attributeData) { + if (!empty($attributeData['attributeHtml']) && mb_strlen($attributeData['attributeHtml']) > 255) { + $this->addValidationError( + new FormFieldValidationError( + $this->getPrefixedId() . '_attributeHtml_' . $attributeNumber, + 'wcf.form.field.text.error.maximumLength', + [ + 'length' => mb_strlen($attributeData['attributeHtml']), + 'maximumLength' => 255 + ] + ) + ); + } + if (!empty($attributeData['validationPattern'])) { + if (mb_strlen($attributeData['validationPattern']) > 255) { + $this->addValidationError( + new FormFieldValidationError( + $this->getPrefixedId() . '_validationPattern_' . $attributeNumber, + 'wcf.form.field.text.error.maximumLength', + [ + 'length' => mb_strlen($attributeData['validationPattern']), + 'maximumLength' => 255 + ] + ) + ); + } + else if (!Regex::compile($attributeData['validationPattern'])->isValid()) { + $this->addValidationError( + new FormFieldValidationError( + $this->getPrefixedId() . '_validationPattern_' . $attributeNumber, + 'wcf.acp.bbcode.attribute.validationPattern.error.invalid' + ) + ); + } + } + } + } +} diff --git a/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php index c03a3153a4..bdb8f2c999 100644 --- a/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php +++ b/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php @@ -3,10 +3,28 @@ namespace wcf\system\package\plugin; use wcf\data\bbcode\attribute\BBCodeAttributeEditor; use wcf\data\bbcode\BBCode; use wcf\data\bbcode\BBCodeEditor; +use wcf\data\bbcode\BBCodeList; use wcf\data\package\PackageCache; +use wcf\system\bbcode\IBBCode; 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\field\bbcode\BBCodeAttributesFormField; +use wcf\system\form\builder\field\BooleanFormField; +use wcf\system\form\builder\field\ClassNameFormField; +use wcf\system\form\builder\field\data\VoidFormFieldDataProcessor; +use wcf\system\form\builder\field\dependency\NonEmptyFormFieldDependency; +use wcf\system\form\builder\field\dependency\ValueFormFieldDependency; +use wcf\system\form\builder\field\IconFormField; +use wcf\system\form\builder\field\RadioButtonFormField; +use wcf\system\form\builder\field\TextFormField; +use wcf\system\form\builder\field\validation\FormFieldValidationError; +use wcf\system\form\builder\field\validation\FormFieldValidator; +use wcf\system\form\builder\field\validation\FormFieldValidatorUtil; +use wcf\system\form\builder\IFormDocument; use wcf\system\WCF; use wcf\util\StringUtil; @@ -18,7 +36,9 @@ use wcf\util\StringUtil; * @license GNU Lesser General Public License * @package WoltLabSuite\Core\Acp\Package\Plugin */ -class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IIdempotentPackageInstallationPlugin { +class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin implements IGuiPackageInstallationPlugin { + use TXmlGuiPackageInstallationPlugin; + /** * @inheritDoc */ @@ -175,6 +195,10 @@ class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlug if (!empty($this->attributes)) { foreach ($this->attributes as $bbcodeID => $bbcodeAttributes) { + if ($bbcodeID != intval($bbcodeID)) { + $bbcodeID = BBCode::getBBCodeByTag($bbcodeID)->bbcodeID; + } + foreach ($bbcodeAttributes as $attributeNo => $attribute) { BBCodeAttributeEditor::create([ 'bbcodeID' => $bbcodeID, @@ -199,8 +223,344 @@ class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlug /** * @inheritDoc + * @since 3.1 */ public static function getSyncDependencies() { return []; } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function getElementData(\DOMElement $element, $saveData = false) { + $data = [ + 'attributes' => [], + 'bbcodeTag' => $element->getAttribute('name'), + 'packageID' => $this->installation->getPackage()->packageID, + 'originIsSystem' => 1 + ]; + + $optionalElements = [ + 'buttonLabel' => 'buttonlabel', + 'className' => 'classname', + 'htmlClose' => 'htmlclose', + 'htmlOpen' => 'htmlopen', + 'isBlockElement' => 'isBlockElement', + 'isSourceCode' => 'sourcecode', + 'wysiwygIcon' => 'wysiwygicon' + ]; + foreach ($optionalElements as $arrayKey => $elementName) { + $child = $element->getElementsByTagName($elementName)->item(0); + if ($child !== null) { + $data[$arrayKey] = $child->nodeValue; + } + } + + if (!empty($data['wysiwygIcon']) && !empty($data['buttonLabel'])) { + $data['showButton'] = 1; + } + + // attributes + $attributes = $element->getElementsByTagName('attributes')->item(0); + if ($attributes !== null) { + $optionalAttributeElements = [ + 'attributeHtml' => 'html', + 'required' => 'required', + 'useText' => 'usetext', + 'validationPattern' => 'validationpattern', + ]; + + /** @var \DOMElement $attribute */ + foreach ($attributes->childNodes as $attribute) { + $attributeData = ['name' => $attribute->nodeValue]; + + foreach ($optionalAttributeElements as $arrayKey => $elementName) { + $child = $attribute->getElementsByTagName($elementName)->item(0); + if ($child !== null) { + $attributeData[$arrayKey] = $child->nodeValue; + } + } + + $data['attributes'][] = $attributeData; + } + } + + return $data; + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function getElementIdentifier(\DOMElement $element) { + return $element->getAttribute('name'); + } + + /** + * @inheritDoc + * @since 3.2 + */ + public function addFormFields(IFormDocument $form) { + /** @var FormContainer $dataContainer */ + $dataContainer = $form->getNodeById('data'); + + // shared validators + $htmlValidator = new FormFieldValidator('format', function(TextFormField $formField) { + $value = $formField->getValue(); + if ($value !== null && $value !== '') { + if (in_array(substr($formField->getValue(), 0, 1), ['<', '>'])) { + $formField->addValidationError( + new FormFieldValidationError( + 'leadingBracket', + 'wcf.acp.pip.bbcode.htmlOpen.error.leadingBracket' + ) + ); + } + else if (in_array(substr($formField->getValue(), -1, 1), ['<', '>'])) { + $formField->addValidationError( + new FormFieldValidationError( + 'trailingBracket', + 'wcf.acp.pip.bbcode.htmlOpen.error.trailingBracket' + ) + ); + } + } + }); + + // add fields + $dataContainer->appendChildren([ + TextFormField::create('bbcodeTag') + ->objectProperty('name') + ->label('wcf.acp.pip.bbcode.bbcodeTag') + ->description('wcf.acp.pip.bbcode.bbcodeTag.description') + ->required() + ->maximumLength(191) + ->addValidator(FormFieldValidatorUtil::getRegularExpressionValidator( + '[a-z0-9]+', + 'wcf.acp.pip.bbcode.bbcodeTag' + )) + ->addValidator(new FormFieldValidator('allNone', function(TextFormField $formField) { + if ($formField->getValue() === 'all' || $formField->getValue() === 'none') { + $formField->addValidationError( + new FormFieldValidationError( + 'allNone', + 'wcf.acp.pip.bbcode.bbcodeTag.error.allNone' + ) + ); + } + })) + ->addValidator(new FormFieldValidator('uniqueness', function(TextFormField $formField) { + if ( + $formField->getDocument()->getFormMode() === IFormDocument::FORM_MODE_CREATE || + $this->editedEntry->getAttribute('name') !== $formField->getValue() + ) { + $providerList = new BBCodeList(); + $providerList->getConditionBuilder()->add('bbcodeTag = ?', [$formField->getValue()]); + + if ($providerList->countObjects() > 0) { + $formField->addValidationError( + new FormFieldValidationError( + 'notUnique', + 'wcf.acp.pip.bbcode.bbcodeTag.error.notUnique' + ) + ); + } + } + })), + + TextFormField::create('htmlOpen') + ->objectProperty('htmlopen') + ->label('wcf.acp.pip.bbcode.htmlOpen') + ->description('wcf.acp.pip.bbcode.htmlOpen.description') + ->maximumLength(255) + ->addValidator($htmlValidator), + + TextFormField::create('htmlClose') + ->objectProperty('htmlclose') + ->label('wcf.acp.pip.bbcode.htmlClose') + ->description('wcf.acp.pip.bbcode.htmlClose.description') + ->maximumLength(255) + // note: the language items for the opening tag are reused + ->addValidator($htmlValidator), + + BooleanFormField::create('isBlockElement') + ->label('wcf.acp.pip.bbcode.isBlockElement') + ->description('wcf.acp.pip.bbcode.isBlockElement.description'), + + BooleanFormField::create('isSourceCode') + ->objectProperty('sourcecode') + ->label('wcf.acp.pip.bbcode.isSourceCode') + ->description('wcf.acp.pip.bbcode.isSourceCode.description'), + + ClassNameFormField::create() + ->objectProperty('classname') + ->implementedInterface(IBBCode::class), + + BooleanFormField::create('showButton') + ->objectProperty('showbutton') + ->label('wcf.acp.pip.bbcode.showButton') + ->description('wcf.acp.pip.bbcode.showButton.description'), + + TextFormField::create('buttonLabel') + ->objectProperty('buttonlabel') + ->label('wcf.acp.pip.bbcode.buttonLabel') + ->description('wcf.acp.pip.bbcode.buttonLabel.description') + ->required() + ->maximumLength(255), + + RadioButtonFormField::create('iconType') + ->label('wcf.acp.pip.bbcode.iconType') + ->options([ + 'filePath' => 'wcf.acp.pip.bbcode.iconType.filePath', + 'fontAwesome' => 'wcf.acp.pip.bbcode.iconType.fontAwesome', + ]) + ->required() + ->value('fontAwesome'), + + TextFormField::create('iconPath') + ->objectProperty('wysiwygicon') + ->label('wcf.acp.pip.bbcode.iconPath') + ->description('wcf.acp.pip.bbcode.iconPath.description') + ->required() + ->maximumLength(255) + ->addValidator(new FormFieldValidator('fileExists', function(TextFormField $formField) { + if (!file_exists(WCF_DIR . 'icon/' . $formField->getValue())) { + $formField->addValidationError( + new FormFieldValidationError( + 'fileDoesNotExist', + 'wcf.acp.pip.bbcode.iconPath.error.fileDoesNotExist' + ) + ); + } + })), + + IconFormField::create('fontAwesomeIcon') + ->objectProperty('wysiwygicon') + ->label('wcf.acp.pip.bbcode.wysiwygIcon') + ->required() + ]); + + $form->appendChild( + FormContainer::create('bbcodeAttributes') + ->attribute('data-ignore-dependencies', 1) + ->label('wcf.acp.pip.bbcode.attributes') + ->appendChild( + BBCodeAttributesFormField::create() + ) + ); + + // discard the `iconType` value as it is only used to distinguish the two icon input fields + $form->getDataHandler()->add(new VoidFormFieldDataProcessor('iconType')); + + // add dependencies + /** @var BooleanFormField $showButton */ + $showButton = $dataContainer->getNodeById('showButton'); + $iconType = $dataContainer->getNodeById('iconType'); + + $dataContainer->getNodeById('buttonLabel')->addDependency( + NonEmptyFormFieldDependency::create('showButton') + ->field($showButton) + ); + $iconType->addDependency( + NonEmptyFormFieldDependency::create('showButton') + ->field($showButton) + ); + $dataContainer->getNodeById('iconPath')->addDependency( + ValueFormFieldDependency::create('iconType') + ->field($iconType) + ->values(['filePath']) + ); + $dataContainer->getNodeById('fontAwesomeIcon')->addDependency( + ValueFormFieldDependency::create('iconType') + ->field($iconType) + ->values(['fontAwesome']) + ); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function setEntryListKeys(IDevtoolsPipEntryList $entryList) { + $entryList->setKeys([ + 'bbcodeTag' => 'wcf.acp.pip.bbcode.bbcodeTag', + 'htmlOpen' => 'wcf.acp.pip.bbcode.htmlOpen', + 'className' => 'wcf.form.field.className' + ]); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function sortDocument(\DOMDocument $document) { + $this->sortImportDelete($document); + + $compareFunction = static::getSortFunction([ + [ + 'isAttribute' => 1, + 'name' => 'name' + ] + ]); + + $this->sortChildNodes($document->getElementsByTagName('import'), $compareFunction); + $this->sortChildNodes($document->getElementsByTagName('delete'), $compareFunction); + } + + /** + * @inheritDoc + * @since 3.2 + */ + protected function writeEntry(\DOMDocument $document, IFormDocument $form) { + $data = $form->getData()['data']; + + $bbcode = $document->createElement($this->tagName); + $bbcode->setAttribute('name', $data['name']); + + $fields = [ + 'classname' => '', + 'htmlclose' => '', + 'htmlopen' => '', + 'isBlockElement' => 0, + 'sourcecode' => 0, + 'buttonlabel' => '', + 'wysiwygicon' => '' + ]; + + foreach ($fields as $field => $defaultValue) { + if (isset($data[$field]) && $data[$field] !== $defaultValue) { + $bbcode->appendChild($document->createElement($field, (string) $data[$field])); + } + } + + if (!empty($data['attributes'])) { + $attributes = $document->createElement('attributes'); + $bbcode->appendChild($attributes); + + foreach ($data['attributes'] as $attributeNumber => $attributeData) { + $attribute = $document->createElement('attribute'); + $attribute->setAttribute('name', (string) $attributeNumber); + + if (!empty($attributeData['attributeHtml'])) { + $attribute->appendChild($document->createElement('html', $attributeData['attributeHtml'])); + } + if (!empty($attributeData['validationPattern'])) { + $attribute->appendChild($document->createElement('validationpattern', $attributeData['validationPattern'])); + } + if (!empty($attributeData['required'])) { + $attribute->appendChild($document->createElement('required', $attributeData['required'])); + } + if (!empty($attributeData['useText'])) { + $attribute->appendChild($document->createElement('usetext', $attributeData['useText'])); + } + + $attributes->appendChild($attribute); + } + } + + $document->getElementsByTagName('import')->item(0)->appendChild($bbcode); + + return $bbcode; + } } diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index a462be9ce5..9a52d3def5 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -117,6 +117,7 @@ + @@ -2142,6 +2143,33 @@ 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.]]> + + + all nor none.]]> + + + + + + + + + + + + + + + + + + + + + + {'WCF_DIR'|constant}icon/.]]> + + @@ -3469,8 +3497,8 @@ Email: {@$emailAddress} {* this line ends with a space *} {$option}{/implode}.]]> - - + + -- 2.20.1