diff --git a/wcfsetup/install/files/acp/templates/__labelGroupObjectTypes.tpl b/wcfsetup/install/files/acp/templates/__labelGroupObjectTypes.tpl new file mode 100644 index 0000000000..0956561d75 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/__labelGroupObjectTypes.tpl @@ -0,0 +1,23 @@ + + +
+ {foreach from=$labelObjectTypeContainers item=container} +
+
{$container->getTitle()}
+
+
    + {foreach from=$container item=objectType} +
  • getDepth()} style="padding-left: {$objectType->getDepth() * 20}px"{/if} data-depth="{$objectType->getDepth()}"> + {$objectType->getLabel()} + +
  • + {/foreach} +
+
+
+ {/foreach} +
diff --git a/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl b/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl index 78779b2195..687f8c64e2 100644 --- a/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl +++ b/wcfsetup/install/files/acp/templates/labelGroupAdd.tpl @@ -1,145 +1,36 @@ {include file='header' pageTitle='wcf.acp.label.group.'|concat:$action} -{include file='aclPermissions'} - - -{if !$groupID|isset} - {include file='shared_aclPermissionJavaScript' containerID='groupPermissions'} -{else} - {include file='shared_aclPermissionJavaScript' containerID='groupPermissions' objectID=$groupID} -{/if} - -{assign var=labelForceSelection value=$forceSelection} -

{lang}wcf.acp.label.group.{$action}{/lang}

- +
-{include file='shared_formNotice'} - -
-
- - -
-
- -
-
- - {if $errorField == 'groupName'} - - {if $errorType == 'empty' || $errorType == 'multilingual'} - {lang}wcf.global.form.error.{$errorType}{/lang} - {else} - {lang}wcf.acp.label.group.groupName.error.{$errorType}{/lang} - {/if} - - {/if} - {lang}wcf.acp.label.group.groupName.description{/lang} - {include file='shared_multipleLanguageInputJavascript' elementIdentifier='groupName' forceSelection=false} -
- - -
-
-
- - {lang}wcf.acp.label.group.groupDescription.description{/lang} -
-
- -
-
-
- -
-
- -
-
-
-
- -
-
-
-
- -
-
{lang}wcf.acl.permissions{/lang}
-
-
- - {event name='dataFields'} -
- - {event name='generalSections'} -
- -
-
- {foreach from=$labelObjectTypeContainers item=container} -
-
{$container->getTitle()}
-
-
    - {foreach from=$container item=objectType} -
  • getDepth()} style="padding-left: {$objectType->getDepth() * 20}px"{/if} data-depth="{$objectType->getDepth()}"> - {$objectType->getLabel()} - -
  • - {/foreach} -
-
-
- {/foreach} -
- - {event name='connectSections'} -
-
- -
- - {csrfToken} -
-
+{unsafe:$form->getHtml()} -{if $action == 'edit' && !$labelGroup->sortAlphabetically} +{if $action == 'edit' && !$formObject->sortAlphabetically} diff --git a/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php index 8038418c42..14ae1d6eb8 100644 --- a/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/LabelGroupAddForm.class.php @@ -2,27 +2,37 @@ namespace wcf\acp\form; +use wcf\data\label\group\LabelGroup; use wcf\data\label\group\LabelGroupAction; use wcf\data\label\group\LabelGroupEditor; use wcf\data\object\type\ObjectTypeCache; -use wcf\form\AbstractForm; +use wcf\form\AbstractFormBuilderForm; use wcf\system\acl\ACLHandler; -use wcf\system\exception\UserInputException; +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\data\processor\CustomFormDataProcessor; +use wcf\system\form\builder\field\acl\AclFormField; +use wcf\system\form\builder\field\BooleanFormField; +use wcf\system\form\builder\field\IntegerFormField; +use wcf\system\form\builder\field\TextFormField; +use wcf\system\form\builder\IFormDocument; +use wcf\system\form\builder\TemplateFormNode; use wcf\system\label\object\type\ILabelObjectTypeHandler; use wcf\system\label\object\type\LabelObjectTypeContainer; use wcf\system\language\I18nHandler; -use wcf\system\request\LinkHandler; use wcf\system\WCF; -use wcf\util\StringUtil; /** * Shows the label group add form. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Alexander Ebert, Marcel Werk + * @copyright 2001-2026 WoltLab GmbH + * @license GNU Lesser General Public License + * + * @extends AbstractFormBuilderForm */ -class LabelGroupAddForm extends AbstractForm +class LabelGroupAddForm extends AbstractFormBuilderForm { /** * @inheritDoc @@ -35,103 +45,41 @@ class LabelGroupAddForm extends AbstractForm public $neededPermissions = ['admin.content.label.canManageLabel']; /** - * force users to select a label - * @var bool + * @inheritDoc */ - public $forceSelection = false; - - public bool $sortAlphabetically = false; + public $objectActionClass = LabelGroupAction::class; /** - * group name - * @var string + * @inheritDoc */ - public $groupName = ''; + public $objectEditLinkController = LabelGroupEditForm::class; /** - * group description - * @var string + * list of label group to object type relations + * @var array */ - public $groupDescription = ''; + public array $objectTypes = []; /** * list of label object type handlers * @var ILabelObjectTypeHandler[] */ - public $labelObjectTypes = []; + protected array $labelObjectTypes = []; /** * list of label object type containers * @var LabelObjectTypeContainer[] */ - public $labelObjectTypeContainers = []; - - /** - * list of label group to object type relations - * @var array - */ - public $objectTypes = []; - - /** - * object type id - * @var int - */ - public $objectTypeID = 0; - - /** - * show order - * @var int - */ - public $showOrder = 0; + protected array $labelObjectTypeContainers = []; - /** - * @inheritDoc - */ + #[\Override] public function readParameters() { parent::readParameters(); - $this->objectTypeID = ACLHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.label'); - - I18nHandler::getInstance()->register('groupName'); - } - - /** - * @inheritDoc - */ - public function readFormParameters() - { - parent::readFormParameters(); - - I18nHandler::getInstance()->readValues(); - - if (I18nHandler::getInstance()->isPlainValue('groupName')) { - $this->groupName = I18nHandler::getInstance()->getValue('groupName'); - } - - if (isset($_POST['groupDescription'])) { - $this->groupDescription = StringUtil::trim($_POST['groupDescription']); - } - if (isset($_POST['forceSelection'])) { - $this->forceSelection = true; - } - if (isset($_POST['sortAlphabetically'])) { - $this->sortAlphabetically = true; - } - if (isset($_POST['objectTypes']) && \is_array($_POST['objectTypes'])) { - $this->objectTypes = $_POST['objectTypes']; - } - if (isset($_POST['showOrder'])) { - $this->showOrder = \intval($_POST['showOrder']); - } - } - - /** - * @inheritDoc - */ - public function readData() - { - // get label object type handlers + // Initialize label object types and containers before form building + // (which happens in checkPermissions), since the TemplateFormNode + // in createForm() needs the containers. $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.label.objectType'); foreach ($objectTypes as $objectType) { $handler = $objectType->getProcessor(); @@ -142,30 +90,99 @@ public function readData() $this->labelObjectTypes[$objectType->objectTypeID] = $handler; $this->labelObjectTypeContainers[$objectType->objectTypeID] = $container; } + } - parent::readData(); + #[\Override] + protected function createForm() + { + parent::createForm(); + + $tabMenu = TabMenuFormContainer::create('tabMenu'); + $tabMenu->appendChildren([ + TabFormContainer::create('general') + ->label('wcf.global.form.data') + ->appendChildren([ + FormContainer::create('generalContainer') + ->appendChildren([ + TextFormField::create('groupName') + ->label('wcf.global.title') + ->required() + ->autoFocus() + ->maximumLength(80) + ->i18n() + ->languageItemPattern('wcf.acp.label.group\d+'), + TextFormField::create('groupDescription') + ->label('wcf.global.description') + ->description('wcf.acp.label.group.groupDescription.description') + ->maximumLength(255), + IntegerFormField::create('showOrder') + ->label('wcf.global.showOrder') + ->minimum(0) + ->value(0), + BooleanFormField::create('forceSelection') + ->label('wcf.acp.label.group.forceSelection'), + BooleanFormField::create('sortAlphabetically') + ->label('wcf.acp.label.group.sortAlphabetically'), + AclFormField::create('aclPermissions') + ->label('wcf.acl.permissions') + ->objectType('com.woltlab.wcf.label'), + ]), + ]), + TabFormContainer::create('connect') + ->label('wcf.acp.label.group.category.connect') + ->appendChildren([ + FormContainer::create('connectElements') + ->appendChildren([ + TemplateFormNode::create('labelObjectTypes') + ->templateName('__labelGroupObjectTypes') + ->variables([ + 'labelObjectTypeContainers' => $this->labelObjectTypeContainers, + ]) + ]), + ]), + ]); - // assign new values for object relations - $this->setObjectTypeRelations(); + $this->form->appendChildren([$tabMenu]); } - /** - * @inheritDoc - */ - public function validate() + #[\Override] + protected function finalizeForm() { - parent::validate(); + parent::finalizeForm(); + + // The groupName column is NOT NULL without a default. When i18n values + // are used, hasSaveValue() returns false and groupName would be missing + // from the data array. This processor ensures it's always present. + $this->form->getDataHandler()->addProcessor( + new CustomFormDataProcessor( + 'groupNameFallback', + function (IFormDocument $document, array $parameters) { + if (!isset($parameters['data']['groupName'])) { + $parameters['data']['groupName'] = ''; + } - // validate group name - if (!I18nHandler::getInstance()->validateValue('groupName')) { - if (I18nHandler::getInstance()->isPlainValue('groupName')) { - throw new UserInputException('groupName'); - } else { - throw new UserInputException('groupName', 'multilingual'); - } + return $parameters; + } + ) + ); + } + + #[\Override] + public function readFormParameters() + { + parent::readFormParameters(); + + if (isset($_POST['objectTypes']) && \is_array($_POST['objectTypes'])) { + $this->objectTypes = $_POST['objectTypes']; } + } - // validate object type relations + #[\Override] + public function validate() + { + parent::validate(); + + // Sanitize object type relations. foreach ($this->objectTypes as $objectTypeID => $data) { if (!isset($this->labelObjectTypes[$objectTypeID])) { unset($this->objectTypes[$objectTypeID]); @@ -173,112 +190,78 @@ public function validate() } } - /** - * @inheritDoc - */ - public function save() + #[\Override] + public function readData() { - parent::save(); - - // save label - $this->objectAction = new LabelGroupAction([], 'create', [ - 'data' => \array_merge($this->additionalFields, [ - 'forceSelection' => $this->forceSelection ? 1 : 0, - 'sortAlphabetically' => $this->sortAlphabetically ? 1 : 0, - 'groupName' => $this->groupName, - 'groupDescription' => $this->groupDescription, - 'showOrder' => $this->showOrder, - ]), - ]); - $returnValues = $this->objectAction->executeAction(); + parent::readData(); - if (!I18nHandler::getInstance()->isPlainValue('groupName')) { + $this->setObjectTypeRelations(); + } + + #[\Override] + public function saved() + { + $formData = $this->form->getData(); + + if ($this->formAction === 'create') { + $group = $this->objectAction->getReturnValues()['returnValues']; + \assert($group instanceof LabelGroup); + $groupID = $group->groupID; + } else { + $groupID = $this->formObject->groupID; + } + + // Handle i18n groupName. + $languageItem = 'wcf.acp.label.group' . $groupID; + if (isset($formData['groupName_i18n'])) { I18nHandler::getInstance()->save( - 'groupName', - 'wcf.acp.label.group' . $returnValues['returnValues']->groupID, + $formData['groupName_i18n'], + $languageItem, 'wcf.acp.label', 1 ); - // update group name - $groupEditor = new LabelGroupEditor($returnValues['returnValues']); - $groupEditor->update([ - 'groupName' => 'wcf.acp.label.group' . $returnValues['returnValues']->groupID, - ]); + if ($this->formAction === 'create') { + (new LabelGroupEditor($group))->update(['groupName' => $languageItem]); + } else { + (new LabelGroupEditor($this->formObject))->update(['groupName' => $languageItem]); + } + } elseif ($this->formAction === 'edit') { + // Switched from i18n to plain value — remove old language items. + I18nHandler::getInstance()->remove($languageItem); } - // save acl - ACLHandler::getInstance()->save($returnValues['returnValues']->groupID, $this->objectTypeID); - ACLHandler::getInstance()->disableAssignVariables(); + // Save ACL. + ACLHandler::getInstance()->save($groupID, $formData['aclPermissions_aclObjectTypeID']); - // save object type relations - $this->saveObjectTypeRelations($returnValues['returnValues']->groupID); + // Save object type relations. + $this->saveObjectTypeRelations($groupID); foreach ($this->labelObjectTypes as $labelObjectType) { $labelObjectType->save(); } - $this->saved(); - - // reset values - $this->forceSelection = false; - $this->sortAlphabetically = false; - $this->groupName = $this->groupDescription = ''; - $this->objectTypes = []; - $this->showOrder = 0; - $this->setObjectTypeRelations(); - - // show success message - WCF::getTPL()->assign([ - 'success' => true, - 'objectEditLink' => LinkHandler::getInstance()->getControllerLink( - LabelGroupEditForm::class, - ['id' => $returnValues['returnValues']->groupID] - ), - ]); - - I18nHandler::getInstance()->reset(); - } + // Reset object type selections for create form. + if ($this->formAction === 'create') { + $this->objectTypes = []; + $this->setObjectTypeRelations(); + } - /** - * @inheritDoc - */ - public function assignVariables() - { - parent::assignVariables(); - - ACLHandler::getInstance()->assignVariables($this->objectTypeID); - I18nHandler::getInstance()->assignVariables(); - - WCF::getTPL()->assign([ - 'action' => 'add', - 'forceSelection' => $this->forceSelection, - 'sortAlphabetically' => $this->sortAlphabetically, - 'groupName' => $this->groupName, - 'groupDescription' => $this->groupDescription, - 'labelObjectTypeContainers' => $this->labelObjectTypeContainers, - 'objectTypeID' => $this->objectTypeID, - 'showOrder' => $this->showOrder, - ]); + parent::saved(); } /** * Saves label group to object relations. - * - * @param ?int $groupID - * @return void */ - protected function saveObjectTypeRelations($groupID) + protected function saveObjectTypeRelations(int $groupID): void { WCF::getDB()->beginTransaction(); // remove old relations - if ($groupID !== null) { - $sql = "DELETE FROM wcf1_label_group_to_object - WHERE groupID = ?"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([$groupID]); - } + $sql = "DELETE FROM wcf1_label_group_to_object + WHERE groupID = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$groupID]); // insert new relations if (!empty($this->objectTypes)) { @@ -310,9 +293,8 @@ protected function saveObjectTypeRelations($groupID) * Sets object type relations. * * @param ?array $data - * @return void */ - protected function setObjectTypeRelations($data = null) + protected function setObjectTypeRelations(?array $data = null): void { if (!empty($_POST)) { // use POST data diff --git a/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php b/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php index 0390b286a4..480823eb01 100644 --- a/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/LabelGroupEditForm.class.php @@ -2,24 +2,22 @@ namespace wcf\acp\form; +use CuyZ\Valinor\Mapper\MappingError; use wcf\acp\page\LabelGroupListPage; use wcf\data\label\group\LabelGroup; -use wcf\data\label\group\LabelGroupAction; -use wcf\form\AbstractForm; -use wcf\system\acl\ACLHandler; +use wcf\http\Helper; use wcf\system\exception\IllegalLinkException; use wcf\system\interaction\admin\LabelGroupInteractions; use wcf\system\interaction\StandaloneInteractionContextMenuComponent; -use wcf\system\language\I18nHandler; use wcf\system\request\LinkHandler; use wcf\system\WCF; /** * Shows the label group edit form. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Alexander Ebert, Marcel Werk + * @copyright 2001-2026 WoltLab GmbH + * @license GNU Lesser General Public License */ class LabelGroupEditForm extends LabelGroupAddForm { @@ -31,127 +29,49 @@ class LabelGroupEditForm extends LabelGroupAddForm /** * @inheritDoc */ - public $neededPermissions = ['admin.content.label.canManageLabel']; + public $formAction = 'edit'; - /** - * group id - * @var int - */ - public $groupID = 0; - - /** - * label group object - * @var LabelGroup - */ - public $group; - - /** - * @inheritDoc - */ + #[\Override] public function readParameters() { parent::readParameters(); - if (isset($_REQUEST['id'])) { - $this->groupID = \intval($_REQUEST['id']); - } - $this->group = new LabelGroup($this->groupID); - if (!$this->group->groupID) { + try { + $queryParameters = Helper::mapQueryParameters( + $_GET, + <<<'EOT' + array { + id: positive-int + } + EOT + ); + } catch (MappingError) { throw new IllegalLinkException(); } - } - - /** - * @inheritDoc - */ - public function save() - { - AbstractForm::save(); - - $this->groupName = 'wcf.acp.label.group' . $this->group->groupID; - if (I18nHandler::getInstance()->isPlainValue('groupName')) { - I18nHandler::getInstance()->remove($this->groupName); - $this->groupName = I18nHandler::getInstance()->getValue('groupName'); - } else { - I18nHandler::getInstance()->save('groupName', $this->groupName, 'wcf.acp.label', 1); - } - - // update label - $this->objectAction = new LabelGroupAction( - [$this->groupID], - 'update', - [ - 'data' => \array_merge($this->additionalFields, [ - 'forceSelection' => $this->forceSelection ? 1 : 0, - 'sortAlphabetically' => $this->sortAlphabetically ? 1 : 0, - 'groupName' => $this->groupName, - 'groupDescription' => $this->groupDescription, - 'showOrder' => $this->showOrder, - ]), - ] - ); - $this->objectAction->executeAction(); - - // update acl - ACLHandler::getInstance()->save($this->groupID, $this->objectTypeID); - ACLHandler::getInstance()->disableAssignVariables(); - - // update object type relations - $this->saveObjectTypeRelations($this->groupID); - - foreach ($this->labelObjectTypes as $labelObjectType) { - $labelObjectType->save(); - } - - $this->saved(); - - // show success message - WCF::getTPL()->assign('success', true); - } - - /** - * @inheritDoc - */ - public function readData() - { - parent::readData(); - if (empty($_POST)) { - I18nHandler::getInstance()->setOptions('groupName', 1, $this->group->groupName, 'wcf.acp.label.group\d+'); + $this->formObject = new LabelGroup($queryParameters['id']); - $this->forceSelection = ($this->group->forceSelection ? true : false); - $this->sortAlphabetically = ($this->group->sortAlphabetically ? true : false); - $this->groupName = $this->group->groupName; - $this->groupDescription = $this->group->groupDescription; - $this->showOrder = $this->group->showOrder; + if (!$this->formObject->getObjectID()) { + throw new IllegalLinkException(); } } - /** - * @inheritDoc - */ + #[\Override] public function assignVariables() { parent::assignVariables(); - I18nHandler::getInstance()->assignVariables(!empty($_POST)); - WCF::getTPL()->assign([ - 'action' => 'edit', - 'groupID' => $this->groupID, - 'labelGroup' => $this->group, 'interactionContextMenu' => StandaloneInteractionContextMenuComponent::forContentHeaderButton( new LabelGroupInteractions(), - $this->group, + $this->formObject, LinkHandler::getInstance()->getControllerLink(LabelGroupListPage::class) ), ]); } - /** - * @inheritDoc - */ - protected function setObjectTypeRelations($data = null) + #[\Override] + protected function setObjectTypeRelations(?array $data = null): void { if (empty($_POST)) { // read database values @@ -159,7 +79,7 @@ protected function setObjectTypeRelations($data = null) FROM wcf1_label_group_to_object WHERE groupID = ?"; $statement = WCF::getDB()->prepare($sql); - $statement->execute([$this->groupID]); + $statement->execute([$this->formObject->groupID]); $data = []; while ($row = $statement->fetchArray()) {