From 5967ec80303b9d02f8af021f645bc3d2a033605c Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 21 May 2025 08:53:49 +0200 Subject: [PATCH 01/82] Implement basic condition types and provider --- .../AbstractConditionProvider.class.php | 39 +++++++++++++ .../provider/UserConditionProvider.class.php | 28 ++++++++++ .../condition/type/IConditionType.class.php | 29 ++++++++++ ...IDatabaseObjectListConditionType.class.php | 23 ++++++++ .../type/IObjectConditionType.class.php | 21 +++++++ .../type/user/UsernameConditionType.class.php | 56 +++++++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php new file mode 100644 index 00000000000..c3ab659ee58 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -0,0 +1,39 @@ + + * @since 6.2 + * + * @template T of IConditionType + */ +abstract class AbstractConditionProvider +{ + /** + * @var array + */ + protected array $conditionTypes = []; + + /** + * @param T $conditionType + */ + public function addCondition(IConditionType $conditionType): void + { + $this->conditionTypes[$conditionType->getIdentifier()] = $conditionType; + } + + /** + * @param T[] $conditionTypes + */ + public function addConditions(array $conditionTypes): void + { + foreach ($conditionTypes as $conditionType) { + $this->addCondition($conditionType); + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php new file mode 100644 index 00000000000..833e9fbf82e --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -0,0 +1,28 @@ + + * @since 6.2 + * + * @extends AbstractConditionProvider&IObjectConditionType> + */ +final class UserConditionProvider extends AbstractConditionProvider +{ + public function __construct() + { + $this->addConditions([ + new UsernameConditionType(), + ]); + // TODO PSR14-event + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php new file mode 100644 index 00000000000..b7fcadd9ea0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php @@ -0,0 +1,29 @@ + + * @since 6.2 + */ +interface IConditionType +{ + /** + * Returns the form field for this condition type. + */ + public function getFormField(string $id): IFormField; + + /** + * Returns the identifier of this condition type. + */ + public function getIdentifier(): string; + + /** + * Returns the label of this condition type. + */ + public function getLabel(): string; +} diff --git a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php new file mode 100644 index 00000000000..77e1c3a46a5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php @@ -0,0 +1,23 @@ + + * @since 6.2 + * + * @template T of DatabaseObjectList + */ +interface IDatabaseObjectListConditionType extends IConditionType +{ + /** + * Adds a filter to the given object list. + * + * @param T $objectList + */ + public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void; +} diff --git a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php new file mode 100644 index 00000000000..9d13ee63f46 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + * + * @template T of object + */ +interface IObjectConditionType extends IConditionType +{ + /** + * Returns `true` if the given object matches the filter, `false` otherwise. + * + * @param T $object + */ + public function match(object $object, float|int|string $filter): bool; +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php new file mode 100644 index 00000000000..4ea0389ba88 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php @@ -0,0 +1,56 @@ + + * @since 6.2 + * + * @implements IDatabaseObjectListConditionType> + * @implements IObjectConditionType + */ +final class UsernameConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): IFormField + { + return TextFormField::create($id); + } + + #[\Override] + public function getIdentifier(): string + { + return 'username'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.username'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void + { + $objectList->getConditionBuilder()->add( + $objectList->getDatabaseTableAlias() . '.username LIKE ?', + ['%' . $filter . '%'] + ); + } + + #[\Override] + public function match(object $object, float|int|string $filter): bool + { + return \str_contains($object->getUsername(), $filter); + } +} From 12055c4240dfee57f33e7ca4f7bbf92749222088 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 21 May 2025 09:05:27 +0200 Subject: [PATCH 02/82] Migrate user group assignment form to form builder --- .../acp/templates/userGroupAssignmentAdd.tpl | 66 +----- .../form/UserGroupAssignmentAddForm.class.php | 203 ++++-------------- .../UserGroupAssignmentEditForm.class.php | 95 ++------ 3 files changed, 56 insertions(+), 308 deletions(-) diff --git a/wcfsetup/install/files/acp/templates/userGroupAssignmentAdd.tpl b/wcfsetup/install/files/acp/templates/userGroupAssignmentAdd.tpl index cfd0d828d14..32b6b4a1bbd 100644 --- a/wcfsetup/install/files/acp/templates/userGroupAssignmentAdd.tpl +++ b/wcfsetup/install/files/acp/templates/userGroupAssignmentAdd.tpl @@ -17,70 +17,6 @@ -{include file='shared_formNotice'} - -
-
- -
-
- - {if $errorField == 'title'} - - {if $errorType == 'empty'} - {lang}wcf.global.form.error.empty{/lang} - {else} - {lang}wcf.acp.group.assignment.title.error.{$errorType}{/lang} - {/if} - - {/if} -
- - - -
-
- {htmlOptions name='groupID' id='groupID' options=$userGroups selected=$groupID} - {if $errorField == 'groupID'} - {if $errorType == 'noValidSelection'} - {lang}wcf.global.form.error.noValidSelection{/lang} - {else} - {lang}wcf.acp.group.assignment.groupID.error.{$errorType}{/lang} - {/if} - {/if} -
- - -
-
-
- -
-
- - {event name='dataFields'} -
- - {event name='sections'} - -
-
-

{lang}wcf.acp.group.assignment.conditions{/lang}

-

{lang}wcf.acp.group.assignment.conditions.description{/lang}

-
- - {if $errorField == 'conditions'} - {lang}wcf.acp.group.assignment.error.noConditions{/lang} - {/if} - - {include file='shared_userConditions'} -
- -
- - - {csrfToken} -
-
+{unsafe:$form->getHtml()} {include file='footer'} diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php index 8e037a1cb61..383656248d6 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php @@ -2,207 +2,82 @@ namespace wcf\acp\form; -use wcf\data\object\type\ObjectType; +use wcf\data\user\group\assignment\UserGroupAssignment; use wcf\data\user\group\assignment\UserGroupAssignmentAction; use wcf\data\user\group\UserGroup; -use wcf\form\AbstractForm; -use wcf\system\condition\ConditionHandler; -use wcf\system\exception\UserInputException; -use wcf\system\request\LinkHandler; -use wcf\system\user\group\assignment\UserGroupAssignmentHandler; -use wcf\system\WCF; -use wcf\util\StringUtil; +use wcf\form\AbstractFormBuilderForm; +use wcf\system\form\builder\container\FormContainer; +use wcf\system\form\builder\field\BooleanFormField; +use wcf\system\form\builder\field\SingleSelectionFormField; +use wcf\system\form\builder\field\TextFormField; /** * Shows the form to create a new automatic user group assignment. * - * @author Matthias Schmidt - * @copyright 2001-2019 WoltLab GmbH + * @author Olaf Braun, Matthias Schmidt + * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License + * + * @extends AbstractFormBuilderForm */ -class UserGroupAssignmentAddForm extends AbstractForm +class UserGroupAssignmentAddForm extends AbstractFormBuilderForm { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.group.assignment.add'; - /** - * list of grouped user group assignment condition object types - * @var ObjectType[][] - */ - public $conditions = []; - - /** - * id of the selected user group - * @var int - */ - public $groupID = 0; - - /** - * true if the automatic assignment is disabled - * @var int - */ - public $isDisabled = 0; - /** * @inheritDoc */ public $neededPermissions = ['admin.user.canManageGroupAssignment']; /** - * title of the user group assignment - * @var string - */ - public $title = ''; - - /** - * list of selectable user groups - * @var UserGroup[] + * @inheritDoc */ - public $userGroups = []; + public $objectActionClass = UserGroupAssignmentAction::class; /** * @inheritDoc */ - public function assignVariables() - { - parent::assignVariables(); + public $objectEditLinkController = UserGroupAssignmentEditForm::class; - WCF::getTPL()->assign([ - 'action' => 'add', - 'groupedObjectTypes' => $this->conditions, - 'groupID' => $this->groupID, - 'isDisabled' => $this->isDisabled, - 'title' => $this->title, - 'userGroups' => $this->userGroups, + #[\Override] + public function createForm() + { + parent::createForm(); + + $this->form->appendChildren([ + FormContainer::create('section') + ->appendChildren([ + TextFormField::create('title') + ->label('wcf.global.name') + ->maximumLength(255) + ->required(), + SingleSelectionFormField::create('groupID') + ->label('wcf.user.group') + ->required() + ->options($this->getUserGroups()), + BooleanFormField::create('isDisabled') + ->label('wcf.acp.group.assignment.isDisabled') + ->value(false), + ]), + // TODO add condition form container ]); } /** - * @inheritDoc + * @return array */ - public function readData() + private function getUserGroups(): array { - $this->userGroups = UserGroup::getSortedGroupsByType([], [ + $userGroups = UserGroup::getSortedGroupsByType([], [ UserGroup::EVERYONE, UserGroup::GUESTS, UserGroup::OWNER, UserGroup::USERS, ]); - foreach ($this->userGroups as $key => $userGroup) { - if (!$userGroup->isAccessible()) { - unset($this->userGroups[$key]); - } - } - - $this->conditions = UserGroupAssignmentHandler::getInstance()->getGroupedObjectTypes(); - - parent::readData(); - } - - /** - * @inheritDoc - */ - public function readFormParameters() - { - parent::readFormParameters(); - - if (isset($_POST['groupID'])) { - $this->groupID = \intval($_POST['groupID']); - } - if (isset($_POST['isDisabled'])) { - $this->isDisabled = 1; - } - if (isset($_POST['title'])) { - $this->title = StringUtil::trim($_POST['title']); - } - - foreach ($this->conditions as $conditions) { - /** @var ObjectType $condition */ - foreach ($conditions as $condition) { - $condition->getProcessor()->readFormParameters(); - } - } - } - - /** - * @inheritDoc - */ - public function save() - { - parent::save(); - - $this->objectAction = new UserGroupAssignmentAction([], 'create', [ - 'data' => \array_merge($this->additionalFields, [ - 'groupID' => $this->groupID, - 'isDisabled' => $this->isDisabled, - 'title' => $this->title, - ]), - ]); - $returnValues = $this->objectAction->executeAction(); - - // transform conditions array into one-dimensional array - $conditions = []; - foreach ($this->conditions as $groupedObjectTypes) { - $conditions = \array_merge($conditions, $groupedObjectTypes); - } - - ConditionHandler::getInstance()->createConditions($returnValues['returnValues']->assignmentID, $conditions); - - $this->saved(); - - // reset values - $this->groupID = 0; - $this->isDisabled = 0; - $this->title = ''; - - foreach ($this->conditions as $conditions) { - foreach ($conditions as $condition) { - $condition->getProcessor()->reset(); - } - } - - WCF::getTPL()->assign([ - 'success' => true, - 'objectEditLink' => LinkHandler::getInstance()->getControllerLink( - UserGroupAssignmentEditForm::class, - ['id' => $returnValues['returnValues']->assignmentID] - ), - ]); - } - - /** - * @inheritDoc - */ - public function validate() - { - parent::validate(); - - if (empty($this->title)) { - throw new UserInputException('title'); - } - if (\strlen($this->title) > 255) { - throw new UserInputException('title', 'tooLong'); - } - - if (!isset($this->userGroups[$this->groupID])) { - throw new UserInputException('groupID', 'noValidSelection'); - } - - $hasData = false; - foreach ($this->conditions as $conditions) { - foreach ($conditions as $condition) { - $condition->getProcessor()->validate(); - - if (!$hasData && $condition->getProcessor()->getData() !== null) { - $hasData = true; - } - } - } - if (!$hasData) { - throw new UserInputException('conditions'); - } + return \array_filter($userGroups, static fn ($userGroup) => $userGroup->isAccessible()); } } diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php index 952b7d452b5..d9f4aa4cac0 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php @@ -4,9 +4,6 @@ use wcf\acp\page\UserGroupAssignmentListPage; use wcf\data\user\group\assignment\UserGroupAssignment; -use wcf\data\user\group\assignment\UserGroupAssignmentAction; -use wcf\form\AbstractForm; -use wcf\system\condition\ConditionHandler; use wcf\system\exception\IllegalLinkException; use wcf\system\interaction\admin\UserGroupAssignmentInteractions; use wcf\system\interaction\StandaloneInteractionContextMenuComponent; @@ -16,8 +13,8 @@ /** * Shows the form to edit an existing automatic user group assignment. * - * @author Matthias Schmidt - * @copyright 2001-2019 WoltLab GmbH + * @author Olaf Braun, Matthias Schmidt + * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License */ class UserGroupAssignmentEditForm extends UserGroupAssignmentAddForm @@ -27,54 +24,10 @@ class UserGroupAssignmentEditForm extends UserGroupAssignmentAddForm */ public $activeMenuItem = 'wcf.acp.menu.link.group.assignment'; - /** - * edited automatic user group assignment - * @var UserGroupAssignment - */ - public $assignment; - - /** - * id of the edited automatic user group assignment - * @var int - */ - public $assignmentID = 0; - - /** - * @inheritDoc - */ - public function assignVariables() - { - parent::assignVariables(); - - WCF::getTPL()->assign([ - 'action' => 'edit', - 'assignment' => $this->assignment, - 'interactionContextMenu' => StandaloneInteractionContextMenuComponent::forContentHeaderButton( - new UserGroupAssignmentInteractions(), - $this->assignment, - LinkHandler::getInstance()->getControllerLink(UserGroupAssignmentListPage::class) - ), - ]); - } - /** * @inheritDoc */ - public function readData() - { - parent::readData(); - - if (empty($_POST)) { - $this->groupID = $this->assignment->groupID; - $this->title = $this->assignment->title; - - $conditions = $this->assignment->getConditions(); - foreach ($conditions as $condition) { - /** @noinspection PhpUndefinedMethodInspection */ - $this->conditions[$condition->getObjectType()->conditiongroup][$condition->objectTypeID]->getProcessor()->setData($condition); - } - } - } + public $formAction = 'edit'; /** * @inheritDoc @@ -83,11 +36,12 @@ public function readParameters() { parent::readParameters(); - if (isset($_REQUEST['id'])) { - $this->assignmentID = \intval($_REQUEST['id']); + if (!isset($_REQUEST['id'])) { + throw new IllegalLinkException(); } - $this->assignment = new UserGroupAssignment($this->assignmentID); - if (!$this->assignment->assignmentID) { + + $this->formObject = new UserGroupAssignment(\intval($_REQUEST['id'])); + if (!$this->formObject->assignmentID) { throw new IllegalLinkException(); } } @@ -95,33 +49,16 @@ public function readParameters() /** * @inheritDoc */ - public function save() + public function assignVariables() { - AbstractForm::save(); + parent::assignVariables(); - $this->objectAction = new UserGroupAssignmentAction([$this->assignment], 'update', [ - 'data' => \array_merge($this->additionalFields, [ - 'groupID' => $this->groupID, - 'isDisabled' => $this->isDisabled, - 'title' => $this->title, - ]), + WCF::getTPL()->assign([ + 'interactionContextMenu' => StandaloneInteractionContextMenuComponent::forContentHeaderButton( + new UserGroupAssignmentInteractions(), + $this->formObject, + LinkHandler::getInstance()->getControllerLink(UserGroupAssignmentListPage::class) + ), ]); - $this->objectAction->executeAction(); - - // transform conditions array into one-dimensional array - $conditions = []; - foreach ($this->conditions as $groupedObjectTypes) { - $conditions = \array_merge($conditions, $groupedObjectTypes); - } - - ConditionHandler::getInstance()->updateConditions( - $this->assignment->assignmentID, - $this->assignment->getConditions(), - $conditions - ); - - $this->saved(); - - WCF::getTPL()->assign('success', true); } } From 815a8dc2af9aaf1ab96767ab43f31f6b892796cb Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 21 May 2025 11:50:24 +0200 Subject: [PATCH 03/82] Implement condition form container --- .../shared_conditionFormContainer.tpl | 37 +++++++ .../shared_conditionRowFormFieldContainer.tpl | 25 +++++ .../Builder/Container/ConditionFormField.ts | 58 ++++++++++ .../Builder/Container/ConditionFormField.js | 42 +++++++ .../form/UserGroupAssignmentAddForm.class.php | 5 +- .../lib/action/ConditionAddAction.class.php | 104 ++++++++++++++++++ .../AbstractConditionProvider.class.php | 45 +++++++- .../provider/UserConditionProvider.class.php | 2 +- .../condition/type/IConditionType.class.php | 2 +- ...IDatabaseObjectListConditionType.class.php | 2 +- .../type/IObjectConditionType.class.php | 2 +- .../type/user/UsernameConditionType.class.php | 2 +- .../ConditionFormContainer.class.php | 85 ++++++++++++++ .../ConditionRowFormFieldContainer.class.php | 80 ++++++++++++++ .../install/files/style/ui/condition.scss | 20 ++++ wcfsetup/install/lang/de.xml | 1 + wcfsetup/install/lang/en.xml | 1 + 17 files changed, 506 insertions(+), 7 deletions(-) create mode 100644 com.woltlab.wcf/templates/shared_conditionFormContainer.tpl create mode 100644 com.woltlab.wcf/templates/shared_conditionRowFormFieldContainer.tpl create mode 100644 ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js create mode 100644 wcfsetup/install/files/lib/action/ConditionAddAction.class.php create mode 100644 wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php create mode 100644 wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php create mode 100644 wcfsetup/install/files/style/ui/condition.scss diff --git a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl new file mode 100644 index 00000000000..f9b04860a01 --- /dev/null +++ b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl @@ -0,0 +1,37 @@ +
getClasses()|empty} class="{implode from=$container->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* + *}{foreach from=$container->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* + *}{if !$container->checkDependencies()} style="display: none;"{/if}{* +*}> + {if $container->getLabel() !== null} + {if $container->getDescription() !== null} +
+

{unsafe:$container->getLabel()}{if $container->markAsRequired()} *{/if}

+

{unsafe:$container->getDescription()}

+
+ {else} +

{unsafe:$container->getLabel()}{if $container->markAsRequired()} *{/if}

+ {/if} + {/if} + +
+ {include file='shared_formContainerChildren'} +
+ + +
+ +{include file='shared_formContainerDependencies'} + + diff --git a/com.woltlab.wcf/templates/shared_conditionRowFormFieldContainer.tpl b/com.woltlab.wcf/templates/shared_conditionRowFormFieldContainer.tpl new file mode 100644 index 00000000000..9438c1d9de6 --- /dev/null +++ b/com.woltlab.wcf/templates/shared_conditionRowFormFieldContainer.tpl @@ -0,0 +1,25 @@ +
getClasses()|empty} class="{implode from=$container->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* + *}{foreach from=$container->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* + *}{if !$container->checkDependencies()} style="display: none;"{/if}{* +*}> + + + {foreach from=$container item='field'} + {if $field->isAvailable()} +
getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* + *}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* + *}{if !$field->checkDependencies()} style="display: none;"{/if}> + {unsafe:$field->getFieldHtml()} + + {include file='shared_formFieldErrors'} + {include file='shared_formFieldDependencies'} + {include file='shared_formFieldDataHandler'} +
+ {/if} + {/foreach} + +
diff --git a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts new file mode 100644 index 00000000000..4f32f8123bb --- /dev/null +++ b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts @@ -0,0 +1,58 @@ +/** + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ + +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; +import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; +import { insertHtml } from "WoltLabSuite/Core/Dom/Util"; +import { unescapeHTML } from "WoltLabSuite/Core/StringUtil"; +import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector"; + +interface ConditionAddResponse { + field: string; + conditionType: string; +} + +export class ConditionFormField { + readonly #containerId: string; + readonly #container: HTMLElement; + readonly #button: HTMLButtonElement; + #index: number; + + constructor(containerId: string, endpoint: string, index: number) { + this.#containerId = containerId; + this.#index = index; + this.#container = document.getElementById(`${containerId}Conditions`) as HTMLElement; + + this.#button = document.getElementById(`${containerId}AddCondition`) as HTMLButtonElement; + this.#button?.addEventListener( + "click", + promiseMutex(async () => { + await this.#showConditionAddDialog(endpoint); + }), + ); + + wheneverFirstSeen(`#${containerId}Container .condition-remove`, (element: HTMLButtonElement) => { + element.addEventListener("click", () => { + element.parentElement?.remove(); + }); + }); + } + + async #showConditionAddDialog(endpoint: string) { + const url = new URL(unescapeHTML(endpoint)); + url.searchParams.set("containerId", this.#containerId); + url.searchParams.set("index", this.#index.toString()); + + const { ok, result } = await dialogFactory().usingFormBuilder().fromEndpoint(url.toString()); + + if (ok) { + this.#index++; + + insertHtml(result.field, this.#container, "append"); + } + } +} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js new file mode 100644 index 00000000000..facc2c0622e --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js @@ -0,0 +1,42 @@ +/** + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ +define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Dom/Util", "WoltLabSuite/Core/StringUtil", "WoltLabSuite/Core/Helper/Selector"], function (require, exports, PromiseMutex_1, Dialog_1, Util_1, StringUtil_1, Selector_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.ConditionFormField = void 0; + class ConditionFormField { + #containerId; + #container; + #button; + #index; + constructor(containerId, endpoint, index) { + this.#containerId = containerId; + this.#index = index; + this.#container = document.getElementById(`${containerId}Conditions`); + this.#button = document.getElementById(`${containerId}AddCondition`); + this.#button?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { + await this.#showConditionAddDialog(endpoint); + })); + (0, Selector_1.wheneverFirstSeen)(`#${containerId}Container .condition-remove`, (element) => { + element.addEventListener("click", () => { + element.parentElement?.remove(); + }); + }); + } + async #showConditionAddDialog(endpoint) { + const url = new URL((0, StringUtil_1.unescapeHTML)(endpoint)); + url.searchParams.set("containerId", this.#containerId); + url.searchParams.set("index", this.#index.toString()); + const { ok, result } = await (0, Dialog_1.dialogFactory)().usingFormBuilder().fromEndpoint(url.toString()); + if (ok) { + this.#index++; + (0, Util_1.insertHtml)(result.field, this.#container, "append"); + } + } + } + exports.ConditionFormField = ConditionFormField; +}); diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php index 383656248d6..6929fa248ce 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentAddForm.class.php @@ -6,6 +6,8 @@ use wcf\data\user\group\assignment\UserGroupAssignmentAction; use wcf\data\user\group\UserGroup; use wcf\form\AbstractFormBuilderForm; +use wcf\system\condition\provider\UserConditionProvider; +use wcf\system\form\builder\container\ConditionFormContainer; use wcf\system\form\builder\container\FormContainer; use wcf\system\form\builder\field\BooleanFormField; use wcf\system\form\builder\field\SingleSelectionFormField; @@ -62,7 +64,8 @@ public function createForm() ->label('wcf.acp.group.assignment.isDisabled') ->value(false), ]), - // TODO add condition form container + ConditionFormContainer::create() + ->conditionProvider(new UserConditionProvider()), ]); } diff --git a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php new file mode 100644 index 00000000000..6e28cf03c3f --- /dev/null +++ b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php @@ -0,0 +1,104 @@ + + * @since 6.3 + */ +final class ConditionAddAction implements RequestHandlerInterface +{ + #[\Override] + public function handle(ServerRequestInterface $request): ResponseInterface + { + $parameters = Helper::mapQueryParameters( + $request->getQueryParams(), + <<<'EOT' + array { + provider: non-empty-string, + containerId: non-empty-string, + index: int, + } + EOT + ); + + if (!\is_subclass_of($parameters['provider'], AbstractConditionProvider::class)) { + throw new UserInputException('provider', 'invalid'); + } + + /** @var AbstractConditionProvider $provider */ + $provider = new $parameters['provider'](); + + $form = $this->getForm($provider); + + if ($request->getMethod() === 'GET') { + return $form->toResponse(); + } elseif ($request->getMethod() === 'POST') { + $response = $form->validateRequest($request); + if ($response !== null) { + return $response; + } + + $data = $form->getData()['data']; + $condition = $provider->getConditionByIdentifier($data['conditionType']); + \assert($condition instanceof IConditionType); + + $document = FormDocument::create('tmpForm'); + + return new JsonResponse([ + 'result' => [ + 'field' => $provider->getConditionFormField($data['conditionType'], $parameters['containerId'], $parameters['index']) + ->parent($document) + ->getHtml(), + 'conditionType' => $data['conditionType'], + ], + ]); + } else { + throw new \LogicException('Unreachable'); + } + } + + /** + * @param AbstractConditionProvider $provider + */ + private function getForm(AbstractConditionProvider $provider): Psr15DialogForm + { + $form = new Psr15DialogForm( + self::class, + WCF::getLanguage()->get('wcf.condition.add') + ); + + $form->appendChild( + SingleSelectionFormField::create('conditionType') + ->label('wcf.condition.condition') + ->filterable() + ->required() + ->options( + \array_map( + static fn (IConditionType $conditionType) => WCF::getLanguage()->get($conditionType->getLabel()), + $provider->getConditionTypes() + ) + ) + ); + + $form->markRequiredFields(false); + $form->build(); + + return $form; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index c3ab659ee58..9120ac1c9ff 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -3,12 +3,13 @@ namespace wcf\system\condition\provider; use wcf\system\condition\type\IConditionType; +use wcf\system\form\builder\container\ConditionRowFormFieldContainer; /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 * * @template T of IConditionType */ @@ -20,6 +21,8 @@ abstract class AbstractConditionProvider protected array $conditionTypes = []; /** + * Adds a condition type to this provider. + * * @param T $conditionType */ public function addCondition(IConditionType $conditionType): void @@ -28,6 +31,8 @@ public function addCondition(IConditionType $conditionType): void } /** + * Adds multiple condition types to this provider. + * * @param T[] $conditionTypes */ public function addConditions(array $conditionTypes): void @@ -36,4 +41,42 @@ public function addConditions(array $conditionTypes): void $this->addCondition($conditionType); } } + + final public function getConditionFormField(string $identifier, string $containerId, int $index): ConditionRowFormFieldContainer + { + $id = "{$containerId}_{$identifier}_{$index}"; + $condition = $this->getConditionByIdentifier($identifier); + if ($condition === null) { + throw new \InvalidArgumentException("Condition type with identifier '{$identifier}' not found."); + } + + return ConditionRowFormFieldContainer::create("{$containerId}_{$identifier}_{$index}_container") + ->containerId($containerId) + ->conditionType($identifier) + ->conditionIndex($index) + ->label($condition->getLabel()) + ->appendChild( + $condition->getFormField($id), + ); + } + + /** + * Returns the condition type with the given identifier. + * + * @return T|null + */ + public function getConditionByIdentifier(string $identifier): ?IConditionType + { + return $this->conditionTypes[$identifier] ?? null; + } + + /** + * Returns all condition types of this provider. + * + * @return array + */ + public function getConditionTypes(): array + { + return $this->conditionTypes; + } } diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 833e9fbf82e..aaf2e5c963b 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -12,7 +12,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 * * @extends AbstractConditionProvider&IObjectConditionType> */ diff --git a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php index b7fcadd9ea0..a18b242b1ff 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php @@ -8,7 +8,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 */ interface IConditionType { diff --git a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php index 77e1c3a46a5..5956cb4d194 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php @@ -8,7 +8,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 * * @template T of DatabaseObjectList */ diff --git a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php index 9d13ee63f46..747af7679ca 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php @@ -6,7 +6,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 * * @template T of object */ diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php index 4ea0389ba88..e996b422f4d 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php @@ -14,7 +14,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 * * @implements IDatabaseObjectListConditionType> * @implements IObjectConditionType diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php new file mode 100644 index 00000000000..6aa2889c46f --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -0,0 +1,85 @@ + + * @since 6.3 + * + * @phpstan-type ConditionProvider AbstractConditionProvider + */ +final class ConditionFormContainer extends FormContainer +{ + use TDefaultIdFormField; + + /** + * @inheritDoc + */ + protected $templateName = 'shared_conditionFormContainer'; + + /** + * @var ConditionProvider + */ + protected AbstractConditionProvider $conditionProvider; + + public function __construct() + { + parent::__construct(); + $this->label("wcf.form.field.condition"); + } + + #[\Override] + protected static function getDefaultId(): string + { + return 'condition'; + } + + #[\Override] + public function isAvailable(): bool + { + return isset($this->conditionProvider); + } + + /** + * @param ConditionProvider $conditionProvider + * + * @return self + */ + public function conditionProvider(AbstractConditionProvider $conditionProvider): self + { + $this->conditionProvider = $conditionProvider; + + return $this; + } + + /** + * @return ConditionProvider + */ + public function getConditionProvider(): AbstractConditionProvider + { + if (!isset($this->conditionProvider)) { + throw new \BadMethodCallException( + "Condition provider has not been set yet for node '{$this->getId()}'." + ); + } + + return $this->conditionProvider; + } + + public function getConditionProviderClass(): string + { + if (!isset($this->conditionProvider)) { + throw new \BadMethodCallException( + "Condition provider has not been set yet for node '{$this->getId()}'." + ); + } + + return $this->conditionProvider::class; + } +} diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php new file mode 100644 index 00000000000..50b8e50301b --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php @@ -0,0 +1,80 @@ + + * @since 6.2 + */ +final class ConditionRowFormFieldContainer extends FormContainer +{ + /** + * @inheritDoc + */ + protected $templateName = 'shared_conditionRowFormFieldContainer'; + + private string $conditionType; + private string $containerId; + private int $conditionIndex = 0; + + public function __construct() + { + $this->addClass("condition-row"); + } + + /** + * Sets the condition index for the container. + */ + public function conditionIndex(int $conditionIndex): self + { + $this->conditionIndex = $conditionIndex; + + return $this; + } + + /** + * Retrieves the condition index of the container. + */ + public function getConditionIndex(): int + { + return $this->conditionIndex; + } + + /** + * Sets the condition type for the container. + */ + public function conditionType(string $conditionType): self + { + $this->conditionType = $conditionType; + + return $this; + } + + /** + * Retrieves the condition type of the container. + */ + public function getConditionType(): string + { + return $this->conditionType; + } + + /** + * Sets the container ID. + */ + public function containerId(string $containerId): self + { + $this->containerId = $containerId; + + return $this; + } + + /** + * Retrieves the container ID. + */ + public function getContainerId(): string + { + return $this->containerId; + } +} diff --git a/wcfsetup/install/files/style/ui/condition.scss b/wcfsetup/install/files/style/ui/condition.scss new file mode 100644 index 00000000000..6cd7c520c68 --- /dev/null +++ b/wcfsetup/install/files/style/ui/condition.scss @@ -0,0 +1,20 @@ +.conditions { + display: flex; + flex-direction: column; +} + +.condition-row { + display: inline-flex; +} + +.condition-remove { + flex: 0; +} + +.condition-label { + flex: 1; +} + +.condition-field { + flex: 1; +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index a0adbf31d3d..f3cb066aca5 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -4150,6 +4150,7 @@ Dateianhänge: 1}{#$minimum} Dateien{else}eine Datei{/if} hochladen.]]> 1}{#$maximum} Dateien{else}eine Datei{/if} hochladen.]]> + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index f3b97d8f561..21107065cd7 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -4096,6 +4096,7 @@ Attachments: 1}{#$minimum} files{else}one file{/if}.]]> 1}{#$maximum} files{else}one file{/if}.]]> + From 985fbd19f54c5d207650e7e75b2c731c0ae5c003 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 21 May 2025 12:39:48 +0200 Subject: [PATCH 04/82] Load and save conditions --- .../shared_conditionFormContainer.tpl | 15 ++-- .../update_com.woltlab.wcf_6.3_step1.php | 19 +++++ .../lib/action/ConditionAddAction.class.php | 2 +- .../AbstractConditionProvider.class.php | 17 ++-- .../ConditionFormContainer.class.php | 77 ++++++++++++++++++- 5 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php diff --git a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl index f9b04860a01..3d61a1a508f 100644 --- a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl +++ b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl @@ -26,12 +26,11 @@ {include file='shared_formContainerDependencies'} diff --git a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php new file mode 100644 index 00000000000..8e18c78e57a --- /dev/null +++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php @@ -0,0 +1,19 @@ + + */ + +use wcf\system\database\table\column\MediumtextDatabaseTableColumn; +use wcf\system\database\table\PartialDatabaseTable; + +return [ + PartialDatabaseTable::create('wcf1_user_group_assignment') + ->columns([ + MediumtextDatabaseTableColumn::create('conditions'), + ]), +]; diff --git a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php index 6e28cf03c3f..a804c6a2b75 100644 --- a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php +++ b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php @@ -62,7 +62,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface return new JsonResponse([ 'result' => [ - 'field' => $provider->getConditionFormField($data['conditionType'], $parameters['containerId'], $parameters['index']) + 'field' => $provider->getConditionFormField($parameters['containerId'], $data['conditionType'], $parameters['index']) ->parent($document) ->getHtml(), 'conditionType' => $data['conditionType'], diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index 9120ac1c9ff..20cc16f4587 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -42,22 +42,29 @@ public function addConditions(array $conditionTypes): void } } - final public function getConditionFormField(string $identifier, string $containerId, int $index): ConditionRowFormFieldContainer + final public function getFieldId(string $containerId, string $identifier, int $index): string + { + return "{$containerId}_{$identifier}_{$index}"; + } + + final public function getConditionFormField(string $containerId, string $identifier, int $index, null|float|int|string $value = null): ConditionRowFormFieldContainer { - $id = "{$containerId}_{$identifier}_{$index}"; $condition = $this->getConditionByIdentifier($identifier); if ($condition === null) { throw new \InvalidArgumentException("Condition type with identifier '{$identifier}' not found."); } + $formField = $condition->getFormField($this->getFieldId($containerId, $identifier, $index)); + if ($value !== null) { + $formField->value($value); + } + return ConditionRowFormFieldContainer::create("{$containerId}_{$identifier}_{$index}_container") ->containerId($containerId) ->conditionType($identifier) ->conditionIndex($index) ->label($condition->getLabel()) - ->appendChild( - $condition->getFormField($id), - ); + ->appendChild($formField); } /** diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php index 6aa2889c46f..f970e7809c0 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -2,9 +2,13 @@ namespace wcf\system\form\builder\container; +use wcf\data\IStorableObject; use wcf\system\condition\provider\AbstractConditionProvider; use wcf\system\condition\type\IConditionType; +use wcf\system\form\builder\data\processor\CustomFormDataProcessor; use wcf\system\form\builder\field\TDefaultIdFormField; +use wcf\system\form\builder\IFormDocument; +use wcf\util\JSON; /** * @author Olaf Braun @@ -22,6 +26,7 @@ final class ConditionFormContainer extends FormContainer * @inheritDoc */ protected $templateName = 'shared_conditionFormContainer'; + private int $lastConditionIndex = 0; /** * @var ConditionProvider @@ -37,7 +42,7 @@ public function __construct() #[\Override] protected static function getDefaultId(): string { - return 'condition'; + return 'conditions'; } #[\Override] @@ -46,6 +51,71 @@ public function isAvailable(): bool return isset($this->conditionProvider); } + #[\Override] + public function readValues(): static + { + $prefixId = $this->getPrefixedId(); + + if ($this->getDocument()->hasRequestData($prefixId)) { + $conditions = $this->getDocument()->getRequestData($prefixId); + + foreach ($conditions as $index => $identifier) { + $this->appendCondition($identifier, $index); + + $this->lastConditionIndex = \max($this->lastConditionIndex, $index); + } + } + + return parent::readValues(); + } + + #[\Override] + public function updatedObject(array $data, IStorableObject $object, $loadValues = true) + { + if ($loadValues && isset($data[$this->getPrefixedId()])) { + $conditions = JSON::decode($data[$this->getPrefixedId()]); + + foreach ($conditions as $index => $condition) { + $this->appendCondition($condition['identifier'], $index, $condition['value']); + + $this->lastConditionIndex = \max($this->lastConditionIndex, $index); + } + } + + return $this; + } + + private function appendCondition(string $identifier, int $index, null|float|int|string $value = null): void + { + $prefixId = $this->getPrefixedId(); + + $formField = $this->getConditionProvider()->getConditionFormField($prefixId, $identifier, $index, $value); + $this->appendChild($formField); + + $fieldId = $this->getConditionProvider()->getFieldId($prefixId, $identifier, $index); + + $this->getDocument()->getDataHandler()->addProcessor( + new CustomFormDataProcessor( + "{$fieldId}DataProcessor", + static function (IFormDocument $document, array $parameters) use ($prefixId, $identifier, $fieldId) { + $conditions = isset($parameters['data'][$prefixId]) ? JSON::decode($parameters['data'][$prefixId]) : []; + + if (isset($parameters['data'][$fieldId])) { + $conditions[] = [ + "identifier" => $identifier, + "value" => $parameters['data'][$fieldId], + ]; + unset($parameters['data'][$fieldId]); + } + + $parameters['data'][$prefixId] = JSON::encode($conditions); + + return $parameters; + } + ) + ); + } + /** * @param ConditionProvider $conditionProvider * @@ -82,4 +152,9 @@ public function getConditionProviderClass(): string return $this->conditionProvider::class; } + + public function getLastConditionIndex(): int + { + return $this->lastConditionIndex; + } } From 234679b4db6e7999e8ea72ded4a347d1d7179c3b Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 22 May 2025 09:59:55 +0200 Subject: [PATCH 05/82] Implement condition date form field --- .../templates/shared_conditionFormField.tpl | 5 + .../shared_dateConditionFormField.tpl | 13 ++ .../provider/UserConditionProvider.class.php | 2 + .../RegistrationDateConditionType.class.php | 65 +++++++++ .../type/user/UsernameConditionType.class.php | 10 +- .../ConditionFormContainer.class.php | 3 +- .../AbstractConditionFormField.class.php | 130 ++++++++++++++++++ .../field/DateConditionFormField.class.php | 67 +++++++++ 8 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 com.woltlab.wcf/templates/shared_conditionFormField.tpl create mode 100644 com.woltlab.wcf/templates/shared_dateConditionFormField.tpl create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php create mode 100644 wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php diff --git a/com.woltlab.wcf/templates/shared_conditionFormField.tpl b/com.woltlab.wcf/templates/shared_conditionFormField.tpl new file mode 100644 index 00000000000..667931f3066 --- /dev/null +++ b/com.woltlab.wcf/templates/shared_conditionFormField.tpl @@ -0,0 +1,5 @@ + diff --git a/com.woltlab.wcf/templates/shared_dateConditionFormField.tpl b/com.woltlab.wcf/templates/shared_dateConditionFormField.tpl new file mode 100644 index 00000000000..ecab70dcf8b --- /dev/null +++ b/com.woltlab.wcf/templates/shared_dateConditionFormField.tpl @@ -0,0 +1,13 @@ +{include file="shared_conditionFormField"} + +getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if} + {if $field->isAutofocused()} autofocus{/if} + {if $field->isRequired()} required{/if} + {if $field->isImmutable()} disabled{/if} + {foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach} +> diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index aaf2e5c963b..2d4b4eb702e 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -6,6 +6,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; +use wcf\system\condition\type\user\RegistrationDateConditionType; use wcf\system\condition\type\user\UsernameConditionType; /** @@ -22,6 +23,7 @@ public function __construct() { $this->addConditions([ new UsernameConditionType(), + new RegistrationDateConditionType(), ]); // TODO PSR14-event } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php new file mode 100644 index 00000000000..f65150b9277 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php @@ -0,0 +1,65 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType> + * @implements IObjectConditionType + */ +final class RegistrationDateConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): AbstractConditionFormField + { + return DateConditionFormField::create($id) + ->conditions(\array_combine($this->getConditions(), $this->getConditions())) + ->nullable() + ->supportTime(); + } + + #[\Override] + public function getIdentifier(): string + { + return 'registrationDate'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.registrationDate'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void + { + // TODO + } + + #[\Override] + public function match(object $object, float|int|string $filter): bool + { + // TODO + return false; + } + + /** + * @return string[] + */ + private function getConditions(): array + { + return [">", "<", ">=", "<="]; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php index e996b422f4d..705c1a9faac 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php @@ -7,7 +7,6 @@ use wcf\data\user\UserList; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\IFormField; use wcf\system\form\builder\field\TextFormField; /** @@ -16,15 +15,18 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType> + * @implements IDatabaseObjectListConditionType> * @implements IObjectConditionType */ final class UsernameConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): IFormField + public function getFormField(string $id): TextFormField { - return TextFormField::create($id); + // TODO supports beginns with, ends with and contains + return TextFormField::create($id) + ->removeFieldClass('long') + ->addFieldClass('medium'); } #[\Override] diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php index f970e7809c0..a71d80216d8 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -105,9 +105,10 @@ static function (IFormDocument $document, array $parameters) use ($prefixId, $id "identifier" => $identifier, "value" => $parameters['data'][$fieldId], ]; - unset($parameters['data'][$fieldId]); } + unset($parameters['data'][$fieldId]); + $parameters['data'][$prefixId] = JSON::encode($conditions); return $parameters; diff --git a/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php new file mode 100644 index 00000000000..dd27be80014 --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php @@ -0,0 +1,130 @@ + + * @since 6.3 + */ +abstract class AbstractConditionFormField extends AbstractFormField implements IImmutableFormField +{ + use TImmutableFormField; + + /** + * @var array + */ + protected array $conditions = []; + + #[\Override] + public function getSaveValue() + { + if ($this instanceof INullableFormField && $this->isNullable() && !$this->getValue()) { + return; + } + + if (!\is_array($this->value)) { + return \serialize([]); + } + + return \serialize($this->value); + } + + #[\Override] + public function validate() + { + if ($this->isRequired() && !\is_array($this->value)) { + $this->addValidationError(new FormFieldValidationError('empty')); + + return; + } + + if ($this instanceof INullableFormField && $this->value === null) { + return; + } + + if (!\array_key_exists($this->getCondition(), $this->conditions)) { + $this->addValidationError( + new FormFieldValidationError( + 'invalidValue', + 'wcf.global.form.error.noValidSelection' + ) + ); + } + } + + #[\Override] + public function value($value) + { + $value = @\unserialize($value); + if (!\is_array($value)) { + $value = []; + } + + return parent::value($value); + } + + #[\Override] + public function getValue() + { + return \is_array($this->value) ? $this->value['value'] : null; + } + + #[\Override] + final public function readValue() + { + if ($this->getDocument()->hasRequestData("{$this->getPrefixedId()}_condition") && $this->hasFieldValue()) { + $condition = $this->getDocument()->getRequestData("{$this->getPrefixedId()}_condition"); + + $this->value = [ + 'condition' => $condition, + 'value' => $this->getFieldValue(), + ]; + } + + return $this; + } + + /** + * Sets the conditions for this form field. + * + * @param array $conditions + */ + public function conditions(array $conditions): static + { + $this->conditions = $conditions; + + return $this; + } + + /** + * Returns the conditions for this form field. + * + * @return array + */ + public function getConditions(): array + { + return $this->conditions; + } + + /** + * Returns the selected condition for this form field. + */ + public function getCondition(): string + { + return $this->value['condition'] ?? ''; + } + + protected function getFieldValue(): mixed + { + return $this->getDocument()->getRequestData($this->getPrefixedId()); + } + + protected function hasFieldValue(): bool + { + return $this->getDocument()->hasRequestData($this->getPrefixedId()); + } +} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php new file mode 100644 index 00000000000..d555211e4b6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php @@ -0,0 +1,67 @@ + + * @since 6.3 + */ +final class DateConditionFormField extends AbstractConditionFormField implements + IAttributeFormField, + IAutoFocusFormField, + ICssClassFormField, + INullableFormField +{ + use TAttributeFormField; + use TAutoFocusFormField; + use TCssClassFormField; + use TNullableFormField; + + public const DATE_FORMAT = 'Y-m-d'; + public const TIME_FORMAT = 'Y-m-d\TH:i:sP'; + + /** + * is `true` if not only the date, but also the time can be set + */ + protected bool $supportsTime = false; + /** + * @inheritDoc + */ + protected $templateName = 'shared_dateConditionFormField'; + + #[\Override] + public function validate() + { + // TODO validate date value + parent::validate(); + } + + /** + * Sets if not only the date, but also the time can be set. + */ + public function supportTime(bool $supportsTime = true): self + { + if ($this->value !== null) { + throw new \BadFunctionCallException( + "After a value has been set, time support cannot be changed for field '{$this->getId()}'." + ); + } + + $this->supportsTime = $supportsTime; + + return $this; + } + + /** + * Returns `true` if not only the date, but also the time can be set, and + * returns `false` otherwise. + * + * By default, the time cannot be set. + */ + public function supportsTime(): bool + { + return $this->supportsTime; + } +} From abc4eb6c1ed80fcae92774419e7ad45e8d57c468 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 23 May 2025 10:37:37 +0200 Subject: [PATCH 06/82] Use the new condition system in the user group assignment handler --- .../assignment/UserGroupAssignment.class.php | 14 ++-- .../condition/ConditionHandler.class.php | 41 +++++++++++- .../type/AbstractConditionType.class.php | 22 +++++++ .../condition/type/IConditionType.class.php | 5 ++ ...IDatabaseObjectListConditionType.class.php | 2 +- .../type/IObjectConditionType.class.php | 2 +- .../RegistrationDateConditionType.class.php | 65 +++++++++++++++++-- .../type/user/UsernameConditionType.class.php | 11 ++-- .../field/DateConditionFormField.class.php | 2 + .../UserGroupAssignmentHandler.class.php | 35 +++------- 10 files changed, 150 insertions(+), 49 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/type/AbstractConditionType.class.php diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php index 476227afedc..3f34787f0af 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php @@ -2,11 +2,11 @@ namespace wcf\data\user\group\assignment; -use wcf\data\condition\Condition; use wcf\data\DatabaseObject; use wcf\data\user\group\UserGroup; use wcf\system\condition\ConditionHandler; use wcf\system\request\IRouteController; +use wcf\util\JSON; /** * Represents an automatic assignment to a user group. @@ -19,20 +19,20 @@ * @property-read int $groupID id of the user group to which users are automatically assigned * @property-read string $title title of the automatic user group assignment * @property-read int $isDisabled is `1` if the user group assignment is disabled and thus not checked for automatic assignments, otherwise `0` + * @property-read string $conditions JSON-encoded string containing the conditions of the automatic user group assignment + * + * @phpstan-import-type ConditionValue from ConditionHandler */ class UserGroupAssignment extends DatabaseObject implements IRouteController { /** * Returns the conditions of the automatic assignment to a user group. * - * @return Condition[] + * @return array{identifier: string, value: ConditionValue}[] */ - public function getConditions() + public function getConditions(): array { - return ConditionHandler::getInstance()->getConditions( - 'com.woltlab.wcf.condition.userGroupAssignment', - $this->assignmentID - ); + return JSON::decode($this->conditions); } /** diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index e4d17cbc09b..2f1dfdadf75 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -8,17 +8,21 @@ use wcf\data\object\type\ObjectType; use wcf\data\object\type\ObjectTypeCache; use wcf\system\cache\builder\ConditionCacheBuilder; +use wcf\system\condition\provider\AbstractConditionProvider; +use wcf\system\condition\type\IConditionType; use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; /** * Handles general condition-related matters. * - * @author Matthias Schmidt - * @copyright 2001-2019 WoltLab GmbH + * @author Olaf Braun, Matthias Schmidt + * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License + * + * @phpstan-type ConditionValue float|int|string */ -class ConditionHandler extends SingletonFactory +final class ConditionHandler extends SingletonFactory { /** * list of available conditions grouped by the id of the related condition @@ -139,4 +143,35 @@ public function updateConditions($objectID, array $oldConditions, array $conditi // create new conditions $this->createConditions($objectID, $conditionObjectTypes); } + + /** + * Returns the list of conditions with assigned filter for the condition provider and stored condition-values. + * + * @template T of IConditionType + * @param AbstractConditionProvider $provider + * @param array{identifier: string, value: ConditionValue}[] $conditions + * + * @return T[] + */ + public function getConditionsWithFilter(AbstractConditionProvider $provider, array $conditions): array + { + $result = []; + foreach ($conditions as $condition) { + $_conditionType = $provider->getConditionByIdentifier($condition['identifier']); + if ($_conditionType === null) { + if (ENABLE_DEBUG_MODE && ENABLE_DEVELOPER_TOOLS) { + throw new \InvalidArgumentException("Condition type with identifier '{$condition['identifier']}' not found."); + } + + continue; + } + + $conditionType = clone $_conditionType; + $conditionType->setFilter($condition['value']); + + $result[] = $conditionType; + } + + return $result; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/AbstractConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/AbstractConditionType.class.php new file mode 100644 index 00000000000..17de167ddaf --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/AbstractConditionType.class.php @@ -0,0 +1,22 @@ + + * @since 6.2 + */ +abstract class AbstractConditionType implements IConditionType +{ + protected float|int|string $filter; + + /** + * @inheritDoc + */ + public function setFilter(float|int|string $filter): void + { + $this->filter = $filter; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php index a18b242b1ff..cd9700288e1 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php @@ -26,4 +26,9 @@ public function getIdentifier(): string; * Returns the label of this condition type. */ public function getLabel(): string; + + /** + * Set the filter value for this condition type. + */ + public function setFilter(float|int|string $filter): void; } diff --git a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php index 5956cb4d194..cef10991e96 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php @@ -19,5 +19,5 @@ interface IDatabaseObjectListConditionType extends IConditionType * * @param T $objectList */ - public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void; + public function applyFilter(DatabaseObjectList $objectList): void; } diff --git a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php index 747af7679ca..fda544e8d35 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php @@ -17,5 +17,5 @@ interface IObjectConditionType extends IConditionType * * @param T $object */ - public function match(object $object, float|int|string $filter): bool; + public function match(object $object): bool; } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php index f65150b9277..ec8f9f10481 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateConditionType.class.php @@ -5,6 +5,7 @@ use wcf\data\DatabaseObjectList; use wcf\data\user\User; use wcf\data\user\UserList; +use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\AbstractConditionFormField; @@ -19,7 +20,7 @@ * @implements IDatabaseObjectListConditionType> * @implements IObjectConditionType */ -final class RegistrationDateConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class RegistrationDateConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): AbstractConditionFormField @@ -43,16 +44,68 @@ public function getLabel(): string } #[\Override] - public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void + public function applyFilter(DatabaseObjectList $objectList): void { - // TODO + ["condition" => $condition, "time" => $time] = $this->getParsedFilter(); + + switch ($condition) { + case ">": + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.registrationDate > ?", + [$time] + ); + break; + case "<": + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.registrationDate < ?", + [$time] + ); + break; + case ">=": + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.registrationDate >= ?", + [$time] + ); + break; + case "<=": + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.registrationDate <= ?", + [$time] + ); + break; + } } #[\Override] - public function match(object $object, float|int|string $filter): bool + public function match(object $object): bool + { + ["condition" => $condition, "time" => $time] = $this->getParsedFilter(); + + return match ($condition) { + ">" => $object->registrationDate > $time, + "<" => $object->registrationDate < $time, + ">=" => $object->registrationDate >= $time, + "<=" => $object->registrationDate <= $time, + default => false, + }; + } + + /** + * @return array{condition: string, time: int} + */ + private function getParsedFilter(): array { - // TODO - return false; + ["condition" => $condition, "value" => $filter] = @\unserialize($this->filter); + $dateTime = \DateTime::createFromFormat( + DateConditionFormField::TIME_FORMAT, + $filter, + new \DateTimeZone(TIMEZONE), + ); + + return [ + 'condition' => $condition, + 'time' => $dateTime->getTimestamp(), + ]; } /** diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php index 705c1a9faac..b57f82d9a68 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UsernameConditionType.class.php @@ -5,6 +5,7 @@ use wcf\data\DatabaseObjectList; use wcf\data\user\User; use wcf\data\user\UserList; +use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\TextFormField; @@ -18,7 +19,7 @@ * @implements IDatabaseObjectListConditionType> * @implements IObjectConditionType */ -final class UsernameConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UsernameConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): TextFormField @@ -42,17 +43,17 @@ public function getLabel(): string } #[\Override] - public function applyFilter(DatabaseObjectList $objectList, float|int|string $filter): void + public function applyFilter(DatabaseObjectList $objectList): void { $objectList->getConditionBuilder()->add( $objectList->getDatabaseTableAlias() . '.username LIKE ?', - ['%' . $filter . '%'] + ['%' . $this->filter . '%'] ); } #[\Override] - public function match(object $object, float|int|string $filter): bool + public function match(object $object): bool { - return \str_contains($object->getUsername(), $filter); + return \str_contains($object->getUsername(), $this->filter); } } diff --git a/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php index d555211e4b6..b7481906e3a 100644 --- a/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php @@ -7,6 +7,8 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 + * + * TODO The time/date value must be saved with the system timezone. */ final class DateConditionFormField extends AbstractConditionFormField implements IAttributeFormField, diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php index affd2528056..6fe451ff174 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php @@ -3,12 +3,13 @@ namespace wcf\system\user\group\assignment; use wcf\data\object\type\ObjectType; -use wcf\data\object\type\ObjectTypeCache; use wcf\data\user\group\assignment\UserGroupAssignment; use wcf\data\user\User; use wcf\data\user\UserAction; use wcf\data\user\UserList; use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; +use wcf\system\condition\ConditionHandler; +use wcf\system\condition\provider\UserConditionProvider; use wcf\system\SingletonFactory; /** @@ -48,6 +49,7 @@ public function checkUsers(array $userIDs) /** @var UserGroupAssignment[] $assignments */ $assignments = UserGroupAssignmentCacheBuilder::getInstance()->getData(); + $conditionProvider = new UserConditionProvider(); foreach ($userList as $user) { $groupIDs = $user->getGroupIDs(); $newGroupIDs = []; @@ -58,9 +60,9 @@ public function checkUsers(array $userIDs) } $checkFailed = false; - $conditions = $assignment->getConditions(); - foreach ($conditions as $condition) { - if (!$condition->getObjectType()->getProcessor()->checkUser($condition, $user)) { + $conditions = ConditionHandler::getInstance()->getConditionsWithFilter($conditionProvider, $assignment->getConditions()); + foreach ($conditions as $conditionType) { + if (!$conditionType->match($user)) { $checkFailed = true; break; } @@ -116,31 +118,12 @@ public function getUsers(UserGroupAssignment $assignment, $maxUsers = null) $userList->sqlLimit = $maxUsers; } - $conditions = $assignment->getConditions(); - foreach ($conditions as $condition) { - $condition->getObjectType()->getProcessor()->addUserCondition($condition, $userList); + $conditions = ConditionHandler::getInstance()->getConditionsWithFilter(new UserConditionProvider(), $assignment->getConditions()); + foreach ($conditions as $conditionType) { + $conditionType->applyFilter($userList); } $userList->readObjects(); return $userList->getObjects(); } - - /** - * @inheritDoc - */ - protected function init() - { - $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.condition.userGroupAssignment'); - foreach ($objectTypes as $objectType) { - if (!$objectType->conditiongroup) { - continue; - } - - if (!isset($this->groupedObjectTypes[$objectType->conditiongroup])) { - $this->groupedObjectTypes[$objectType->conditiongroup] = []; - } - - $this->groupedObjectTypes[$objectType->conditiongroup][$objectType->objectTypeID] = $objectType; - } - } } From 67f0a7720b6ee091469ce305a8def53fedddf07e Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 23 May 2025 11:07:58 +0200 Subject: [PATCH 07/82] Remove unnecessary ConditionHandler calls --- .../assignment/UserGroupAssignmentAction.class.php | 14 -------------- .../assignment/UserGroupAssignmentEditor.class.php | 5 ----- 2 files changed, 19 deletions(-) diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentAction.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentAction.class.php index f900e1b5e2d..9abcb3cdac6 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentAction.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentAction.class.php @@ -5,7 +5,6 @@ use wcf\data\AbstractDatabaseObjectAction; use wcf\data\IToggleAction; use wcf\data\TDatabaseObjectToggle; -use wcf\system\condition\ConditionHandler; /** * Executes user group assignment-related actions. @@ -34,17 +33,4 @@ class UserGroupAssignmentAction extends AbstractDatabaseObjectAction implements * @inheritDoc */ protected $requireACP = ['create', 'delete', 'toggle', 'update']; - - /** - * @inheritDoc - */ - public function delete() - { - ConditionHandler::getInstance()->deleteConditions( - 'com.woltlab.wcf.condition.userGroupAssignment', - $this->objectIDs - ); - - return parent::delete(); - } } diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php index d7cb60e334b..371bbb319c4 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php @@ -4,8 +4,6 @@ use wcf\data\DatabaseObjectEditor; use wcf\data\IEditableCachedObject; -use wcf\data\object\type\ObjectTypeCache; -use wcf\system\cache\builder\ConditionCacheBuilder; use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; /** @@ -32,8 +30,5 @@ class UserGroupAssignmentEditor extends DatabaseObjectEditor implements IEditabl public static function resetCache() { UserGroupAssignmentCacheBuilder::getInstance()->reset(); - ConditionCacheBuilder::getInstance()->reset([ - 'definitionID' => ObjectTypeCache::getInstance()->getDefinitionByName('com.woltlab.wcf.condition.userGroupAssignment')->definitionID, - ]); } } From 3fcf09d43df2bd08077567ed9c86864c1a645649 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 23 May 2025 11:20:07 +0200 Subject: [PATCH 08/82] Add some phrases --- wcfsetup/install/lang/de.xml | 2 ++ wcfsetup/install/lang/en.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index f3cb066aca5..4ecf3a793aa 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3533,6 +3533,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 21107065cd7..91dbafaf96f 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3456,6 +3456,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + + From a0e4c736698ab0b97376afddd4e8fb7604f090ce Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 23 May 2025 13:59:54 +0200 Subject: [PATCH 09/82] Reformate code --- .../shared_conditionFormContainer.tpl | 10 ++++----- .../templates/shared_conditionFormField.tpl | 2 +- .../shared_conditionRowFormFieldContainer.tpl | 12 +++++----- .../shared_dateConditionFormField.tpl | 22 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl index 3d61a1a508f..439b9bd5556 100644 --- a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl +++ b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl @@ -1,16 +1,16 @@
getClasses()|empty} class="{implode from=$container->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* *}{foreach from=$container->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* - *}{if !$container->checkDependencies()} style="display: none;"{/if}{* + *}{if !$container->checkDependencies()} style="display: none" {/if}{* *}> {if $container->getLabel() !== null} {if $container->getDescription() !== null}
-

{unsafe:$container->getLabel()}{if $container->markAsRequired()} *{/if}

+

{unsafe:$container->getLabel()}{if $container->markAsRequired()}*{/if}

{unsafe:$container->getDescription()}

{else} -

{unsafe:$container->getLabel()}{if $container->markAsRequired()} *{/if}

+

{unsafe:$container->getLabel()}{if $container->markAsRequired()}*{/if}

{/if} {/if} @@ -19,7 +19,7 @@
@@ -28,7 +28,7 @@ +{/if} diff --git a/com.woltlab.wcf/templates/shared_textConditionFormField.tpl b/com.woltlab.wcf/templates/shared_textConditionFormField.tpl deleted file mode 100644 index 58ee4505450..00000000000 --- a/com.woltlab.wcf/templates/shared_textConditionFormField.tpl +++ /dev/null @@ -1,15 +0,0 @@ -{include file="shared_conditionFormField"} - -getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* - *}{if $field->getAutoComplete() !== null} autocomplete="{$field->getAutoComplete()}"{/if}{* - *}{if $field->isAutofocused()} autofocus{/if}{* - *}{if $field->isRequired()} required{/if}{* - *}{if $field->isImmutable()} disabled{/if}{* - *}{if $field->getPlaceholder() !== null} placeholder="{$field->getPlaceholder()}"{/if}{* - *}{if $field->getDocument()->isAjax()} data-dialog-submit-on-enter="true"{/if}{* - *}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* -*}> diff --git a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts index 4f32f8123bb..0dbe3a0263b 100644 --- a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts +++ b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts @@ -10,6 +10,7 @@ import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; import { insertHtml } from "WoltLabSuite/Core/Dom/Util"; import { unescapeHTML } from "WoltLabSuite/Core/StringUtil"; import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector"; +import { getPhrase } from "WoltLabSuite/Core/Language"; interface ConditionAddResponse { field: string; @@ -35,10 +36,26 @@ export class ConditionFormField { }), ); - wheneverFirstSeen(`#${containerId}Container .condition-remove`, (element: HTMLButtonElement) => { - element.addEventListener("click", () => { - element.parentElement?.remove(); + wheneverFirstSeen(`#${containerId}Container .condition-container`, (container: HTMLElement) => { + const deleteButton = document.createElement("button"); + deleteButton.type = "button"; + deleteButton.classList.add("button", "small", "jsTooltip", "condition-button-remove"); + deleteButton.title = getPhrase("wcf.global.button.delete"); + const icon = document.createElement("fa-icon"); + icon.setIcon("times"); + deleteButton.appendChild(icon); + container.prepend(deleteButton); + deleteButton.addEventListener("click", () => { + container.remove(); }); + + const index = parseInt(container.dataset.conditionIndex!); + this.#index = Math.max(this.#index, index); + const hidden = document.createElement("input"); + hidden.type = "hidden"; + hidden.name = `${containerId}[${index}]`; + hidden.value = container.dataset.conditionType!; + container.appendChild(hidden); }); } @@ -50,8 +67,6 @@ export class ConditionFormField { const { ok, result } = await dialogFactory().usingFormBuilder().fromEndpoint(url.toString()); if (ok) { - this.#index++; - insertHtml(result.field, this.#container, "append"); } } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js index facc2c0622e..d508a795dd4 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js @@ -4,7 +4,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Dom/Util", "WoltLabSuite/Core/StringUtil", "WoltLabSuite/Core/Helper/Selector"], function (require, exports, PromiseMutex_1, Dialog_1, Util_1, StringUtil_1, Selector_1) { +define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Dom/Util", "WoltLabSuite/Core/StringUtil", "WoltLabSuite/Core/Helper/Selector", "WoltLabSuite/Core/Language"], function (require, exports, PromiseMutex_1, Dialog_1, Util_1, StringUtil_1, Selector_1, Language_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConditionFormField = void 0; @@ -21,10 +21,25 @@ define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabS this.#button?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { await this.#showConditionAddDialog(endpoint); })); - (0, Selector_1.wheneverFirstSeen)(`#${containerId}Container .condition-remove`, (element) => { - element.addEventListener("click", () => { - element.parentElement?.remove(); + (0, Selector_1.wheneverFirstSeen)(`#${containerId}Container .condition-container`, (container) => { + const deleteButton = document.createElement("button"); + deleteButton.type = "button"; + deleteButton.classList.add("button", "small", "jsTooltip", "condition-button-remove"); + deleteButton.title = (0, Language_1.getPhrase)("wcf.global.button.delete"); + const icon = document.createElement("fa-icon"); + icon.setIcon("times"); + deleteButton.appendChild(icon); + container.prepend(deleteButton); + deleteButton.addEventListener("click", () => { + container.remove(); }); + const index = parseInt(container.dataset.conditionIndex); + this.#index = Math.max(this.#index, index); + const hidden = document.createElement("input"); + hidden.type = "hidden"; + hidden.name = `${containerId}[${index}]`; + hidden.value = container.dataset.conditionType; + container.appendChild(hidden); }); } async #showConditionAddDialog(endpoint) { @@ -33,7 +48,6 @@ define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabS url.searchParams.set("index", this.#index.toString()); const { ok, result } = await (0, Dialog_1.dialogFactory)().usingFormBuilder().fromEndpoint(url.toString()); if (ok) { - this.#index++; (0, Util_1.insertHtml)(result.field, this.#container, "append"); } } diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index e72a8e86507..6ff09699891 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -3,7 +3,7 @@ namespace wcf\system\condition\provider; use wcf\system\condition\type\IConditionType; -use wcf\system\form\builder\container\ConditionRowFormFieldContainer; +use wcf\system\form\builder\container\FormContainer; /** * @author Olaf Braun @@ -47,23 +47,23 @@ final public function getFieldId(string $containerId, string $identifier, int $i return "{$containerId}_{$identifier}_{$index}"; } - final public function getConditionFormField(string $containerId, string $identifier, int $index, mixed $value = null): ConditionRowFormFieldContainer + final public function getConditionFormField(string $containerId, string $identifier, int $index): FormContainer { $condition = $this->getConditionByIdentifier($identifier); if ($condition === null) { throw new \InvalidArgumentException("Condition type with identifier '{$identifier}' not found."); } - $formField = $condition->getFormField($this->getFieldId($containerId, $identifier, $index)); - if ($value !== null) { - $formField->value($value); - } + $id = $this->getFieldId($containerId, $identifier, $index); + $formField = $condition->getFormField($id) + ->label($condition->getLabel()); - return ConditionRowFormFieldContainer::create("{$containerId}_{$identifier}_{$index}_container") - ->containerId($containerId) - ->conditionType($identifier) - ->conditionIndex($index) - ->label($condition->getLabel()) + return FormContainer::create("{$id}_container") + ->removeClass("section") + ->addClass("condition-container") + ->attribute("data-container-id", $containerId) + ->attribute("data-condition-type", $identifier) + ->attribute("data-condition-index", (string)$index) ->appendChild($formField); } diff --git a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php index 5dfe61fb723..03b9aa4219a 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IConditionType.class.php @@ -2,6 +2,7 @@ namespace wcf\system\condition\type; +use wcf\system\form\builder\container\IFormContainer; use wcf\system\form\builder\field\IFormField; /** @@ -17,7 +18,7 @@ interface IConditionType /** * Returns the form field for this condition type. */ - public function getFormField(string $id): IFormField; + public function getFormField(string $id): IFormField|IFormContainer; /** * Returns the identifier of this condition type. diff --git a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php index f0f2295af2f..c68b8ec1395 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php @@ -15,9 +15,7 @@ interface IObjectConditionType extends IConditionType { /** - * Returns `true` if the given object matches the filter, `false` otherwise. - * * @param T $object */ - public function match(object $object): bool; + public function matches(object $object): bool; } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index da36d30c807..e41849a668e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -9,7 +9,7 @@ use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\SingleSelectionFormField; +use wcf\system\form\builder\field\SelectFormField; /** * @author Olaf Braun @@ -24,16 +24,17 @@ final class UserInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): SingleSelectionFormField + public function getFormField(string $id): SelectFormField { - return SingleSelectionFormField::create($id) + return SelectFormField::create($id) ->options( UserGroup::getGroupsByType(invalidGroupTypes: [ UserGroup::EVERYONE, UserGroup::GUESTS, UserGroup::USERS, ]) - ); + ) + ->required(); } #[\Override] @@ -62,7 +63,7 @@ public function applyFilter(DatabaseObjectList $objectList): void } #[\Override] - public function match(object $object): bool + public function matches(object $object): bool { return \in_array($this->filter, $object->getGroupIDs(), true); } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index a7338388899..59da956f17e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -9,7 +9,7 @@ use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\SingleSelectionFormField; +use wcf\system\form\builder\field\SelectFormField; /** * @author Olaf Braun @@ -24,16 +24,17 @@ final class UserNotInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): SingleSelectionFormField + public function getFormField(string $id): SelectFormField { - return SingleSelectionFormField::create($id) + return SelectFormField::create($id) ->options( UserGroup::getGroupsByType(invalidGroupTypes: [ UserGroup::EVERYONE, UserGroup::GUESTS, UserGroup::USERS, ]) - ); + ) + ->required(); } #[\Override] @@ -62,7 +63,7 @@ public function applyFilter(DatabaseObjectList $objectList): void } #[\Override] - public function match(object $object): bool + public function matches(object $object): bool { return !\in_array($this->filter, $object->getGroupIDs(), true); } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php index dc7b148a6d0..dfaff458bfb 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php @@ -8,8 +8,9 @@ use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\AbstractConditionFormField; -use wcf\system\form\builder\field\DateConditionFormField; +use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; +use wcf\system\form\builder\field\DateFormField; +use wcf\system\form\builder\field\SingleSelectionFormField; /** * @author Olaf Braun @@ -25,12 +26,19 @@ final class UserRegistrationDateConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): AbstractConditionFormField + public function getFormField(string $id): PrefixConditionFormFieldContainer { - return DateConditionFormField::create($id) - ->conditions(\array_combine($this->getConditions(), $this->getConditions())) - ->nullable() - ->supportTime(); + return PrefixConditionFormFieldContainer::create($id) + ->field( + DateFormField::create("{$id}Value") + ->supportTime() + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options(\array_combine($this->getConditions(), $this->getConditions())) + ->required() + ); } #[\Override] @@ -57,7 +65,7 @@ public function applyFilter(DatabaseObjectList $objectList): void } #[\Override] - public function match(object $object): bool + public function matches(object $object): bool { ["condition" => $condition, "value" => $time] = $this->filter; diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index 43dbf7c5a9e..9d47d30e0ec 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -8,7 +8,9 @@ use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\IntegerConditionFormField; +use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; +use wcf\system\form\builder\field\IntegerFormField; +use wcf\system\form\builder\field\SingleSelectionFormField; use wcf\util\DateUtil; /** @@ -25,11 +27,19 @@ final class UserRegistrationDaysConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): IntegerConditionFormField + public function getFormField(string $id): PrefixConditionFormFieldContainer { - return IntegerConditionFormField::create($id) - ->conditions($this->getConditions()) - ->suffix("wcf.acp.option.suffix.days"); + return PrefixConditionFormFieldContainer::create($id) + ->field( + IntegerFormField::create("{$id}Value") + ->suffix("wcf.acp.option.suffix.days") + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options($this->getConditions()) + ->required() + ); } #[\Override] @@ -56,7 +66,7 @@ public function applyFilter(DatabaseObjectList $objectList): void } #[\Override] - public function match(object $object): bool + public function matches(object $object): bool { ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php index 92ca136bb54..67952006dd7 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php @@ -8,7 +8,9 @@ use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\TextConditionFormField; +use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; +use wcf\system\form\builder\field\SingleSelectionFormField; +use wcf\system\form\builder\field\TextFormField; /** * @author Olaf Braun @@ -24,10 +26,18 @@ final class UserUsernameConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] - public function getFormField(string $id): TextConditionFormField + public function getFormField(string $id): PrefixConditionFormFieldContainer { - return TextConditionFormField::create($id) - ->conditions($this->getConditions()); + return PrefixConditionFormFieldContainer::create($id) + ->field( + TextFormField::create("{$id}Value") + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options($this->getConditions()) + ->required() + ); } #[\Override] @@ -47,9 +57,9 @@ public function applyFilter(DatabaseObjectList $objectList): void { ["condition" => $condition, "value" => $value] = $this->filter; $filter = match ($condition) { - "%_" => $value . '%', + "_%" => $value . '%', "%_%" => '%' . $value . '%', - "_%" => '%' . $value, + "%_" => '%' . $value, default => '', }; @@ -60,14 +70,14 @@ public function applyFilter(DatabaseObjectList $objectList): void } #[\Override] - public function match(object $object): bool + public function matches(object $object): bool { ["condition" => $condition, "value" => $value] = $this->filter; return match ($condition) { - "%_" => \str_starts_with($object->username, $value), + "_%" => \str_starts_with($object->username, $value), "%_%" => \str_contains($object->username, $value), - "_%" => \str_ends_with($object->username, $value), + "%_" => \str_ends_with($object->username, $value), default => false, }; } @@ -78,9 +88,9 @@ public function match(object $object): bool private function getConditions(): array { return [ - "%_" => "wcf.condition.startsWith", + "_%" => "wcf.condition.startsWith", "%_%" => "wcf.condition.contains", - "_%" => "wcf.condition.endsWith", + "%_" => "wcf.condition.endsWith", ]; } } diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php index a8b58d432d7..ce29d77a2ec 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -6,8 +6,10 @@ use wcf\system\condition\provider\AbstractConditionProvider; use wcf\system\condition\type\IConditionType; use wcf\system\form\builder\data\processor\CustomFormDataProcessor; +use wcf\system\form\builder\field\IFormField; use wcf\system\form\builder\field\TDefaultIdFormField; use wcf\system\form\builder\IFormDocument; +use wcf\system\form\builder\IFormNode; use wcf\util\JSON; /** @@ -75,24 +77,41 @@ public function updatedObject(array $data, IStorableObject $object, $loadValues if ($loadValues && isset($data[$this->getPrefixedId()])) { $conditions = JSON::decode($data[$this->getPrefixedId()]); + $data = $containers = []; foreach ($conditions as $index => $condition) { - $this->appendCondition($condition['identifier'], $index, $condition['value']); + $containers[] = $this->appendCondition($condition['identifier'], $index); + $fieldId = $this->getConditionProvider()->getFieldId($this->getPrefixedId(), $condition['identifier'], $index); + $data[$fieldId] = $condition['value']; $this->lastConditionIndex = \max($this->lastConditionIndex, $index); } + + foreach ($containers as $container) { + /** @var IFormNode $child */ + foreach ($container->getIterator() as $child) { + if ($child instanceof IFormField || $child instanceof FormContainer) { + $child->updatedObject($data, $object); + } + } + } } return $this; } - private function appendCondition(string $identifier, int $index, mixed $value = null): void + private function appendCondition(string $identifier, int $index): FormContainer { $prefixId = $this->getPrefixedId(); - $formField = $this->getConditionProvider()->getConditionFormField($prefixId, $identifier, $index, $value); - $this->appendChild($formField); + $node = $this->getConditionProvider()->getConditionFormField($prefixId, $identifier, $index); + $this->appendChild($node); - $fieldId = $this->getConditionProvider()->getFieldId($prefixId, $identifier, $index); + $fieldId = $this->getConditionProvider()->getFieldId($this->getPrefixedId(), $identifier, $index); + + /** @var IFormNode $child */ + foreach ($node->getIterator() as $child) { + $child->populate(); + } $this->getDocument()->getDataHandler()->addProcessor( new CustomFormDataProcessor( @@ -115,6 +134,8 @@ static function (IFormDocument $document, array $parameters) use ($prefixId, $id } ) ); + + return $node; } /** diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php deleted file mode 100644 index 50b8e50301b..00000000000 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionRowFormFieldContainer.class.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @since 6.2 - */ -final class ConditionRowFormFieldContainer extends FormContainer -{ - /** - * @inheritDoc - */ - protected $templateName = 'shared_conditionRowFormFieldContainer'; - - private string $conditionType; - private string $containerId; - private int $conditionIndex = 0; - - public function __construct() - { - $this->addClass("condition-row"); - } - - /** - * Sets the condition index for the container. - */ - public function conditionIndex(int $conditionIndex): self - { - $this->conditionIndex = $conditionIndex; - - return $this; - } - - /** - * Retrieves the condition index of the container. - */ - public function getConditionIndex(): int - { - return $this->conditionIndex; - } - - /** - * Sets the condition type for the container. - */ - public function conditionType(string $conditionType): self - { - $this->conditionType = $conditionType; - - return $this; - } - - /** - * Retrieves the condition type of the container. - */ - public function getConditionType(): string - { - return $this->conditionType; - } - - /** - * Sets the container ID. - */ - public function containerId(string $containerId): self - { - $this->containerId = $containerId; - - return $this; - } - - /** - * Retrieves the container ID. - */ - public function getContainerId(): string - { - return $this->containerId; - } -} diff --git a/wcfsetup/install/files/lib/system/form/builder/container/FormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/FormContainer.class.php index 1e3e3a4c95d..2760a6f7b12 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/FormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/FormContainer.class.php @@ -59,6 +59,7 @@ public function getHtml() $this->templateName, \array_merge($this->getHtmlVariables(), [ 'container' => $this, + 'form' => $this->getDocument(), ]), ); } diff --git a/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php new file mode 100644 index 00000000000..299e62301ce --- /dev/null +++ b/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php @@ -0,0 +1,211 @@ + + * @since 6.3 + */ +final class PrefixConditionFormFieldContainer extends FormContainer +{ + /** + * form field to which the prefix selection is added + */ + protected IFormField $field; + + /** + * selection form field containing the prefix options + */ + protected ?ISelectionFormField $prefixField; + + /** + * @inheritDoc + */ + protected $templateName = 'shared_prefixFormFieldContainer'; + + #[\Override] + public function populate() + { + $this->getDocument()->getDataHandler() + ->addProcessor( + new CustomFormDataProcessor( + $this->getId() . "DataProcessor", + function (IFormDocument $document, array $parameters) { + $fieldId = $this->getField()->getId(); + $prefixId = $this->getPrefixField()->getId(); + if (isset($parameters['data'][$prefixId], $parameters['data'][$fieldId])) { + $parameters['data'][$this->getId()] = [ + 'condition' => $parameters['data'][$prefixId], + 'value' => $parameters['data'][$fieldId], + ]; + } + + unset( + $parameters['data'][$fieldId], + $parameters['data'][$prefixId] + ); + + return $parameters; + }, + ) + ); + + return parent::populate(); + } + + #[\Override] + public function updatedObject(array $data, IStorableObject $object, $loadValues = true) + { + if ($loadValues && isset($data[$this->getId()])) { + ["condition" => $condition, "value" => $value] = $data[$this->getId()]; + + $this->getField()->updatedObject([$this->getField()->getId() => $value], $object); + $this->getPrefixField()->updatedObject([$this->getPrefixField()->getId() => $condition], $object); + } + + return $this; + } + + public function field(IFormField $formField): static + { + if (isset($this->field)) { + throw new \BadMethodCallException("Field has already been set for container '{$this->getId()}'."); + } + + $this->field = $formField; + $this->appendChild($formField); + + return $this; + } + + public function getField(): IFormField + { + if (!isset($this->field)) { + throw new \BadMethodCallException("Field has not been set yet for container '{$this->getId()}'."); + } + + return $this->field; + } + + /** + * Returns the initial option of the prefix selection dropdown. + * + * @return array{label: string, value: mixed, depth: int, isSelectable: bool} + * @throws \BadMethodCallException if no prefix field is set or has no options + */ + public function getSelectedPrefixOption(): array + { + if (!isset($this->prefixField)) { + throw new \BadMethodCallException( + "There is no prefix field for which a label could be determined for container '{$this->getId()}'." + ); + } + if (empty($this->getPrefixField()->getOptions())) { + throw new \BadMethodCallException( + "The prefix field has no options for container '{$this->getId()}'." + ); + } + + foreach ($this->getPrefixField()->getNestedOptions() as $option) { + if ($this->getPrefixField()->getValue() === null) { + if ($option['isSelectable']) { + return $option; + } + } elseif ($option['value'] == $this->getPrefixField()->getValue()) { + return $option; + } + } + + // Return the first selectable option if no valid value is selected. + foreach ($this->getPrefixField()->getNestedOptions() as $option) { + if ($option['isSelectable']) { + return $option; + } + } + + throw new \RuntimeException( + "Cannot determine selected prefix option for container '{$this->getId()}'." + ); + } + + /** + * Returns the selection form field containing the prefix options. + */ + public function getPrefixField(): ?ISelectionFormField + { + return $this->prefixField; + } + + /** + * Returns the label used for the prefix selection if the field has no selectable options + * or is immutable. + */ + public function getPrefixLabel(): string + { + if ($this->getPrefixField() === null) { + throw new \BadMethodCallException( + "There is no prefix field for which a label could be determined for container '{$this->getId()}'." + ); + } + + if (empty($this->getPrefixField()->getOptions())) { + return ''; + } + + if (isset($this->getPrefixField()->getOptions()[$this->getPrefixField()->getValue()])) { + return $this->getPrefixField()->getOptions()[$this->getPrefixField()->getValue()]; + } + + return ''; + } + + /** + * Sets the selection form field containing the prefix options. + */ + public function prefixField(ISelectionFormField $formField): static + { + if (isset($this->prefixField)) { + throw new \BadMethodCallException( + "Prefix field has already been set for container '{$this->getId()}'." + ); + } + + $this->prefixField = $formField; + $this->appendChild($formField); + + return $this; + } + + /** + * Returns `true` if the prefix selection has any selectable options. + */ + public function prefixHasSelectableOptions(): bool + { + $prefixField = $this->getPrefixField(); + + if ($prefixField === null) { + return false; + } + + if ($prefixField instanceof IImmutableFormField && $prefixField->isImmutable()) { + return false; + } + + foreach ($prefixField->getNestedOptions() as $option) { + if ($option['isSelectable']) { + return true; + } + } + + return false; + } +} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php deleted file mode 100644 index b3069a1416e..00000000000 --- a/wcfsetup/install/files/lib/system/form/builder/field/AbstractConditionFormField.class.php +++ /dev/null @@ -1,133 +0,0 @@ - - * @since 6.3 - */ -abstract class AbstractConditionFormField extends AbstractFormField implements IImmutableFormField -{ - use TImmutableFormField; - - /** - * @var array - */ - protected array $conditions = []; - - #[\Override] - public function getSaveValue() - { - if ($this instanceof INullableFormField && $this->isNullable() && !$this->getValue()) { - return; - } - - if (!\is_array($this->value)) { - return []; - } - - return $this->value; - } - - #[\Override] - public function validate() - { - if ($this->isRequired() && !\is_array($this->value)) { - $this->addValidationError(new FormFieldValidationError('empty')); - - return; - } - - if ($this instanceof INullableFormField && $this->value === null) { - return; - } - - if (!\array_key_exists($this->getCondition(), $this->conditions)) { - $this->addValidationError( - new FormFieldValidationError( - 'invalidValue', - 'wcf.global.form.error.noValidSelection' - ) - ); - } - } - - #[\Override] - public function value($value) - { - if (!\is_array($value)) { - $value = null; - } elseif (!\array_key_exists('condition', $value) || !\array_key_exists('value', $value)) { - throw new \InvalidArgumentException('Invalid serialized value'); - } - - return parent::value($value); - } - - #[\Override] - public function getValue() - { - return \is_array($this->value) ? $this->value['value'] : null; - } - - #[\Override] - final public function readValue() - { - if ($this->getDocument()->hasRequestData("{$this->getPrefixedId()}_condition") && $this->hasFieldValue()) { - $condition = $this->getDocument()->getRequestData("{$this->getPrefixedId()}_condition"); - - $this->value = [ - 'condition' => $condition, - 'value' => $this->getFieldValue(), - ]; - } - - return $this; - } - - /** - * Sets the conditions for this form field. - * - * @param array $conditions - */ - public function conditions(array $conditions): static - { - $this->conditions = $conditions; - - return $this; - } - - /** - * Returns the conditions for this form field. - * - * @return array - */ - public function getConditions(): array - { - return $this->conditions; - } - - /** - * Returns the selected condition for this form field. - */ - public function getCondition(): string - { - return \is_array($this->value) ? $this->value['condition'] : ''; - } - - protected function getFieldValue(): mixed - { - return $this->getDocument()->getRequestData($this->getPrefixedId()); - } - - protected function hasFieldValue(): bool - { - return $this->getDocument()->hasRequestData($this->getPrefixedId()); - } -} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php deleted file mode 100644 index 558524dbdc0..00000000000 --- a/wcfsetup/install/files/lib/system/form/builder/field/DateConditionFormField.class.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @since 6.3 - */ -final class DateConditionFormField extends AbstractConditionFormField implements - IAttributeFormField, - IAutoFocusFormField, - ICssClassFormField, - INullableFormField -{ - use TAttributeFormField; - use TAutoFocusFormField; - use TCssClassFormField; - use TNullableFormField; - - public const DATE_FORMAT = 'Y-m-d'; - - public const TIME_FORMAT = 'Y-m-d\TH:i:s'; - - /** - * is `true` if not only the date, but also the time can be set - */ - protected bool $supportsTime = false; - - /** - * @inheritDoc - */ - protected $templateName = 'shared_dateConditionFormField'; - - public function __construct() - { - $this->fieldAttribute("data-ignore-timezone", "1"); - } - - #[\Override] - public function getSaveValue() - { - if ($this->getValue() === null) { - return parent::getSaveValue(); - } - - $dateTime = \DateTime::createFromFormat( - $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT, - $this->getValue(), - WCF::getUser()->getTimezone() - ); - - return [ - "condition" => $this->getCondition(), - "value" => $dateTime->getTimestamp(), - ]; - } - - #[\Override] - public function validate() - { - parent::validate(); - - if ($this->getValue() !== null) { - $dateTime = \DateTime::createFromFormat( - $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT, - $this->getValue(), - WCF::getUser()->getTimezone() - ); - - if ($dateTime === false) { - $this->addValidationError( - new FormFieldValidationError( - 'format', - 'wcf.form.field.date.error.format' - ) - ); - } - } - } - - #[\Override] - public function value($value): self - { - parent::value($value); - - if ($this->getValue() !== null) { - $dateTime = DateUtil::getDateTimeByTimestamp($this->getValue()); - $dateTime->setTimezone(WCF::getUser()->getTimezone()); - $this->value["value"] = $dateTime->format($this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT); - } - - return $this; - } - - /** - * Sets if not only the date, but also the time can be set. - */ - public function supportTime(bool $supportsTime = true): self - { - if ($this->value !== null) { - throw new \BadFunctionCallException( - "After a value has been set, time support cannot be changed for field '{$this->getId()}'." - ); - } - - $this->supportsTime = $supportsTime; - - return $this; - } - - /** - * Returns `true` if not only the date, but also the time can be set, and - * returns `false` otherwise. - * - * By default, the time cannot be set. - */ - public function supportsTime(): bool - { - return $this->supportsTime; - } -} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/IntegerConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/IntegerConditionFormField.class.php deleted file mode 100644 index 79c1cd913ef..00000000000 --- a/wcfsetup/install/files/lib/system/form/builder/field/IntegerConditionFormField.class.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @since 6.3 - */ -final class IntegerConditionFormField extends AbstractConditionFormField implements - IAttributeFormField, - IAutoFocusFormField, - ICssClassFormField, - INullableFormField, - IPlaceholderFormField, - IAutoCompleteFormField, - ISuffixedFormField -{ - use TAttributeFormField { - getReservedFieldAttributes as private defaultGetReservedFieldAttributes; - } - use TAutoFocusFormField; - use TCssClassFormField; - use TNullableFormField; - use TPlaceholderFormField; - use TAutoCompleteFormField; - use TInputModeFormField; - use TSuffixedFormField; - - /** - * @inheritDoc - */ - protected $templateName = 'shared_numericConditionFormField'; - - /** - * step value for the input element - */ - protected int $step = 1; - - public function __construct() - { - $this->addFieldClass('short'); - } - - /** - * @return string[] - */ - protected static function getReservedFieldAttributes(): array - { - return \array_merge( - self::defaultGetReservedFieldAttributes(), - [ - 'step', - ] - ); - } - - public function step(int $step): self - { - $this->step = $step; - - return $this; - } - - public function getStep(): int - { - return $this->step; - } - - #[\Override] - public function getFieldValue(): int - { - return \intval(parent::getFieldValue()); - } - - /** - * @return string[] - */ - protected function getValidInputModes(): array - { - return ['numeric']; - } -} diff --git a/wcfsetup/install/files/lib/system/form/builder/field/TextConditionFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/TextConditionFormField.class.php deleted file mode 100644 index 284020c2075..00000000000 --- a/wcfsetup/install/files/lib/system/form/builder/field/TextConditionFormField.class.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @since 6.3 - */ -final class TextConditionFormField extends AbstractConditionFormField implements - IAttributeFormField, - IAutoFocusFormField, - ICssClassFormField, - INullableFormField, - IPlaceholderFormField, - IAutoCompleteFormField -{ - use TAttributeFormField; - use TAutoFocusFormField; - use TCssClassFormField; - use TNullableFormField; - use TPlaceholderFormField; - use TAutoCompleteFormField; - - /** - * @inheritDoc - */ - protected $templateName = 'shared_textConditionFormField'; - - public function __construct() - { - $this->addFieldClass('medium'); - } - - #[\Override] - public function hasFieldValue(): bool - { - return parent::hasFieldValue() && \mb_strlen($this->getFieldValue()) > 0; - } - - #[\Override] - public function getFieldValue(): string - { - return StringUtil::trim(parent::getFieldValue()); - } -} diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php index 6fe451ff174..6d5ef9d5055 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php @@ -62,7 +62,7 @@ public function checkUsers(array $userIDs) $checkFailed = false; $conditions = ConditionHandler::getInstance()->getConditionsWithFilter($conditionProvider, $assignment->getConditions()); foreach ($conditions as $conditionType) { - if (!$conditionType->match($user)) { + if (!$conditionType->matches($user)) { $checkFailed = true; break; } diff --git a/wcfsetup/install/files/style/ui/condition.scss b/wcfsetup/install/files/style/ui/condition.scss index 6cd7c520c68..25651446043 100644 --- a/wcfsetup/install/files/style/ui/condition.scss +++ b/wcfsetup/install/files/style/ui/condition.scss @@ -1,20 +1,14 @@ -.conditions { +.condition-container { display: flex; - flex-direction: column; + flex-direction: row; } -.condition-row { - display: inline-flex; +.condition-button-remove { + flex: 0 30px; } -.condition-remove { - flex: 0; -} - -.condition-label { - flex: 1; -} - -.condition-field { +.condition-container > dl { flex: 1; + display: flex; + flex-direction: row; } From e3fcf766b36d06a0e322b25e02733363a8121560 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Mon, 2 Jun 2025 10:46:24 +0200 Subject: [PATCH 24/82] Use a meaningful name for the template types. --- .../lib/system/condition/ConditionHandler.class.php | 6 +++--- .../provider/AbstractConditionProvider.class.php | 12 ++++++------ .../type/IDatabaseObjectListConditionType.class.php | 4 ++-- .../condition/type/IObjectConditionType.class.php | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index 948277472f6..77b8fe62fa9 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -145,11 +145,11 @@ public function updateConditions($objectID, array $oldConditions, array $conditi /** * Returns the list of conditions with assigned filter for the condition provider and stored condition-values. * - * @template T of IConditionType - * @param AbstractConditionProvider $provider + * @template TCondition of IConditionType + * @param AbstractConditionProvider $provider * @param array{identifier: string, value: mixed}[] $conditions * - * @return T[] + * @return TCondition[] */ public function getConditionsWithFilter(AbstractConditionProvider $provider, array $conditions): array { diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index 6ff09699891..29b94946e76 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -11,19 +11,19 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @template T of IConditionType + * @template TCondition of IConditionType */ abstract class AbstractConditionProvider { /** - * @var array + * @var array */ protected array $conditionTypes = []; /** * Adds a condition type to this provider. * - * @param T $conditionType + * @param TCondition $conditionType */ public function addCondition(IConditionType $conditionType): void { @@ -33,7 +33,7 @@ public function addCondition(IConditionType $conditionType): void /** * Adds multiple condition types to this provider. * - * @param T[] $conditionTypes + * @param TCondition[] $conditionTypes */ public function addConditions(array $conditionTypes): void { @@ -70,7 +70,7 @@ final public function getConditionFormField(string $containerId, string $identif /** * Returns the condition type with the given identifier. * - * @return T|null + * @return TCondition|null */ public function getConditionByIdentifier(string $identifier): ?IConditionType { @@ -80,7 +80,7 @@ public function getConditionByIdentifier(string $identifier): ?IConditionType /** * Returns all condition types of this provider. * - * @return array + * @return array */ public function getConditionTypes(): array { diff --git a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php index c198d6a40c2..645d49af06e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IDatabaseObjectListConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @template T of DatabaseObjectList + * @template TObjectList of DatabaseObjectList * @template TFilter * @extends IConditionType */ @@ -19,7 +19,7 @@ interface IDatabaseObjectListConditionType extends IConditionType /** * Adds a filter to the given object list. * - * @param T $objectList + * @param TObjectList $objectList */ public function applyFilter(DatabaseObjectList $objectList): void; } diff --git a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php index c68b8ec1395..5acee692822 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IObjectConditionType.class.php @@ -8,14 +8,14 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @template T of object + * @template TObject of object * @template TFilter * @extends IConditionType */ interface IObjectConditionType extends IConditionType { /** - * @param T $object + * @param TObject $object */ public function matches(object $object): bool; } From b68ace692e580f4a2a867c9d6bcd19ab58e646bd Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 3 Jun 2025 15:35:22 +0200 Subject: [PATCH 25/82] Make the prefix form field a required field and change it to a generic `IFormField`. --- .../shared_prefixFormFieldContainer.tpl | 48 +++++++-------- ...refixConditionFormFieldContainer.class.php | 60 +++++++------------ 2 files changed, 45 insertions(+), 63 deletions(-) diff --git a/com.woltlab.wcf/templates/shared_prefixFormFieldContainer.tpl b/com.woltlab.wcf/templates/shared_prefixFormFieldContainer.tpl index 3fc7c74b959..ff0c5b4d9a7 100644 --- a/com.woltlab.wcf/templates/shared_prefixFormFieldContainer.tpl +++ b/com.woltlab.wcf/templates/shared_prefixFormFieldContainer.tpl @@ -1,59 +1,59 @@ -
getField()->getClasses()|empty} class="{implode from=$container->getField()->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* - *}{foreach from=$container->getField()->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* - *}{if !$container->getField()->checkDependencies()} style="display: none +{assign var='field' value=$container->getField()} +{assign var='prefixField' value=$container->getPrefixField()} + +
getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{* + *}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{* + *}{if !$field->checkDependencies()} style="display: none ;"{/if}{* *}> -
{if $container->getLabel() !== null}{if $container->getField()->isRequired() && $form->marksRequiredFields()} *{/if}{/if}
+
{if $container->getLabel() !== null}{if $field->isRequired() && $form->marksRequiredFields()} *{/if}{/if}
- {if $container->getPrefixField() !== null && $container->getPrefixField()->isAvailable()} + {if $prefixField->isAvailable()} {if !$container->prefixHasSelectableOptions()} - {if $container->getPrefixLabel() !== ''} - {unsafe:$container->getPrefixLabel()} - {/if} + {unsafe:$prefixField->getFieldHtml()} {else} - + {unsafe:$container->getSelectedPrefixOption()[label]} {icon name='caret-down' type='solid'} - + - {include file='shared_formFieldDependencies' field=$container->getPrefixField()} - {include file='shared_formFieldDataHandler' field=$container->getPrefixField()} {/if} + {include file='shared_formFieldDependencies' field=$prefixField sandbox=true} + {include file='shared_formFieldDataHandler' field=$prefixField sandbox=true} {/if} - - {unsafe:$container->getField()->getFieldHtml()} + {unsafe:$field->getFieldHtml()}
{if $container->getDescription() !== null} {unsafe:$container->getDescription()} {/if} - {include file='shared_formFieldErrors' field=$container->getField()} + {include file='shared_formFieldErrors' field=$field sandbox=true} - {if $container->getPrefixField() !== null && $container->getPrefixField()->isAvailable()} - {foreach from=$container->getPrefixField()->getValidationErrors() item='validationError'} + {if $prefixField !== null && $prefixField->isAvailable()} + {foreach from=$prefixField->getValidationErrors() item='validationError'} {unsafe:$validationError->getHtml()} {/foreach} {/if} - {include file='shared_formFieldDependencies' field=$container->getField()} - {include file='shared_formFieldDataHandler' field=$container->getField()} + {include file='shared_formFieldDependencies' field=$field sandbox=true} + {include file='shared_formFieldDataHandler' field=$field sandbox=true}
-{if $container->getPrefixField() !== null && $container->getPrefixField()->isAvailable() && !$container->getPrefixField()->isImmutable() && $container->prefixHasSelectableOptions()} +{if $prefixField->isAvailable() && !$prefixField->isImmutable() && $container->prefixHasSelectableOptions()} diff --git a/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php index 299e62301ce..c1bbf6eb21e 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/PrefixConditionFormFieldContainer.class.php @@ -18,14 +18,14 @@ final class PrefixConditionFormFieldContainer extends FormContainer { /** - * form field to which the prefix selection is added + * form field to which the prefix is added */ protected IFormField $field; /** - * selection form field containing the prefix options + * form field containing the prefix field */ - protected ?ISelectionFormField $prefixField; + protected IFormField $prefixField; /** * @inheritDoc @@ -104,29 +104,28 @@ public function getField(): IFormField */ public function getSelectedPrefixOption(): array { - if (!isset($this->prefixField)) { - throw new \BadMethodCallException( - "There is no prefix field for which a label could be determined for container '{$this->getId()}'." - ); - } - if (empty($this->getPrefixField()->getOptions())) { + $prefixField = $this->getPrefixField(); + + \assert($prefixField instanceof ISelectionFormField); + + if (empty($prefixField->getOptions())) { throw new \BadMethodCallException( "The prefix field has no options for container '{$this->getId()}'." ); } - foreach ($this->getPrefixField()->getNestedOptions() as $option) { - if ($this->getPrefixField()->getValue() === null) { + foreach ($prefixField->getNestedOptions() as $option) { + if ($prefixField->getValue() === null) { if ($option['isSelectable']) { return $option; } - } elseif ($option['value'] == $this->getPrefixField()->getValue()) { + } elseif ($option['value'] == $prefixField->getValue()) { return $option; } } // Return the first selectable option if no valid value is selected. - foreach ($this->getPrefixField()->getNestedOptions() as $option) { + foreach ($prefixField->getNestedOptions() as $option) { if ($option['isSelectable']) { return $option; } @@ -138,40 +137,23 @@ public function getSelectedPrefixOption(): array } /** - * Returns the selection form field containing the prefix options. + * Returns the prefix form field. */ - public function getPrefixField(): ?ISelectionFormField + public function getPrefixField(): IFormField { - return $this->prefixField; - } - - /** - * Returns the label used for the prefix selection if the field has no selectable options - * or is immutable. - */ - public function getPrefixLabel(): string - { - if ($this->getPrefixField() === null) { + if (!isset($this->prefixField)) { throw new \BadMethodCallException( - "There is no prefix field for which a label could be determined for container '{$this->getId()}'." + "Prefix field has not been set yet for container '{$this->getId()}'." ); } - if (empty($this->getPrefixField()->getOptions())) { - return ''; - } - - if (isset($this->getPrefixField()->getOptions()[$this->getPrefixField()->getValue()])) { - return $this->getPrefixField()->getOptions()[$this->getPrefixField()->getValue()]; - } - - return ''; + return $this->prefixField; } /** - * Sets the selection form field containing the prefix options. + * Sets the prefix form field. */ - public function prefixField(ISelectionFormField $formField): static + public function prefixField(IFormField $formField): static { if (isset($this->prefixField)) { throw new \BadMethodCallException( @@ -186,13 +168,13 @@ public function prefixField(ISelectionFormField $formField): static } /** - * Returns `true` if the prefix selection has any selectable options. + * Returns `true` if the prefix form field has any selectable options. */ public function prefixHasSelectableOptions(): bool { $prefixField = $this->getPrefixField(); - if ($prefixField === null) { + if (!($prefixField instanceof ISelectionFormField)) { return false; } From 18ac2e69bd7f9290fb05ab64c206c945990662fa Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 3 Jun 2025 15:42:29 +0200 Subject: [PATCH 26/82] Refactor condition class names for consistency and clarity --- .../Core/Form/Builder/Container/ConditionFormField.ts | 4 ++-- .../Core/Form/Builder/Container/ConditionFormField.js | 4 ++-- .../condition/provider/AbstractConditionProvider.class.php | 2 +- wcfsetup/install/files/style/ui/condition.scss | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts index 0dbe3a0263b..13e4fae6b75 100644 --- a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts +++ b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts @@ -36,10 +36,10 @@ export class ConditionFormField { }), ); - wheneverFirstSeen(`#${containerId}Container .condition-container`, (container: HTMLElement) => { + wheneverFirstSeen(`#${containerId}Container .condition__container`, (container: HTMLElement) => { const deleteButton = document.createElement("button"); deleteButton.type = "button"; - deleteButton.classList.add("button", "small", "jsTooltip", "condition-button-remove"); + deleteButton.classList.add("button", "small", "jsTooltip", "condition__remove"); deleteButton.title = getPhrase("wcf.global.button.delete"); const icon = document.createElement("fa-icon"); icon.setIcon("times"); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js index d508a795dd4..fe2ad65df97 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js @@ -21,10 +21,10 @@ define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabS this.#button?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { await this.#showConditionAddDialog(endpoint); })); - (0, Selector_1.wheneverFirstSeen)(`#${containerId}Container .condition-container`, (container) => { + (0, Selector_1.wheneverFirstSeen)(`#${containerId}Container .condition__container`, (container) => { const deleteButton = document.createElement("button"); deleteButton.type = "button"; - deleteButton.classList.add("button", "small", "jsTooltip", "condition-button-remove"); + deleteButton.classList.add("button", "small", "jsTooltip", "condition__remove"); deleteButton.title = (0, Language_1.getPhrase)("wcf.global.button.delete"); const icon = document.createElement("fa-icon"); icon.setIcon("times"); diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index 29b94946e76..78d0a58f30f 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -60,7 +60,7 @@ final public function getConditionFormField(string $containerId, string $identif return FormContainer::create("{$id}_container") ->removeClass("section") - ->addClass("condition-container") + ->addClass("condition__container") ->attribute("data-container-id", $containerId) ->attribute("data-condition-type", $identifier) ->attribute("data-condition-index", (string)$index) diff --git a/wcfsetup/install/files/style/ui/condition.scss b/wcfsetup/install/files/style/ui/condition.scss index 25651446043..d018ea92d99 100644 --- a/wcfsetup/install/files/style/ui/condition.scss +++ b/wcfsetup/install/files/style/ui/condition.scss @@ -1,13 +1,13 @@ -.condition-container { +.condition__container { display: flex; flex-direction: row; } -.condition-button-remove { +.condition__remove { flex: 0 30px; } -.condition-container > dl { +.condition__container > dl { flex: 1; display: flex; flex-direction: row; From 70403b232ace1b6897cdc71bab3e45fbcad71aeb Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:44:08 +0200 Subject: [PATCH 27/82] Refactor ConditionFormField to remove lastConditionIndex and adjust index handling --- .../templates/shared_conditionFormContainer.tpl | 2 +- .../Core/Form/Builder/Container/ConditionFormField.ts | 7 +++---- .../Core/Form/Builder/Container/ConditionFormField.js | 7 +++---- .../builder/container/ConditionFormContainer.class.php | 10 ---------- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl index 439b9bd5556..bf7fabb2f1f 100644 --- a/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl +++ b/com.woltlab.wcf/templates/shared_conditionFormContainer.tpl @@ -31,6 +31,6 @@ 'WoltLabSuite/Core/Form/Builder/Container/ConditionFormField', ], (DefaultContainerDependency, { ConditionFormField }) => { new DefaultContainerDependency('{unsafe:$container->getPrefixedId()|encodeJS}Container'); - new ConditionFormField('{unsafe:$container->getPrefixedId()|encodeJS}', '{link controller="ConditionAdd" isACP=false provider=$container->getConditionProviderClass()}{/link}', {$container->getLastConditionIndex() + 1}); + new ConditionFormField('{unsafe:$container->getPrefixedId()|encodeJS}', '{link controller="ConditionAdd" isACP=false provider=$container->getConditionProviderClass()}{/link}'); }); diff --git a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts index 13e4fae6b75..3014325cadc 100644 --- a/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts +++ b/ts/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.ts @@ -21,11 +21,10 @@ export class ConditionFormField { readonly #containerId: string; readonly #container: HTMLElement; readonly #button: HTMLButtonElement; - #index: number; + #index: number = 0; - constructor(containerId: string, endpoint: string, index: number) { + constructor(containerId: string, endpoint: string) { this.#containerId = containerId; - this.#index = index; this.#container = document.getElementById(`${containerId}Conditions`) as HTMLElement; this.#button = document.getElementById(`${containerId}AddCondition`) as HTMLButtonElement; @@ -62,7 +61,7 @@ export class ConditionFormField { async #showConditionAddDialog(endpoint: string) { const url = new URL(unescapeHTML(endpoint)); url.searchParams.set("containerId", this.#containerId); - url.searchParams.set("index", this.#index.toString()); + url.searchParams.set("index", (this.#index + 1).toString()); const { ok, result } = await dialogFactory().usingFormBuilder().fromEndpoint(url.toString()); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js index fe2ad65df97..33a98b563a8 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Container/ConditionFormField.js @@ -12,10 +12,9 @@ define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabS #containerId; #container; #button; - #index; - constructor(containerId, endpoint, index) { + #index = 0; + constructor(containerId, endpoint) { this.#containerId = containerId; - this.#index = index; this.#container = document.getElementById(`${containerId}Conditions`); this.#button = document.getElementById(`${containerId}AddCondition`); this.#button?.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(async () => { @@ -45,7 +44,7 @@ define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabS async #showConditionAddDialog(endpoint) { const url = new URL((0, StringUtil_1.unescapeHTML)(endpoint)); url.searchParams.set("containerId", this.#containerId); - url.searchParams.set("index", this.#index.toString()); + url.searchParams.set("index", (this.#index + 1).toString()); const { ok, result } = await (0, Dialog_1.dialogFactory)().usingFormBuilder().fromEndpoint(url.toString()); if (ok) { (0, Util_1.insertHtml)(result.field, this.#container, "append"); diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php index ce29d77a2ec..eddeb744669 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -28,7 +28,6 @@ final class ConditionFormContainer extends FormContainer * @inheritDoc */ protected $templateName = 'shared_conditionFormContainer'; - private int $lastConditionIndex = 0; /** * @var ConditionProvider @@ -63,8 +62,6 @@ public function readValues(): static foreach ($conditions as $index => $identifier) { $this->appendCondition($identifier, $index); - - $this->lastConditionIndex = \max($this->lastConditionIndex, $index); } } @@ -82,8 +79,6 @@ public function updatedObject(array $data, IStorableObject $object, $loadValues $containers[] = $this->appendCondition($condition['identifier'], $index); $fieldId = $this->getConditionProvider()->getFieldId($this->getPrefixedId(), $condition['identifier'], $index); $data[$fieldId] = $condition['value']; - - $this->lastConditionIndex = \max($this->lastConditionIndex, $index); } foreach ($containers as $container) { @@ -174,9 +169,4 @@ public function getConditionProviderClass(): string return $this->conditionProvider::class; } - - public function getLastConditionIndex(): int - { - return $this->lastConditionIndex; - } } From 02cc5841fc632a2982520ad4f0255fac61117c45 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 10 Jun 2025 10:35:40 +0200 Subject: [PATCH 28/82] Refactor `ConditionFormContainer` for improved PHPStan compatibility --- .../container/ConditionFormContainer.class.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php index eddeb744669..dad15b6b305 100644 --- a/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php +++ b/wcfsetup/install/files/lib/system/form/builder/container/ConditionFormContainer.class.php @@ -4,7 +4,6 @@ use wcf\data\IStorableObject; use wcf\system\condition\provider\AbstractConditionProvider; -use wcf\system\condition\type\IConditionType; use wcf\system\form\builder\data\processor\CustomFormDataProcessor; use wcf\system\form\builder\field\IFormField; use wcf\system\form\builder\field\TDefaultIdFormField; @@ -17,8 +16,6 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @phpstan-type ConditionProvider AbstractConditionProvider> */ final class ConditionFormContainer extends FormContainer { @@ -30,7 +27,7 @@ final class ConditionFormContainer extends FormContainer protected $templateName = 'shared_conditionFormContainer'; /** - * @var ConditionProvider + * @phpstan-ignore missingType.generics */ protected AbstractConditionProvider $conditionProvider; @@ -134,9 +131,7 @@ static function (IFormDocument $document, array $parameters) use ($prefixId, $id } /** - * @param ConditionProvider $conditionProvider - * - * @return self + * @phpstan-ignore missingType.generics */ public function conditionProvider(AbstractConditionProvider $conditionProvider): self { @@ -146,7 +141,7 @@ public function conditionProvider(AbstractConditionProvider $conditionProvider): } /** - * @return ConditionProvider + * @phpstan-ignore missingType.generics */ public function getConditionProvider(): AbstractConditionProvider { From 5fbc87108f02cbe869f95dd2361d2f79f5af9893 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 09:04:47 +0200 Subject: [PATCH 29/82] Sort condition types by name --- .../lib/action/ConditionAddAction.class.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php index a31d8d85b03..ed79a16db73 100644 --- a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php +++ b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php @@ -82,18 +82,22 @@ private function getForm(AbstractConditionProvider $provider): Psr15DialogForm self::class, WCF::getLanguage()->get('wcf.condition.add') ); + $options = \array_map( + static fn (IConditionType $conditionType) => WCF::getLanguage()->get($conditionType->getLabel()), + $provider->getConditionTypes() + ); + $collator = new \Collator(WCF::getLanguage()->getLocale()); + \usort( + $options, + static fn (string $a, string $b) => $collator->compare($a, $b) + ); $form->appendChild( SingleSelectionFormField::create('conditionType') ->label('wcf.condition.condition') ->filterable() ->required() - ->options( - \array_map( - static fn (IConditionType $conditionType) => WCF::getLanguage()->get($conditionType->getLabel()), - $provider->getConditionTypes() - ) - ) + ->options($options) ); $form->markRequiredFields(false); From ccec4f48d97614e8bdf800f2761f6710fa53ce70 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 09:04:55 +0200 Subject: [PATCH 30/82] Add user condition types for email, language, avatar, and signature --- .../provider/UserConditionProvider.class.php | 8 ++ .../user/UserAvatarConditionType.class.php | 62 ++++++++++++ .../user/UserEmailConditionType.class.php | 96 +++++++++++++++++++ .../user/UserLanguageConditionType.class.php | 60 ++++++++++++ .../user/UserSignatureConditionType.class.php | 68 +++++++++++++ wcfsetup/install/lang/de.xml | 4 + wcfsetup/install/lang/en.xml | 4 + 7 files changed, 302 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 2fdfcc69585..29e922176dc 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -7,10 +7,14 @@ use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; +use wcf\system\condition\type\user\UserAvatarConditionType; +use wcf\system\condition\type\user\UserEmailConditionType; use wcf\system\condition\type\user\UserInGroupConditionType; +use wcf\system\condition\type\user\UserLanguageConditionType; use wcf\system\condition\type\user\UserNotInGroupConditionType; use wcf\system\condition\type\user\UserRegistrationDateConditionType; use wcf\system\condition\type\user\UserRegistrationDaysConditionType; +use wcf\system\condition\type\user\UserSignatureConditionType; use wcf\system\condition\type\user\UserUsernameConditionType; use wcf\system\event\EventHandler; @@ -28,10 +32,14 @@ public function __construct() { $this->addConditions([ new UserUsernameConditionType(), + new UserEmailConditionType(), new UserRegistrationDateConditionType(), new UserRegistrationDaysConditionType(), new UserInGroupConditionType(), new UserNotInGroupConditionType(), + new UserLanguageConditionType(), + new UserAvatarConditionType(), + new UserSignatureConditionType(), ]); EventHandler::getInstance()->fire( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php new file mode 100644 index 00000000000..f68c2cfa593 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php @@ -0,0 +1,62 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, bool> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserAvatarConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): BooleanFormField + { + return BooleanFormField::create($id); + } + + #[\Override] + public function getIdentifier(): string + { + return 'avatar'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.avatar'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.avatarFileID IS NULL"); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.avatarFileID IS NOT NULL"); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return $object->avatarFileID !== null; + } else { + return $object->avatarFileID === null; + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php new file mode 100644 index 00000000000..c8e9f509f14 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php @@ -0,0 +1,96 @@ + + * @since 6.3 + * + * @phpstan-type Filter = array{condition: string, value: string} + * @implements IDatabaseObjectListConditionType, Filter> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserEmailConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): PrefixConditionFormFieldContainer + { + return PrefixConditionFormFieldContainer::create($id) + ->field( + TextFormField::create("{$id}Value") + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options($this->getConditions()) + ->required() + ); + } + + #[\Override] + public function getIdentifier(): string + { + return 'email'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.email'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + ["condition" => $condition, "value" => $value] = $this->filter; + $filter = match ($condition) { + "_%" => $value . '%', + "%_%" => '%' . $value . '%', + "%_" => '%' . $value, + default => '', + }; + + $objectList->getConditionBuilder()->add( + $objectList->getDatabaseTableAlias() . '.email LIKE ?', + [$filter] + ); + } + + #[\Override] + public function matches(object $object): bool + { + ["condition" => $condition, "value" => $value] = $this->filter; + + return match ($condition) { + "_%" => \str_starts_with($object->email, $value), + "%_%" => \str_contains($object->email, $value), + "%_" => \str_ends_with($object->email, $value), + default => false, + }; + } + + /** + * @return array + */ + private function getConditions(): array + { + return [ + "_%" => "wcf.condition.startsWith", + "%_%" => "wcf.condition.contains", + "%_" => "wcf.condition.endsWith", + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php new file mode 100644 index 00000000000..bd68f9dd1c1 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php @@ -0,0 +1,60 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, int> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserLanguageConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): SelectFormField + { + return SelectFormField::create($id) + ->options(LanguageFactory::getInstance()->getLanguages()) + ->required(); + } + + #[\Override] + public function getIdentifier(): string + { + return 'language'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.language'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.languageID = ?", + [$this->filter] + ); + } + + #[\Override] + public function matches(object $object): bool + { + return $this->filter === $object->languageID; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php new file mode 100644 index 00000000000..fd3bdb9be71 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -0,0 +1,68 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, bool> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserSignatureConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): BooleanFormField + { + return BooleanFormField::create($id); + } + + #[\Override] + public function getIdentifier(): string + { + return 'signature'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.signature'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add( + "({$objectList->getDatabaseTableAlias()}.signature = ? OR {$objectList->getDatabaseTableAlias()}.signature IS NULL)", + [''] + ); + } else { + $objectList->getConditionBuilder()->add( + "({$objectList->getDatabaseTableAlias()}.signature <> ? AND {$objectList->getDatabaseTableAlias()}.signature IS NOT NULL)", + [''] + ); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return $object->signature === '' || $object->signature === null; + } else { + return $object->signature !== '' && $object->signature !== null; + } + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index cf61a85b3bf..c25164d2541 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3539,6 +3539,10 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 12ba0bd5757..b8cf5be0d09 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3462,6 +3462,10 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + + + + From 80bfb6b4086cc9e245b0405f193f70af4a0a26b9 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 09:15:49 +0200 Subject: [PATCH 31/82] Implement an `AbstractUserBooleanConditionType` and coverPhoto condition type for users --- .../provider/UserConditionProvider.class.php | 2 + ...AbstractUserBooleanConditionType.class.php | 68 +++++++++++++++++++ .../user/UserAvatarConditionType.class.php | 51 +------------- .../UserCoverPhotoConditionType.class.php | 17 +++++ .../user/UserSignatureConditionType.class.php | 29 +------- wcfsetup/install/lang/de.xml | 1 + wcfsetup/install/lang/en.xml | 1 + 7 files changed, 95 insertions(+), 74 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 29e922176dc..4163c729cb3 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -8,6 +8,7 @@ use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\condition\type\user\UserAvatarConditionType; +use wcf\system\condition\type\user\UserCoverPhotoConditionType; use wcf\system\condition\type\user\UserEmailConditionType; use wcf\system\condition\type\user\UserInGroupConditionType; use wcf\system\condition\type\user\UserLanguageConditionType; @@ -40,6 +41,7 @@ public function __construct() new UserLanguageConditionType(), new UserAvatarConditionType(), new UserSignatureConditionType(), + new UserCoverPhotoConditionType(), ]); EventHandler::getInstance()->fire( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php new file mode 100644 index 00000000000..b84c5e921c3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -0,0 +1,68 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, bool> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +abstract class AbstractUserBooleanConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + public function __construct( + public readonly string $id, + public readonly string $columnName + ) { + } + + #[\Override] + public function getIdentifier(): string + { + return $this->id; + } + + #[\Override] + public function getLabel(): string + { + return "wcf.condition.user.{$this->id}"; + } + + #[\Override] + public function getFormField(string $id): BooleanFormField + { + return BooleanFormField::create($id); + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NULL"); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NOT NULL"); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return $object->{$this->columnName} !== null; + } else { + return $object->{$this->columnName} === null; + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php index f68c2cfa593..f52605d3a7e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php @@ -2,61 +2,16 @@ namespace wcf\system\condition\type\user; -use wcf\data\DatabaseObjectList; -use wcf\data\user\User; -use wcf\data\user\UserList; -use wcf\system\condition\type\AbstractConditionType; -use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\BooleanFormField; - /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @implements IDatabaseObjectListConditionType, bool> - * @implements IObjectConditionType - * @extends AbstractConditionType */ -final class UserAvatarConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserAvatarConditionType extends AbstractUserBooleanConditionType { - #[\Override] - public function getFormField(string $id): BooleanFormField - { - return BooleanFormField::create($id); - } - - #[\Override] - public function getIdentifier(): string - { - return 'avatar'; - } - - #[\Override] - public function getLabel(): string - { - return 'wcf.condition.user.avatar'; - } - - #[\Override] - public function applyFilter(DatabaseObjectList $objectList): void - { - if ($this->filter) { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.avatarFileID IS NULL"); - } else { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.avatarFileID IS NOT NULL"); - } - } - - #[\Override] - public function matches(object $object): bool + public function __construct() { - if ($this->filter) { - return $object->avatarFileID !== null; - } else { - return $object->avatarFileID === null; - } + parent::__construct("avatar", 'avatarFileID'); } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php new file mode 100644 index 00000000000..49b35893198 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php @@ -0,0 +1,17 @@ + + * @since 6.3 + */ +final class UserCoverPhotoConditionType extends AbstractUserBooleanConditionType +{ + public function __construct() + { + parent::__construct("coverPhoto", 'coverPhotoFileID'); + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index fd3bdb9be71..244663ec681 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -3,41 +3,18 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; -use wcf\data\user\User; -use wcf\data\user\UserList; -use wcf\system\condition\type\AbstractConditionType; -use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\field\BooleanFormField; /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @implements IDatabaseObjectListConditionType, bool> - * @implements IObjectConditionType - * @extends AbstractConditionType */ -final class UserSignatureConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserSignatureConditionType extends AbstractUserBooleanConditionType { - #[\Override] - public function getFormField(string $id): BooleanFormField - { - return BooleanFormField::create($id); - } - - #[\Override] - public function getIdentifier(): string - { - return 'signature'; - } - - #[\Override] - public function getLabel(): string + public function __construct() { - return 'wcf.condition.user.signature'; + parent::__construct('signature', 'signature'); } #[\Override] diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index c25164d2541..b2ec4c9f964 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3543,6 +3543,7 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index b8cf5be0d09..48b6707339e 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3466,6 +3466,7 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + From f7b2a533dd1d41ab0ffbff4cdb8abee6db000b9d Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:03:19 +0200 Subject: [PATCH 32/82] Add abstract user string condition type and refactor email/username conditions --- .../AbstractUserStringConditionType.class.php | 102 ++++++++++++++++++ .../user/UserEmailConditionType.class.php | 85 +-------------- .../user/UserUsernameConditionType.class.php | 85 +-------------- 3 files changed, 108 insertions(+), 164 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php new file mode 100644 index 00000000000..3a2ca9165f7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -0,0 +1,102 @@ + + * @since 6.3 + * + * @phpstan-type Filter = array{condition: string, value: string} + * @implements IDatabaseObjectListConditionType, Filter> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +abstract class AbstractUserStringConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + public function __construct( + public readonly string $identifier, + public readonly string $columnName + ) { + } + + #[\Override] + public function getFormField(string $id): PrefixConditionFormFieldContainer + { + return PrefixConditionFormFieldContainer::create($id) + ->field( + TextFormField::create("{$id}Value") + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options($this->getConditions()) + ->required() + ); + } + + #[\Override] + public function getIdentifier(): string + { + return $this->identifier; + } + + #[\Override] + public function getLabel(): string + { + return "wcf.condition.user.{$this->identifier}"; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + ["condition" => $condition, "value" => $value] = $this->filter; + $filter = match ($condition) { + "_%" => $value . '%', + "%_%" => '%' . $value . '%', + "%_" => '%' . $value, + default => '', + }; + + $objectList->getConditionBuilder()->add( + $objectList->getDatabaseTableAlias() . ".{$this->columnName} LIKE ?", + [$filter] + ); + } + + #[\Override] + public function matches(object $object): bool + { + ["condition" => $condition, "value" => $value] = $this->filter; + + return match ($condition) { + "_%" => \str_starts_with($object->{$this->columnName}, $value), + "%_%" => \str_contains($object->{$this->columnName}, $value), + "%_" => \str_ends_with($object->{$this->columnName}, $value), + default => false, + }; + } + + /** + * @return array + */ + private function getConditions(): array + { + return [ + "_%" => "wcf.condition.startsWith", + "%_%" => "wcf.condition.contains", + "%_" => "wcf.condition.endsWith", + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php index c8e9f509f14..71a684263ba 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php @@ -2,95 +2,16 @@ namespace wcf\system\condition\type\user; -use wcf\data\DatabaseObjectList; -use wcf\data\user\User; -use wcf\data\user\UserList; -use wcf\system\condition\type\AbstractConditionType; -use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; -use wcf\system\form\builder\field\SingleSelectionFormField; -use wcf\system\form\builder\field\TextFormField; - /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @phpstan-type Filter = array{condition: string, value: string} - * @implements IDatabaseObjectListConditionType, Filter> - * @implements IObjectConditionType - * @extends AbstractConditionType */ -final class UserEmailConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserEmailConditionType extends AbstractUserStringConditionType { - #[\Override] - public function getFormField(string $id): PrefixConditionFormFieldContainer - { - return PrefixConditionFormFieldContainer::create($id) - ->field( - TextFormField::create("{$id}Value") - ->required() - ) - ->prefixField( - SingleSelectionFormField::create("{$id}Condition") - ->options($this->getConditions()) - ->required() - ); - } - - #[\Override] - public function getIdentifier(): string - { - return 'email'; - } - - #[\Override] - public function getLabel(): string - { - return 'wcf.condition.user.email'; - } - - #[\Override] - public function applyFilter(DatabaseObjectList $objectList): void - { - ["condition" => $condition, "value" => $value] = $this->filter; - $filter = match ($condition) { - "_%" => $value . '%', - "%_%" => '%' . $value . '%', - "%_" => '%' . $value, - default => '', - }; - - $objectList->getConditionBuilder()->add( - $objectList->getDatabaseTableAlias() . '.email LIKE ?', - [$filter] - ); - } - - #[\Override] - public function matches(object $object): bool - { - ["condition" => $condition, "value" => $value] = $this->filter; - - return match ($condition) { - "_%" => \str_starts_with($object->email, $value), - "%_%" => \str_contains($object->email, $value), - "%_" => \str_ends_with($object->email, $value), - default => false, - }; - } - - /** - * @return array - */ - private function getConditions(): array + public function __construct() { - return [ - "_%" => "wcf.condition.startsWith", - "%_%" => "wcf.condition.contains", - "%_" => "wcf.condition.endsWith", - ]; + parent::__construct('email', 'email'); } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php index 67952006dd7..0d22d1c4fe1 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php @@ -2,95 +2,16 @@ namespace wcf\system\condition\type\user; -use wcf\data\DatabaseObjectList; -use wcf\data\user\User; -use wcf\data\user\UserList; -use wcf\system\condition\type\AbstractConditionType; -use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IObjectConditionType; -use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; -use wcf\system\form\builder\field\SingleSelectionFormField; -use wcf\system\form\builder\field\TextFormField; - /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @phpstan-type Filter = array{condition: string, value: string} - * @implements IDatabaseObjectListConditionType, Filter> - * @implements IObjectConditionType - * @extends AbstractConditionType */ -final class UserUsernameConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserUsernameConditionType extends AbstractUserStringConditionType { - #[\Override] - public function getFormField(string $id): PrefixConditionFormFieldContainer - { - return PrefixConditionFormFieldContainer::create($id) - ->field( - TextFormField::create("{$id}Value") - ->required() - ) - ->prefixField( - SingleSelectionFormField::create("{$id}Condition") - ->options($this->getConditions()) - ->required() - ); - } - - #[\Override] - public function getIdentifier(): string - { - return 'username'; - } - - #[\Override] - public function getLabel(): string - { - return 'wcf.condition.user.username'; - } - - #[\Override] - public function applyFilter(DatabaseObjectList $objectList): void - { - ["condition" => $condition, "value" => $value] = $this->filter; - $filter = match ($condition) { - "_%" => $value . '%', - "%_%" => '%' . $value . '%', - "%_" => '%' . $value, - default => '', - }; - - $objectList->getConditionBuilder()->add( - $objectList->getDatabaseTableAlias() . '.username LIKE ?', - [$filter] - ); - } - - #[\Override] - public function matches(object $object): bool - { - ["condition" => $condition, "value" => $value] = $this->filter; - - return match ($condition) { - "_%" => \str_starts_with($object->username, $value), - "%_%" => \str_contains($object->username, $value), - "%_" => \str_ends_with($object->username, $value), - default => false, - }; - } - - /** - * @return array - */ - private function getConditions(): array + public function __construct() { - return [ - "_%" => "wcf.condition.startsWith", - "%_%" => "wcf.condition.contains", - "%_" => "wcf.condition.endsWith", - ]; + parent::__construct('username', 'username'); } } From 9f87171133e1854a2c3af7ccb1137958810e80b3 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:03:37 +0200 Subject: [PATCH 33/82] Improve sorting logic --- wcfsetup/install/files/lib/action/ConditionAddAction.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php index ed79a16db73..83c4c2772b7 100644 --- a/wcfsetup/install/files/lib/action/ConditionAddAction.class.php +++ b/wcfsetup/install/files/lib/action/ConditionAddAction.class.php @@ -87,7 +87,7 @@ private function getForm(AbstractConditionProvider $provider): Psr15DialogForm $provider->getConditionTypes() ); $collator = new \Collator(WCF::getLanguage()->getLocale()); - \usort( + \uasort( $options, static fn (string $a, string $b) => $collator->compare($a, $b) ); From 2510f26555657d56ae5287143a091c455ba8396d Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:04:05 +0200 Subject: [PATCH 34/82] Rename 'id' to 'identifier' in AbstractUserBooleanConditionType --- .../type/user/AbstractUserBooleanConditionType.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php index b84c5e921c3..f1f25aa0c9b 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -23,7 +23,7 @@ abstract class AbstractUserBooleanConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { public function __construct( - public readonly string $id, + public readonly string $identifier, public readonly string $columnName ) { } @@ -31,13 +31,13 @@ public function __construct( #[\Override] public function getIdentifier(): string { - return $this->id; + return $this->identifier; } #[\Override] public function getLabel(): string { - return "wcf.condition.user.{$this->id}"; + return "wcf.condition.user.{$this->identifier}"; } #[\Override] From cc6867b70a45f7f4cb1722ae8d8de8251e9b7efb Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:04:15 +0200 Subject: [PATCH 35/82] Fix condition handling in UserRegistrationDaysConditionType --- .../type/user/UserRegistrationDaysConditionType.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index 9d47d30e0ec..e4d58429daf 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -37,7 +37,7 @@ public function getFormField(string $id): PrefixConditionFormFieldContainer ) ->prefixField( SingleSelectionFormField::create("{$id}Condition") - ->options($this->getConditions()) + ->options(\array_combine($this->getConditions(), $this->getConditions())) ->required() ); } @@ -90,7 +90,7 @@ private function getParsedFilter(): array $date = DateUtil::getDateTimeByTimestamp(TIME_NOW); $date->setTimezone(new \DateTimeZone(TIMEZONE)); - $date->sub(new \DateInterval("P{$this->filter['condition']}D")); + $date->sub(new \DateInterval("P{$this->filter['value']}D")); return [ 'condition' => $this->filter['condition'], From dadd545541fa294e514bf3c1c4826abddb2929ee Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:15:09 +0200 Subject: [PATCH 36/82] Add user condition types for banned, email confirmation, and enabled status --- .../provider/UserConditionProvider.class.php | 6 +++ .../user/UserIsBannedConditionType.class.php | 39 +++++++++++++++++++ ...serIsEmailConfirmedConditionType.class.php | 17 ++++++++ .../user/UserIsEnabledConditionType.class.php | 39 +++++++++++++++++++ wcfsetup/install/lang/de.xml | 3 ++ wcfsetup/install/lang/en.xml | 3 ++ 6 files changed, 107 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 4163c729cb3..6fd2287d53a 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -11,6 +11,9 @@ use wcf\system\condition\type\user\UserCoverPhotoConditionType; use wcf\system\condition\type\user\UserEmailConditionType; use wcf\system\condition\type\user\UserInGroupConditionType; +use wcf\system\condition\type\user\UserIsBannedConditionType; +use wcf\system\condition\type\user\UserIsEmailConfirmedConditionType; +use wcf\system\condition\type\user\UserIsEnabledConditionType; use wcf\system\condition\type\user\UserLanguageConditionType; use wcf\system\condition\type\user\UserNotInGroupConditionType; use wcf\system\condition\type\user\UserRegistrationDateConditionType; @@ -42,6 +45,9 @@ public function __construct() new UserAvatarConditionType(), new UserSignatureConditionType(), new UserCoverPhotoConditionType(), + new UserIsBannedConditionType(), + new UserIsEnabledConditionType(), + new UserIsEmailConfirmedConditionType(), ]); EventHandler::getInstance()->fire( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php new file mode 100644 index 00000000000..46ead1b9636 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php @@ -0,0 +1,39 @@ + + * @since 6.3 + */ +final class UserIsBannedConditionType extends AbstractUserBooleanConditionType +{ + public function __construct() + { + parent::__construct("isBanned", 'banned'); + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.banned = ?", [1]); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.banned = ?", [0]); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return (bool)$object->banned; + } else { + return !$object->banned; + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php new file mode 100644 index 00000000000..dc230b6487b --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php @@ -0,0 +1,17 @@ + + * @since 6.3 + */ +final class UserIsEmailConfirmedConditionType extends AbstractUserBooleanConditionType +{ + public function __construct() + { + parent::__construct("isEmailConfirmed", 'emailConfirmed'); + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php new file mode 100644 index 00000000000..e5e368690f7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php @@ -0,0 +1,39 @@ + + * @since 6.3 + */ +final class UserIsEnabledConditionType extends AbstractUserBooleanConditionType +{ + public function __construct() + { + parent::__construct("isEnabled", 'activationCode'); + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.activationCode = ?", [0]); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.activationCode <> = ?", [0]); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return $object->activationCode !== 0; + } else { + return $object->activationCode === 0; + } + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index b2ec4c9f964..c4e4eeeaef4 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3544,6 +3544,9 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 48b6707339e..17b9658965b 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3467,6 +3467,9 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + + + From 455115e9929e72ea4aa6be9dc2091e1821bfa495 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:37:19 +0200 Subject: [PATCH 37/82] Add user trophy condition types --- .../provider/UserConditionProvider.class.php | 4 + .../UserHasNotTrophyConditionType.class.php | 90 +++++++++++++++++++ .../user/UserHasTrophyConditionType.class.php | 90 +++++++++++++++++++ wcfsetup/install/lang/de.xml | 2 + wcfsetup/install/lang/en.xml | 2 + 5 files changed, 188 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 6fd2287d53a..a8cab3ef906 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -10,6 +10,8 @@ use wcf\system\condition\type\user\UserAvatarConditionType; use wcf\system\condition\type\user\UserCoverPhotoConditionType; use wcf\system\condition\type\user\UserEmailConditionType; +use wcf\system\condition\type\user\UserHasNotTrophyConditionType; +use wcf\system\condition\type\user\UserHasTrophyConditionType; use wcf\system\condition\type\user\UserInGroupConditionType; use wcf\system\condition\type\user\UserIsBannedConditionType; use wcf\system\condition\type\user\UserIsEmailConfirmedConditionType; @@ -48,6 +50,8 @@ public function __construct() new UserIsBannedConditionType(), new UserIsEnabledConditionType(), new UserIsEmailConfirmedConditionType(), + new UserHasTrophyConditionType(), + new UserHasNotTrophyConditionType(), ]); EventHandler::getInstance()->fire( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php new file mode 100644 index 00000000000..986150b549b --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php @@ -0,0 +1,90 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, int> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserHasNotTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): SelectFormField + { + return SelectFormField::create($id) + ->options($this->getTrophies()) + ->required(); + } + + #[\Override] + public function getIdentifier(): string + { + return 'hasNotTrophy'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.hasNotTrophy'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.userID NOT IN ( + SELECT userID + FROM wcf1_user_trophy + WHERE trophyID IN = ? + )", + [$this->filter] + ); + } + + #[\Override] + public function matches(object $object): bool + { + $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; + $trophyIDs = \array_map(static function ($userTrophy) { + return $userTrophy->trophyID; + }, $userTrophies); + + return !\in_array($this->filter, $trophyIDs, true); + } + + /** + * @return Trophy[] + */ + private function getTrophies(): array + { + $trophyList = new TrophyList(); + $trophyList->readObjects(); + $trophies = $trophyList->getObjects(); + + $collator = new \Collator(WCF::getLanguage()->getLocale()); + \uasort( + $trophies, + static fn (Trophy $a, Trophy $b) => $collator->compare($a->getTitle(), $b->getTitle()) + ); + + return $trophies; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php new file mode 100644 index 00000000000..9924a1744fe --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php @@ -0,0 +1,90 @@ + + * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, int> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +final class UserHasTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + #[\Override] + public function getFormField(string $id): SelectFormField + { + return SelectFormField::create($id) + ->options($this->getTrophies()) + ->required(); + } + + #[\Override] + public function getIdentifier(): string + { + return 'hasTrophy'; + } + + #[\Override] + public function getLabel(): string + { + return 'wcf.condition.user.hasTrophy'; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}.userID IN ( + SELECT userID + FROM wcf1_user_trophy + WHERE trophyID = ? + )", + [$this->filter] + ); + } + + #[\Override] + public function matches(object $object): bool + { + $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; + $trophyIDs = \array_map(static function ($userTrophy) { + return $userTrophy->trophyID; + }, $userTrophies); + + return \in_array($this->filter, $trophyIDs, true); + } + + /** + * @return Trophy[] + */ + private function getTrophies(): array + { + $trophyList = new TrophyList(); + $trophyList->readObjects(); + $trophies = $trophyList->getObjects(); + + $collator = new \Collator(WCF::getLanguage()->getLocale()); + \uasort( + $trophies, + static fn (Trophy $a, Trophy $b) => $collator->compare($a->getTitle(), $b->getTitle()) + ); + + return $trophies; + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index c4e4eeeaef4..0c535927b6e 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3547,6 +3547,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 17b9658965b..004a32c076b 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3470,6 +3470,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + + From 9c7d4011af9b571443af3c12b333ad012e1c3e04 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 4 Jun 2025 10:37:44 +0200 Subject: [PATCH 38/82] Add user integer condition types for activity points, likes received, and trophy points --- .../provider/UserConditionProvider.class.php | 4 + ...AbstractUserIntegerConditionType.class.php | 91 +++++++++++++++++++ ...serRegistrationDaysConditionType.class.php | 1 + wcfsetup/install/lang/de.xml | 3 + wcfsetup/install/lang/en.xml | 3 + 5 files changed, 102 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index a8cab3ef906..39f04ebb685 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -7,6 +7,7 @@ use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; +use wcf\system\condition\type\user\AbstractUserIntegerConditionType; use wcf\system\condition\type\user\UserAvatarConditionType; use wcf\system\condition\type\user\UserCoverPhotoConditionType; use wcf\system\condition\type\user\UserEmailConditionType; @@ -52,6 +53,9 @@ public function __construct() new UserIsEmailConfirmedConditionType(), new UserHasTrophyConditionType(), new UserHasNotTrophyConditionType(), + new class("activityPoints", "activityPoints") extends AbstractUserIntegerConditionType {}, + new class("likesReceived", "likesReceived") extends AbstractUserIntegerConditionType {}, + new class("trophyPoints", "trophyPoints") extends AbstractUserIntegerConditionType {}, ]); EventHandler::getInstance()->fire( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php new file mode 100644 index 00000000000..087c5965e88 --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php @@ -0,0 +1,91 @@ + + * @since 6.3 + * + * @phpstan-type Filter = array{condition: string, value: int} + * @implements IDatabaseObjectListConditionType, Filter> + * @implements IObjectConditionType + * @extends AbstractConditionType + */ +abstract class AbstractUserIntegerConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +{ + public function __construct( + public readonly string $identifier, + public readonly string $columnName + ) { + } + + #[\Override] + public function getFormField(string $id): PrefixConditionFormFieldContainer + { + return PrefixConditionFormFieldContainer::create($id) + ->field( + IntegerFormField::create("{$id}Value") + ->minimum(0) + ->required() + ) + ->prefixField( + SingleSelectionFormField::create("{$id}Condition") + ->options(\array_combine($this->getConditions(), $this->getConditions())) + ->required() + ); + } + + #[\Override] + public function getIdentifier(): string + { + return $this->identifier; + } + + #[\Override] + public function getLabel(): string + { + return "wcf.condition.user.{$this->identifier}"; + } + + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + $objectList->getConditionBuilder()->add( + "{$objectList->getDatabaseTableAlias()}{$this->columnName} {$this->filter['condition']} ?", + [$this->filter['value']] + ); + } + + #[\Override] + public function matches(object $object): bool + { + return match ($this->filter['condition']) { + '=' => $object->{$this->columnName} == $this->filter['value'], + '>' => $object->{$this->columnName} < $this->filter['value'], + '<' => $object->{$this->columnName} > $this->filter['value'], + '>=' => $object->{$this->columnName} <= $this->filter['value'], + '<=' => $object->{$this->columnName} >= $this->filter['value'], + default => throw new \InvalidArgumentException("Unknown condition: {$this->filter['condition']}"), + }; + } + + /** + * @return string[] + */ + private function getConditions(): array + { + return ["=", ">", "<", ">=", "<="]; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index e4d58429daf..1daca2320b9 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -33,6 +33,7 @@ public function getFormField(string $id): PrefixConditionFormFieldContainer ->field( IntegerFormField::create("{$id}Value") ->suffix("wcf.acp.option.suffix.days") + ->minimum(1) ->required() ) ->prefixField( diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 0c535927b6e..585e642ce7c 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3547,6 +3547,9 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 004a32c076b..329ab2a023c 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3470,6 +3470,9 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + + + From 2b15f963242dfb172e9e2f861ddc2992feb9cee5 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 5 Jun 2025 11:57:05 +0200 Subject: [PATCH 39/82] Fix SQL query syntax and add TODO for user options conditions --- .../system/condition/provider/UserConditionProvider.class.php | 2 ++ .../condition/type/user/UserInGroupConditionType.class.php | 2 +- .../condition/type/user/UserIsEnabledConditionType.class.php | 2 +- .../condition/type/user/UserNotInGroupConditionType.class.php | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 39f04ebb685..2210ea08686 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -58,6 +58,8 @@ public function __construct() new class("trophyPoints", "trophyPoints") extends AbstractUserIntegerConditionType {}, ]); + // TODO add conditions for user options that implement `ISearchableConditionUserOption` + EventHandler::getInstance()->fire( new UserConditionProviderCollecting($this) ); diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index e41849a668e..cede62b0bd1 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -57,7 +57,7 @@ public function applyFilter(DatabaseObjectList $objectList): void SELECT userID FROM wcf1_user_to_group WHERE groupID = ? - )", + )", [$this->filter] ); } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php index e5e368690f7..35e28ef5039 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php @@ -23,7 +23,7 @@ public function applyFilter(DatabaseObjectList $objectList): void if ($this->filter) { $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.activationCode = ?", [0]); } else { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.activationCode <> = ?", [0]); + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.activationCode <> ?", [0]); } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index 59da956f17e..5a13ff902ff 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -57,7 +57,7 @@ public function applyFilter(DatabaseObjectList $objectList): void SELECT userID FROM wcf1_user_to_group WHERE groupID = ? - )", + )", [$this->filter] ); } From 8d3f738121f4a44361febb6d5805c50a2804c627 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 5 Jun 2025 12:02:17 +0200 Subject: [PATCH 40/82] Refactor user condition types to extend AbstractUserIsNullConditionType and update filter logic --- ...AbstractUserBooleanConditionType.class.php | 8 ++--- .../AbstractUserIsNullConditionType.class.php | 34 +++++++++++++++++++ .../user/UserAvatarConditionType.class.php | 2 +- .../UserCoverPhotoConditionType.class.php | 2 +- .../user/UserIsBannedConditionType.class.php | 22 ------------ ...serIsEmailConfirmedConditionType.class.php | 2 +- 6 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php index f1f25aa0c9b..ad6ec8aee91 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -50,9 +50,9 @@ public function getFormField(string $id): BooleanFormField public function applyFilter(DatabaseObjectList $objectList): void { if ($this->filter) { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NULL"); + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} = ?", [1]); } else { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NOT NULL"); + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} = ?", [0]); } } @@ -60,9 +60,9 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { if ($this->filter) { - return $object->{$this->columnName} !== null; + return (bool)$object->{$this->columnName}; } else { - return $object->{$this->columnName} === null; + return !$object->{$this->columnName}; } } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php new file mode 100644 index 00000000000..c97ffa4c35d --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php @@ -0,0 +1,34 @@ + + * @since 6.3 + */ +abstract class AbstractUserIsNullConditionType extends AbstractUserBooleanConditionType +{ + #[\Override] + public function applyFilter(DatabaseObjectList $objectList): void + { + if ($this->filter) { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NULL"); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NOT NULL"); + } + } + + #[\Override] + public function matches(object $object): bool + { + if ($this->filter) { + return $object->{$this->columnName} !== null; + } else { + return $object->{$this->columnName} === null; + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php index f52605d3a7e..d5aa44fc8d9 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php @@ -8,7 +8,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserAvatarConditionType extends AbstractUserBooleanConditionType +final class UserAvatarConditionType extends AbstractUserIsNullConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php index 49b35893198..9cd460599f9 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php @@ -8,7 +8,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserCoverPhotoConditionType extends AbstractUserBooleanConditionType +final class UserCoverPhotoConditionType extends AbstractUserIsNullConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php index 46ead1b9636..0c63b37d301 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php @@ -2,8 +2,6 @@ namespace wcf\system\condition\type\user; -use wcf\data\DatabaseObjectList; - /** * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH @@ -16,24 +14,4 @@ public function __construct() { parent::__construct("isBanned", 'banned'); } - - #[\Override] - public function applyFilter(DatabaseObjectList $objectList): void - { - if ($this->filter) { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.banned = ?", [1]); - } else { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.banned = ?", [0]); - } - } - - #[\Override] - public function matches(object $object): bool - { - if ($this->filter) { - return (bool)$object->banned; - } else { - return !$object->banned; - } - } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php index dc230b6487b..c25a0c15d40 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php @@ -8,7 +8,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserIsEmailConfirmedConditionType extends AbstractUserBooleanConditionType +final class UserIsEmailConfirmedConditionType extends AbstractUserIsNullConditionType { public function __construct() { From 4ea3ea2af188b91d87388f20945ad1ba3716b42c Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 5 Jun 2025 12:05:21 +0200 Subject: [PATCH 41/82] Refactor user condition types to use anonymous classes for improved flexibility --- .../provider/UserConditionProvider.class.php | 21 ++++++++----------- .../user/UserAvatarConditionType.class.php | 17 --------------- .../UserCoverPhotoConditionType.class.php | 17 --------------- .../user/UserEmailConditionType.class.php | 17 --------------- .../user/UserIsBannedConditionType.class.php | 17 --------------- ...serIsEmailConfirmedConditionType.class.php | 17 --------------- .../user/UserUsernameConditionType.class.php | 17 --------------- 7 files changed, 9 insertions(+), 114 deletions(-) delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php delete mode 100644 wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 2210ea08686..22d6e5187a1 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -7,22 +7,19 @@ use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; +use wcf\system\condition\type\user\AbstractUserBooleanConditionType; use wcf\system\condition\type\user\AbstractUserIntegerConditionType; -use wcf\system\condition\type\user\UserAvatarConditionType; -use wcf\system\condition\type\user\UserCoverPhotoConditionType; -use wcf\system\condition\type\user\UserEmailConditionType; +use wcf\system\condition\type\user\AbstractUserIsNullConditionType; +use wcf\system\condition\type\user\AbstractUserStringConditionType; use wcf\system\condition\type\user\UserHasNotTrophyConditionType; use wcf\system\condition\type\user\UserHasTrophyConditionType; use wcf\system\condition\type\user\UserInGroupConditionType; -use wcf\system\condition\type\user\UserIsBannedConditionType; -use wcf\system\condition\type\user\UserIsEmailConfirmedConditionType; use wcf\system\condition\type\user\UserIsEnabledConditionType; use wcf\system\condition\type\user\UserLanguageConditionType; use wcf\system\condition\type\user\UserNotInGroupConditionType; use wcf\system\condition\type\user\UserRegistrationDateConditionType; use wcf\system\condition\type\user\UserRegistrationDaysConditionType; use wcf\system\condition\type\user\UserSignatureConditionType; -use wcf\system\condition\type\user\UserUsernameConditionType; use wcf\system\event\EventHandler; /** @@ -38,19 +35,19 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new UserUsernameConditionType(), - new UserEmailConditionType(), + new class("username", "username") extends AbstractUserStringConditionType {}, + new class("email", "email") extends AbstractUserStringConditionType {}, new UserRegistrationDateConditionType(), new UserRegistrationDaysConditionType(), new UserInGroupConditionType(), new UserNotInGroupConditionType(), new UserLanguageConditionType(), - new UserAvatarConditionType(), + new class("avatar", 'avatarFileID') extends AbstractUserIsNullConditionType {}, new UserSignatureConditionType(), - new UserCoverPhotoConditionType(), - new UserIsBannedConditionType(), + new class("coverPhoto", 'coverPhotoFileID') extends AbstractUserIsNullConditionType {}, + new class("isBanned", 'banned') extends AbstractUserBooleanConditionType {}, new UserIsEnabledConditionType(), - new UserIsEmailConfirmedConditionType(), + new class("isEmailConfirmed", 'emailConfirmed') extends AbstractUserIsNullConditionType {}, new UserHasTrophyConditionType(), new UserHasNotTrophyConditionType(), new class("activityPoints", "activityPoints") extends AbstractUserIntegerConditionType {}, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php deleted file mode 100644 index d5aa44fc8d9..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserAvatarConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserAvatarConditionType extends AbstractUserIsNullConditionType -{ - public function __construct() - { - parent::__construct("avatar", 'avatarFileID'); - } -} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php deleted file mode 100644 index 9cd460599f9..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserCoverPhotoConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserCoverPhotoConditionType extends AbstractUserIsNullConditionType -{ - public function __construct() - { - parent::__construct("coverPhoto", 'coverPhotoFileID'); - } -} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php deleted file mode 100644 index 71a684263ba..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserEmailConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserEmailConditionType extends AbstractUserStringConditionType -{ - public function __construct() - { - parent::__construct('email', 'email'); - } -} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php deleted file mode 100644 index 0c63b37d301..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsBannedConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserIsBannedConditionType extends AbstractUserBooleanConditionType -{ - public function __construct() - { - parent::__construct("isBanned", 'banned'); - } -} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php deleted file mode 100644 index c25a0c15d40..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEmailConfirmedConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserIsEmailConfirmedConditionType extends AbstractUserIsNullConditionType -{ - public function __construct() - { - parent::__construct("isEmailConfirmed", 'emailConfirmed'); - } -} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php deleted file mode 100644 index 0d22d1c4fe1..00000000000 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserUsernameConditionType.class.php +++ /dev/null @@ -1,17 +0,0 @@ - - * @since 6.3 - */ -final class UserUsernameConditionType extends AbstractUserStringConditionType -{ - public function __construct() - { - parent::__construct('username', 'username'); - } -} From 6104e0bb75b72edbc35c71b6dd3cd4d7d6e82350 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 5 Jun 2025 12:08:13 +0200 Subject: [PATCH 42/82] Add multifactor authentication condition to user conditions --- .../system/condition/provider/UserConditionProvider.class.php | 1 + wcfsetup/install/lang/de.xml | 1 + wcfsetup/install/lang/en.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 22d6e5187a1..110d3ac3e48 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -48,6 +48,7 @@ public function __construct() new class("isBanned", 'banned') extends AbstractUserBooleanConditionType {}, new UserIsEnabledConditionType(), new class("isEmailConfirmed", 'emailConfirmed') extends AbstractUserIsNullConditionType {}, + new class("isMultifactorActive", 'multifactorActive') extends AbstractUserBooleanConditionType {}, new UserHasTrophyConditionType(), new UserHasNotTrophyConditionType(), new class("activityPoints", "activityPoints") extends AbstractUserIntegerConditionType {}, diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 585e642ce7c..feb120254af 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3552,6 +3552,7 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 329ab2a023c..cfaa778488b 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3475,6 +3475,7 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi + From 25d86b9778cdf7bdef52dbe3803d391199086b99 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 10 Jun 2025 10:33:47 +0200 Subject: [PATCH 43/82] Fix condition logic for user signature and null checks --- .../type/user/AbstractUserIsNullConditionType.class.php | 4 ++-- .../condition/type/user/UserSignatureConditionType.class.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php index c97ffa4c35d..1638e24a65a 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php @@ -16,9 +16,9 @@ abstract class AbstractUserIsNullConditionType extends AbstractUserBooleanCondit public function applyFilter(DatabaseObjectList $objectList): void { if ($this->filter) { - $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NULL"); - } else { $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NOT NULL"); + } else { + $objectList->getConditionBuilder()->add("{$objectList->getDatabaseTableAlias()}.{$this->columnName} IS NULL"); } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index 244663ec681..647099b51b2 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -22,12 +22,12 @@ public function applyFilter(DatabaseObjectList $objectList): void { if ($this->filter) { $objectList->getConditionBuilder()->add( - "({$objectList->getDatabaseTableAlias()}.signature = ? OR {$objectList->getDatabaseTableAlias()}.signature IS NULL)", + "({$objectList->getDatabaseTableAlias()}.signature <> ? AND {$objectList->getDatabaseTableAlias()}.signature IS NOT NULL)", [''] ); } else { $objectList->getConditionBuilder()->add( - "({$objectList->getDatabaseTableAlias()}.signature <> ? AND {$objectList->getDatabaseTableAlias()}.signature IS NOT NULL)", + "({$objectList->getDatabaseTableAlias()}.signature = ? OR {$objectList->getDatabaseTableAlias()}.signature IS NULL)", [''] ); } From 6fcefb9db3c07895c4412c630179b6dd13e0ee63 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Tue, 17 Jun 2025 12:28:39 +0200 Subject: [PATCH 44/82] Apply suggestions from code review Co-authored-by: Alexander Ebert --- .../type/user/UserHasNotTrophyConditionType.class.php | 6 ++---- .../type/user/UserHasTrophyConditionType.class.php | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php index 986150b549b..af8eddf66b5 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php @@ -53,7 +53,7 @@ public function applyFilter(DatabaseObjectList $objectList): void "{$objectList->getDatabaseTableAlias()}.userID NOT IN ( SELECT userID FROM wcf1_user_trophy - WHERE trophyID IN = ? + WHERE trophyID = ? )", [$this->filter] ); @@ -63,9 +63,7 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; - $trophyIDs = \array_map(static function ($userTrophy) { - return $userTrophy->trophyID; - }, $userTrophies); + $trophyIDs = \array_column($userTrophies, 'trophyID'); return !\in_array($this->filter, $trophyIDs, true); } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php index 9924a1744fe..23f3a53cf4d 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php @@ -63,9 +63,7 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; - $trophyIDs = \array_map(static function ($userTrophy) { - return $userTrophy->trophyID; - }, $userTrophies); + $trophyIDs = \array_column($userTrophies, 'trophyID'); return \in_array($this->filter, $trophyIDs, true); } From 9906fa224374baac7cd98add4ff093ebf4db343b Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 17 Jun 2025 12:30:13 +0200 Subject: [PATCH 45/82] Fix comparison operators --- .../type/user/AbstractUserIntegerConditionType.class.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php index 087c5965e88..38b47d8f66c 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php @@ -73,10 +73,10 @@ public function matches(object $object): bool { return match ($this->filter['condition']) { '=' => $object->{$this->columnName} == $this->filter['value'], - '>' => $object->{$this->columnName} < $this->filter['value'], - '<' => $object->{$this->columnName} > $this->filter['value'], - '>=' => $object->{$this->columnName} <= $this->filter['value'], - '<=' => $object->{$this->columnName} >= $this->filter['value'], + '>' => $object->{$this->columnName} > $this->filter['value'], + '<' => $object->{$this->columnName} < $this->filter['value'], + '>=' => $object->{$this->columnName} >= $this->filter['value'], + '<=' => $object->{$this->columnName} <= $this->filter['value'], default => throw new \InvalidArgumentException("Unknown condition: {$this->filter['condition']}"), }; } From 6362be905947068e8d748952b64d575996f6f3e9 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 09:16:48 +0200 Subject: [PATCH 46/82] Add missing `.` in the sql query --- .../type/user/AbstractUserIntegerConditionType.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php index 38b47d8f66c..e5ef789c3fc 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php @@ -63,7 +63,7 @@ public function getLabel(): string public function applyFilter(DatabaseObjectList $objectList): void { $objectList->getConditionBuilder()->add( - "{$objectList->getDatabaseTableAlias()}{$this->columnName} {$this->filter['condition']} ?", + "{$objectList->getDatabaseTableAlias()}.{$this->columnName} {$this->filter['condition']} ?", [$this->filter['value']] ); } From bd9747aa9e74d4c437de2aee5306e03dd3cd9ddf Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 09:18:30 +0200 Subject: [PATCH 47/82] Fix comparison logic --- .../type/user/UserRegistrationDaysConditionType.class.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index 1daca2320b9..79ae4cae94b 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -72,10 +72,10 @@ public function matches(object $object): bool ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); return match ($condition) { - '>' => $object->registrationDate < $timestamp, - '<' => $object->registrationDate > $timestamp, - '>=' => $object->registrationDate <= $timestamp, - '<=' => $object->registrationDate >= $timestamp, + '>' => $object->registrationDate > $timestamp, + '<' => $object->registrationDate < $timestamp, + '>=' => $object->registrationDate >= $timestamp, + '<=' => $object->registrationDate <= $timestamp, default => throw new \InvalidArgumentException("Unknown condition: {$condition}"), }; } From 67f02560528d2004b61094fb511732c6ff93c064 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 10:11:08 +0200 Subject: [PATCH 48/82] Fix matches logic --- .../condition/type/user/UserIsEnabledConditionType.class.php | 4 ++-- .../condition/type/user/UserSignatureConditionType.class.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php index 35e28ef5039..f689f18b007 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php @@ -31,9 +31,9 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { if ($this->filter) { - return $object->activationCode !== 0; - } else { return $object->activationCode === 0; + } else { + return $object->activationCode !== 0; } } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index 647099b51b2..28cef22b649 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -37,9 +37,9 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { if ($this->filter) { - return $object->signature === '' || $object->signature === null; - } else { return $object->signature !== '' && $object->signature !== null; + } else { + return $object->signature === '' || $object->signature === null; } } } From 1dd2d2d9a25377e68c1251241fc21bcf752908cd Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 10:18:57 +0200 Subject: [PATCH 49/82] Change Filter Type to string when `SelectFormField` is used --- .../user/UserHasNotTrophyConditionType.class.php | 12 +++++++----- .../type/user/UserHasTrophyConditionType.class.php | 12 +++++++----- .../type/user/UserInGroupConditionType.class.php | 12 +++++++----- .../type/user/UserLanguageConditionType.class.php | 12 +++++++----- .../type/user/UserNotInGroupConditionType.class.php | 12 +++++++----- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php index af8eddf66b5..f19179db225 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php @@ -20,15 +20,17 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType, int> - * @implements IObjectConditionType - * @extends AbstractConditionType + * @implements IDatabaseObjectListConditionType, string> + * @implements IObjectConditionType + * @extends AbstractConditionType */ final class UserHasNotTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): SelectFormField { + // SelectFormField stores its value as a string, + // so we need to convert it to an integer in the `applyFilter`&`matches` method. return SelectFormField::create($id) ->options($this->getTrophies()) ->required(); @@ -55,7 +57,7 @@ public function applyFilter(DatabaseObjectList $objectList): void FROM wcf1_user_trophy WHERE trophyID = ? )", - [$this->filter] + [(int)$this->filter] ); } @@ -65,7 +67,7 @@ public function matches(object $object): bool $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; $trophyIDs = \array_column($userTrophies, 'trophyID'); - return !\in_array($this->filter, $trophyIDs, true); + return !\in_array((int)$this->filter, $trophyIDs, true); } /** diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php index 23f3a53cf4d..795f6cc6b12 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php @@ -20,15 +20,17 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType, int> - * @implements IObjectConditionType - * @extends AbstractConditionType + * @implements IDatabaseObjectListConditionType, string> + * @implements IObjectConditionType + * @extends AbstractConditionType */ final class UserHasTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): SelectFormField { + // SelectFormField stores its value as a string, + // so we need to convert it to an integer in the `applyFilter`&`matches` method. return SelectFormField::create($id) ->options($this->getTrophies()) ->required(); @@ -55,7 +57,7 @@ public function applyFilter(DatabaseObjectList $objectList): void FROM wcf1_user_trophy WHERE trophyID = ? )", - [$this->filter] + [(int)$this->filter] ); } @@ -65,7 +67,7 @@ public function matches(object $object): bool $userTrophies = UserTrophyList::getUserTrophies([$object->userID], false)[$object->userID]; $trophyIDs = \array_column($userTrophies, 'trophyID'); - return \in_array($this->filter, $trophyIDs, true); + return \in_array((int)$this->filter, $trophyIDs, true); } /** diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index cede62b0bd1..8efc55355a6 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -17,15 +17,17 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType, int> - * @implements IObjectConditionType - * @extends AbstractConditionType + * @implements IDatabaseObjectListConditionType, string> + * @implements IObjectConditionType + * @extends AbstractConditionType */ final class UserInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): SelectFormField { + // SelectFormField stores its value as a string, + // so we need to convert it to an integer in the `applyFilter`&`matches` method. return SelectFormField::create($id) ->options( UserGroup::getGroupsByType(invalidGroupTypes: [ @@ -58,13 +60,13 @@ public function applyFilter(DatabaseObjectList $objectList): void FROM wcf1_user_to_group WHERE groupID = ? )", - [$this->filter] + [(int)$this->filter] ); } #[\Override] public function matches(object $object): bool { - return \in_array($this->filter, $object->getGroupIDs(), true); + return \in_array((int)$this->filter, $object->getGroupIDs(), true); } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php index bd68f9dd1c1..42cd3315b12 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php @@ -17,15 +17,17 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType, int> - * @implements IObjectConditionType - * @extends AbstractConditionType + * @implements IDatabaseObjectListConditionType, string> + * @implements IObjectConditionType + * @extends AbstractConditionType */ final class UserLanguageConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): SelectFormField { + // SelectFormField stores its value as a string, + // so we need to convert it to an integer in the `applyFilter`&`matches` method. return SelectFormField::create($id) ->options(LanguageFactory::getInstance()->getLanguages()) ->required(); @@ -48,13 +50,13 @@ public function applyFilter(DatabaseObjectList $objectList): void { $objectList->getConditionBuilder()->add( "{$objectList->getDatabaseTableAlias()}.languageID = ?", - [$this->filter] + [(int)$this->filter] ); } #[\Override] public function matches(object $object): bool { - return $this->filter === $object->languageID; + return (int)$this->filter === $object->languageID; } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index 5a13ff902ff..feb39859b47 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -17,15 +17,17 @@ * @license GNU Lesser General Public License * @since 6.3 * - * @implements IDatabaseObjectListConditionType, int> - * @implements IObjectConditionType - * @extends AbstractConditionType + * @implements IDatabaseObjectListConditionType, string> + * @implements IObjectConditionType + * @extends AbstractConditionType */ final class UserNotInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType { #[\Override] public function getFormField(string $id): SelectFormField { + // SelectFormField stores its value as a string, + // so we need to convert it to an integer in the `applyFilter`&`matches` method. return SelectFormField::create($id) ->options( UserGroup::getGroupsByType(invalidGroupTypes: [ @@ -58,13 +60,13 @@ public function applyFilter(DatabaseObjectList $objectList): void FROM wcf1_user_to_group WHERE groupID = ? )", - [$this->filter] + [(int)$this->filter] ); } #[\Override] public function matches(object $object): bool { - return !\in_array($this->filter, $object->getGroupIDs(), true); + return !\in_array((int)$this->filter, $object->getGroupIDs(), true); } } From 8b415f17f793653460103a0c2ed224941949a084 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 10:41:28 +0200 Subject: [PATCH 50/82] Escape special characters --- .../type/user/AbstractUserStringConditionType.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php index 3a2ca9165f7..25ebe2cbbde 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -62,6 +62,8 @@ public function getLabel(): string public function applyFilter(DatabaseObjectList $objectList): void { ["condition" => $condition, "value" => $value] = $this->filter; + $value = \addcslashes($value, '_%'); + $filter = match ($condition) { "_%" => $value . '%', "%_%" => '%' . $value . '%', From c482ad0fec015e8b393d52614449be7e869495a4 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 11:27:32 +0200 Subject: [PATCH 51/82] Compare strings case-insensitive --- .../type/user/AbstractUserStringConditionType.class.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php index 25ebe2cbbde..d92f8281e84 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -81,11 +81,13 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { ["condition" => $condition, "value" => $value] = $this->filter; + $value = \strtolower($value); + $objectValue = \strtolower($object->{$this->columnName}); return match ($condition) { - "_%" => \str_starts_with($object->{$this->columnName}, $value), - "%_%" => \str_contains($object->{$this->columnName}, $value), - "%_" => \str_ends_with($object->{$this->columnName}, $value), + "_%" => \str_starts_with($objectValue, $value), + "%_%" => \str_contains($objectValue, $value), + "%_" => \str_ends_with($objectValue, $value), default => false, }; } From aef3d3a5633ea6ce1be29e249f6979a5544e4e95 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 11:54:37 +0200 Subject: [PATCH 52/82] Fix condition comparison logic --- .../user/UserRegistrationDaysConditionType.class.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index 79ae4cae94b..73f5a090596 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -61,7 +61,7 @@ public function applyFilter(DatabaseObjectList $objectList): void ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); $objectList->getConditionBuilder()->add( - "{$objectList->getDatabaseTableAlias()}.registrationDate {$condition} ?", + "? {$condition} {$objectList->getDatabaseTableAlias()}.registrationDate", [$timestamp] ); } @@ -72,10 +72,10 @@ public function matches(object $object): bool ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); return match ($condition) { - '>' => $object->registrationDate > $timestamp, - '<' => $object->registrationDate < $timestamp, - '>=' => $object->registrationDate >= $timestamp, - '<=' => $object->registrationDate <= $timestamp, + '>' => $timestamp > $object->registrationDate, + '<' => $timestamp < $object->registrationDate, + '>=' => $timestamp >= $object->registrationDate, + '<=' => $timestamp <= $object->registrationDate, default => throw new \InvalidArgumentException("Unknown condition: {$condition}"), }; } From c2b7354e82275a0b00872c522b8160f4176bf8b1 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 5 Jun 2025 13:10:12 +0200 Subject: [PATCH 53/82] Add IMigrateConditionType interface and implement migration methods in user group condition types --- .../provider/UserConditionProvider.class.php | 32 +++++++++++++++++++ .../type/IMigrateConditionType.class.php | 21 ++++++++++++ .../user/UserInGroupConditionType.class.php | 23 ++++++++++++- .../UserNotInGroupConditionType.class.php | 23 ++++++++++++- 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 110d3ac3e48..e619fa23731 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -6,6 +6,7 @@ use wcf\data\user\UserList; use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\condition\type\user\AbstractUserBooleanConditionType; use wcf\system\condition\type\user\AbstractUserIntegerConditionType; @@ -62,4 +63,35 @@ public function __construct() new UserConditionProviderCollecting($this) ); } + + /** + * @param array $conditionData + * + * @return array{identifier: string, value: mixed}[] + */ + public function migrateConditionData(array $conditionData): array + { + if ($conditionData === []) { + return []; + } + + $result = []; + + foreach ($this->conditionTypes as $conditionType) { + if (!($conditionType instanceof IMigrateConditionType)) { + continue; + } + + \array_push( + $result, + ...$conditionType->migrateConditionData($conditionData) + ); + + if ($conditionData === []) { + break; + } + } + + return $result; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php new file mode 100644 index 00000000000..97b62b04c9c --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +interface IMigrateConditionType +{ + /** + * Converts the old condition structure to the new one. All migrated values must be removed from the `$conditionData` array. + * + * @param array $conditionData + * + * @return array{identifier: string, value: mixed}[] + */ + public function migrateConditionData(array &$conditionData): array; +} diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index 8efc55355a6..a57547bf31e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -8,6 +8,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\SelectFormField; @@ -21,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField @@ -69,4 +70,24 @@ public function matches(object $object): bool { return \in_array((int)$this->filter, $object->getGroupIDs(), true); } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['groupIDs'])) { + return []; + } + + $result = []; + foreach ($conditionData['groupIDs'] as $groupID) { + $result[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => $groupID, + ]; + } + + unset($conditionData['groupIDs']); + + return $result; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index feb39859b47..c24ee46fc62 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -8,6 +8,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\SelectFormField; @@ -21,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserNotInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserNotInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField @@ -69,4 +70,24 @@ public function matches(object $object): bool { return !\in_array((int)$this->filter, $object->getGroupIDs(), true); } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['notGroupIDs'])) { + return []; + } + + $result = []; + foreach ($conditionData['notGroupIDs'] as $groupID) { + $result[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => $groupID, + ]; + } + + unset($conditionData['notGroupIDs']); + + return $result; + } } From 9d1c9be85bb739ce59987e50c155161cb8aa40f6 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 10 Jun 2025 10:32:16 +0200 Subject: [PATCH 54/82] Implement migration support for user condition types --- .../condition/ConditionHandler.class.php | 82 +++++++++++++++++++ .../provider/ConditionMigration.class.php | 38 +++++++++ .../provider/UserConditionProvider.class.php | 36 +------- .../type/IMigrateConditionType.class.php | 2 + ...AbstractUserIntegerConditionType.class.php | 39 ++++++++- .../AbstractUserStringConditionType.class.php | 34 +++++++- .../user/UserInGroupConditionType.class.php | 6 ++ .../UserNotInGroupConditionType.class.php | 6 ++ ...serRegistrationDateConditionType.class.php | 48 ++++++++++- ...serRegistrationDaysConditionType.class.php | 48 +++-------- .../user/UserSignatureConditionType.class.php | 26 +++++- 11 files changed, 287 insertions(+), 78 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index 77b8fe62fa9..41718475dc8 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -9,9 +9,13 @@ use wcf\data\object\type\ObjectTypeCache; use wcf\system\cache\builder\ConditionCacheBuilder; use wcf\system\condition\provider\AbstractConditionProvider; +use wcf\system\condition\provider\ConditionMigration; use wcf\system\condition\type\IConditionType; +use wcf\system\condition\type\IMigrateConditionType; +use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; +use wcf\system\WCF; /** * Handles general condition-related matters. @@ -172,4 +176,82 @@ public function getConditionsWithFilter(AbstractConditionProvider $provider, arr return $result; } + + /** + * Exports the conditions for all objects that belong to the specified object type definition. + * + * @return array> + */ + public function exportConditions(string $definitionName): array + { + $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes($definitionName); + if ($objectTypes === []) { + return []; + } + + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('objectTypeID IN (?)', [ + \array_map(static fn (ObjectType $objectType): int => $objectType->objectTypeID, $objectTypes), + ]); + + $sql = "SELECT * + FROM wcf1_condition + {$conditionBuilder}"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute($conditionBuilder->getParameters()); + + $result = []; + while ($row = $statement->fetchArray()) { + $result[$row['objectID']] ??= []; + $result[$row['objectID']][ObjectTypeCache::getInstance()->getObjectType($row['objectTypeID'])->objectType] = \unserialize($row['conditionData']); + } + + return $result; + } + + /** + * The stored data from the `wcf1_condition` table is migrated to the new format. + * The key of `$conditionData` is the type of condition (objectType), the value is the content of the `wcf1_condition.conditionData` column, unserialize as an array. + * + * @template TCondition of IConditionType + * @param AbstractConditionProvider $provider + * @param array> $conditionData + */ + public function migrateConditionData(AbstractConditionProvider $provider, array $conditionData): ConditionMigration + { + if ($conditionData === []) { + return ConditionMigration::forNoConditions(); + } + + $migratedData = []; + /** @var IMigrateConditionType[] $conditionTypes */ + $conditionTypes = \array_filter( + $provider->getConditionTypes(), + static fn (IConditionType $condition): bool => $condition instanceof IMigrateConditionType + ); + + if ($conditionTypes === []) { + return ConditionMigration::forNoConditions(); + } + + foreach ($conditionData as $objectType => &$condition) { + foreach ($conditionTypes as $conditionType) { + if (!$conditionType->canMigrateConditionData($objectType)) { + continue; + } + + \array_push( + $migratedData, + ...$conditionType->migrateConditionData($condition) + ); + + if ($condition === []) { + unset($conditionData[$objectType]); + break; + } + } + } + + return ConditionMigration::for($conditionData, $migratedData); + } } diff --git a/wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php b/wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php new file mode 100644 index 00000000000..ac87bd9939a --- /dev/null +++ b/wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php @@ -0,0 +1,38 @@ + + * @since 6.2 + */ +final class ConditionMigration +{ + private function __construct( + public readonly bool $isFullMigrated, + /** @var array{identifier: string, value: mixed}[] */ + public readonly array $conditions = [], + ) { + } + + /** + * Creates a new ConditionMigration instance based on condition data and conditions. + * + * @param array{identifier: string, value: mixed}[] $previousConditionData + * @param array{identifier: string, value: mixed}[] $migratedConditionData + */ + public static function for(array $previousConditionData, array $migratedConditionData): self + { + return new self($previousConditionData === [], $migratedConditionData); + } + + /** + * Creates a new ConditionMigration instance for empty data. + */ + public static function forNoConditions(): self + { + return new self(true); + } +} diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index e619fa23731..6b5cd81671d 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -6,7 +6,6 @@ use wcf\data\user\UserList; use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\condition\type\user\AbstractUserBooleanConditionType; use wcf\system\condition\type\user\AbstractUserIntegerConditionType; @@ -36,8 +35,8 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new class("username", "username") extends AbstractUserStringConditionType {}, - new class("email", "email") extends AbstractUserStringConditionType {}, + new class("username", "username", "username", 'com.woltlab.wcf.username') extends AbstractUserStringConditionType {}, + new class("email", "email", "email", 'com.woltlab.wcf.email') extends AbstractUserStringConditionType {}, new UserRegistrationDateConditionType(), new UserRegistrationDaysConditionType(), new UserInGroupConditionType(), @@ -63,35 +62,4 @@ public function __construct() new UserConditionProviderCollecting($this) ); } - - /** - * @param array $conditionData - * - * @return array{identifier: string, value: mixed}[] - */ - public function migrateConditionData(array $conditionData): array - { - if ($conditionData === []) { - return []; - } - - $result = []; - - foreach ($this->conditionTypes as $conditionType) { - if (!($conditionType instanceof IMigrateConditionType)) { - continue; - } - - \array_push( - $result, - ...$conditionType->migrateConditionData($conditionData) - ); - - if ($conditionData === []) { - break; - } - } - - return $result; - } } diff --git a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php index 97b62b04c9c..0fa2fa80c23 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php @@ -18,4 +18,6 @@ interface IMigrateConditionType * @return array{identifier: string, value: mixed}[] */ public function migrateConditionData(array &$conditionData): array; + + public function canMigrateConditionData(string $objectType): bool; } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php index e5ef789c3fc..903ca0c50ca 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php @@ -7,6 +7,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; use wcf\system\form\builder\field\IntegerFormField; @@ -23,11 +24,12 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserIntegerConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +abstract class AbstractUserIntegerConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, - public readonly string $columnName + public readonly string $columnName, + public readonly ?string $migrateConditionObjectType = null, ) { } @@ -84,8 +86,39 @@ public function matches(object $object): bool /** * @return string[] */ - private function getConditions(): array + protected function getConditions(): array { return ["=", ">", "<", ">=", "<="]; } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $this->migrateConditionObjectType !== null && $objectType === $this->migrateConditionObjectType; + } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + $lessThan = $conditionData['lessThan'] ?? null; + $greaterThan = $conditionData['greaterThan'] ?? null; + $conditions = []; + + if ($lessThan !== null) { + $conditions[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => ["value" => $lessThan, 'condition' => '<'], + ]; + } + if ($greaterThan !== null) { + $conditions[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => ["value" => $greaterThan, 'condition' => '>'], + ]; + } + + unset($conditionData['lessThan'], $conditionData['greaterThan']); + + return $conditions; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php index d92f8281e84..ad6f9621df5 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -7,6 +7,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; use wcf\system\form\builder\field\SingleSelectionFormField; @@ -23,11 +24,13 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserStringConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +abstract class AbstractUserStringConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, - public readonly string $columnName + public readonly string $columnName, + public readonly ?string $migrateKeyName = null, + public readonly ?string $migrateConditionObjectType = null, ) { } @@ -103,4 +106,31 @@ private function getConditions(): array "%_" => "wcf.condition.endsWith", ]; } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $this->migrateConditionObjectType !== null && $objectType === $this->migrateConditionObjectType; + } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if ($this->migrateKeyName === null || !isset($conditionData[$this->migrateKeyName])) { + return []; + } + + $value = $conditionData[$this->migrateKeyName]; + unset($conditionData[$this->migrateKeyName]); + + return [ + [ + 'identifier' => $this->identifier, + 'value' => [ + 'condition' => "%_%", + 'value' => $value, + ], + ], + ]; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index a57547bf31e..b8ad6b5c9f4 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -71,6 +71,12 @@ public function matches(object $object): bool return \in_array((int)$this->filter, $object->getGroupIDs(), true); } + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.userGroup'; + } + #[\Override] public function migrateConditionData(array &$conditionData): array { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index c24ee46fc62..e17413c336f 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -71,6 +71,12 @@ public function matches(object $object): bool return !\in_array((int)$this->filter, $object->getGroupIDs(), true); } + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.userGroup'; + } + #[\Override] public function migrateConditionData(array &$conditionData): array { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php index dfaff458bfb..b63b1ff725c 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php @@ -7,6 +7,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; use wcf\system\form\builder\field\DateFormField; @@ -23,7 +24,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserRegistrationDateConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserRegistrationDateConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): PrefixConditionFormFieldContainer @@ -85,4 +86,49 @@ private function getConditions(): array { return [">", "<", ">=", "<="]; } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + $registrationDateStart = $conditionData['registrationDateStart'] ?? null; + $registrationDateEnd = $conditionData['registrationDateEnd'] ?? null; + $conditions = []; + + if ($registrationDateStart !== null) { + $conditions[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => [ + 'value' => $this->convertDateStringTimestamp($registrationDateStart, 0, 0, 0), + 'condition' => '>=', + ], + ]; + } + if ($registrationDateEnd !== null) { + $conditions[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => [ + 'value' => $this->convertDateStringTimestamp($registrationDateEnd, 23, 59, 59), + 'condition' => '<=', + ], + ]; + } + + unset($conditionData['registrationDateStart'], $conditionData['registrationDateEnd']); + + return $conditions; + } + + private function convertDateStringTimestamp(string $date, int $hour, int $minute, int $seconds): int + { + $dateTime = new \DateTime($date, new \DateTimeZone(TIMEZONE)); + $dateTime->setTime($hour, $minute, $seconds); + + return $dateTime->getTimestamp(); + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === "com.woltlab.wcf.registrationDate"; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index 73f5a090596..d4041e5e9a2 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -3,14 +3,8 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; -use wcf\data\user\User; -use wcf\data\user\UserList; -use wcf\system\condition\type\AbstractConditionType; -use wcf\system\condition\type\IDatabaseObjectListConditionType; -use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; use wcf\system\form\builder\field\IntegerFormField; -use wcf\system\form\builder\field\SingleSelectionFormField; use wcf\util\DateUtil; /** @@ -18,41 +12,23 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 - * - * @phpstan-type Filter = array{condition: string, value: int} - * @implements IDatabaseObjectListConditionType, Filter> - * @implements IObjectConditionType - * @extends AbstractConditionType */ -final class UserRegistrationDaysConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserRegistrationDaysConditionType extends AbstractUserIntegerConditionType { - #[\Override] - public function getFormField(string $id): PrefixConditionFormFieldContainer + public function __construct() { - return PrefixConditionFormFieldContainer::create($id) - ->field( - IntegerFormField::create("{$id}Value") - ->suffix("wcf.acp.option.suffix.days") - ->minimum(1) - ->required() - ) - ->prefixField( - SingleSelectionFormField::create("{$id}Condition") - ->options(\array_combine($this->getConditions(), $this->getConditions())) - ->required() - ); + parent::__construct('registrationDays', 'registrationDate', 'com.woltlab.wcf.registrationDateInterval'); } #[\Override] - public function getIdentifier(): string + public function getFormField(string $id): PrefixConditionFormFieldContainer { - return 'registrationDays'; - } + $container = parent::getFormField($id); + $filed = $container->getField(); + \assert($filed instanceof IntegerFormField); + $filed->suffix("wcf.acp.option.suffix.days"); - #[\Override] - public function getLabel(): string - { - return 'wcf.condition.user.registrationDays'; + return $container; } #[\Override] @@ -99,10 +75,8 @@ private function getParsedFilter(): array ]; } - /** - * @return string[] - */ - private function getConditions(): array + #[\Override] + protected function getConditions(): array { return [">", "<", ">=", "<="]; } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index 28cef22b649..197c029365f 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -3,6 +3,7 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; +use wcf\system\condition\type\IMigrateConditionType; /** * @author Olaf Braun @@ -10,7 +11,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserSignatureConditionType extends AbstractUserBooleanConditionType +final class UserSignatureConditionType extends AbstractUserBooleanConditionType implements IMigrateConditionType { public function __construct() { @@ -42,4 +43,27 @@ public function matches(object $object): bool return $object->signature === '' || $object->signature === null; } } + + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.signature'; + } + + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['userSignature'])) { + return []; + } + + $result = [ + [ + 'identifier' => $this->getIdentifier(), + 'value' => $conditionData['userSignature'] === 1, + ], + ]; + + unset($conditionData['userSignature']); + + return $result; + } } From 508c36fcdd57c53f271d74dba7258fec240bb9c7 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Tue, 10 Jun 2025 11:38:34 +0200 Subject: [PATCH 55/82] Add migration support for user group assignment conditions --- com.woltlab.wcf/package.xml | 8 ++- .../update_com.woltlab.wcf_6.3_step1.php | 2 + ...om.woltlab.wcf_6.3_userGroupAssignment.php | 23 +++++++++ .../UserGroupAssignmentEditForm.class.php | 6 +++ .../data/user/group/UserGroupEditor.class.php | 4 +- .../assignment/UserGroupAssignment.class.php | 1 + .../UserGroupAssignmentEditor.class.php | 4 +- .../UserGroupAssignmentCacheBuilder.class.php | 22 ++++---- .../eager/UserGroupAssignmentCache.class.php | 50 +++++++++++++++++++ .../provider/UserConditionProvider.class.php | 2 +- .../type/IMigrateConditionType.class.php | 2 +- ...AbstractUserBooleanConditionType.class.php | 31 +++++++++++- .../user/UserSignatureConditionType.class.php | 33 +++--------- .../UserGroupAssignmentCronjob.class.php | 4 +- .../UserGroupAssignmentHandler.class.php | 5 +- ...rGroupAssignmentMigrateCondition.class.php | 42 ++++++++++++++++ wcfsetup/setup/db/install.sql | 3 +- 17 files changed, 187 insertions(+), 55 deletions(-) create mode 100644 wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php create mode 100644 wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php create mode 100644 wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php diff --git a/com.woltlab.wcf/package.xml b/com.woltlab.wcf/package.xml index b0598667426..882df82c685 100644 --- a/com.woltlab.wcf/package.xml +++ b/com.woltlab.wcf/package.xml @@ -51,10 +51,8 @@ diff --git a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php index 8e18c78e57a..2ab540695d9 100644 --- a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php +++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php @@ -8,6 +8,7 @@ * @license GNU Lesser General Public License */ +use wcf\system\database\table\column\DefaultFalseBooleanDatabaseTableColumn; use wcf\system\database\table\column\MediumtextDatabaseTableColumn; use wcf\system\database\table\PartialDatabaseTable; @@ -15,5 +16,6 @@ PartialDatabaseTable::create('wcf1_user_group_assignment') ->columns([ MediumtextDatabaseTableColumn::create('conditions'), + DefaultFalseBooleanDatabaseTableColumn::create('needMigration'), ]), ]; diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php new file mode 100644 index 00000000000..28ca7588673 --- /dev/null +++ b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php @@ -0,0 +1,23 @@ +exportConditions("com.woltlab.wcf.condition.userGroupAssignment"); +if ($exportedConditions === []) { + return; +} + +$sql = "UPDATE wcf1_user_group_assignment + SET conditions = ?, + needMigration = ? + WHERE assignmentID = ?"; +$statement = WCF::getDB()->prepare($sql); +foreach ($exportedConditions as $assignmentID => $conditionData) { + $statement->execute([ + JSON::encode($conditionData), + 1, + $assignmentID, + ]); +} diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php index d9f4aa4cac0..4c3e12241ce 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php @@ -5,6 +5,7 @@ use wcf\acp\page\UserGroupAssignmentListPage; use wcf\data\user\group\assignment\UserGroupAssignment; use wcf\system\exception\IllegalLinkException; +use wcf\system\user\group\assignment\command\UserGroupAssignmentMigrateCondition; use wcf\system\interaction\admin\UserGroupAssignmentInteractions; use wcf\system\interaction\StandaloneInteractionContextMenuComponent; use wcf\system\request\LinkHandler; @@ -44,6 +45,11 @@ public function readParameters() if (!$this->formObject->assignmentID) { throw new IllegalLinkException(); } + + if ($this->formObject->needMigration) { + (new UserGroupAssignmentMigrateCondition($this->formObject))(); + $this->formObject = new UserGroupAssignment(\intval($_REQUEST['id'])); + } } /** diff --git a/wcfsetup/install/files/lib/data/user/group/UserGroupEditor.class.php b/wcfsetup/install/files/lib/data/user/group/UserGroupEditor.class.php index a2cb30115c1..80fe5869ea8 100644 --- a/wcfsetup/install/files/lib/data/user/group/UserGroupEditor.class.php +++ b/wcfsetup/install/files/lib/data/user/group/UserGroupEditor.class.php @@ -4,9 +4,9 @@ use wcf\data\DatabaseObjectEditor; use wcf\data\IEditableCachedObject; -use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; use wcf\system\cache\builder\UserGroupCacheBuilder; use wcf\system\cache\builder\UserGroupPermissionCacheBuilder; +use wcf\system\cache\eager\UserGroupAssignmentCache; use wcf\system\exception\SystemException; use wcf\system\user\storage\UserStorageHandler; use wcf\system\WCF; @@ -208,7 +208,7 @@ public static function resetCache() UserGroupPermissionCacheBuilder::getInstance()->reset(); // https://github.com/WoltLab/WCF/issues/4045 - UserGroupAssignmentCacheBuilder::getInstance()->reset(); + (new UserGroupAssignmentCache())->rebuild(); // Clear cached group assignments. UserStorageHandler::getInstance()->resetAll('groupIDs'); diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php index 545b1a7cd98..aaff99c0dd9 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php @@ -19,6 +19,7 @@ * @property-read string $title title of the automatic user group assignment * @property-read int $isDisabled is `1` if the user group assignment is disabled and thus not checked for automatic assignments, otherwise `0` * @property-read string $conditions JSON-encoded string containing the conditions of the automatic user group assignment + * @property-read bool $needMigration indicates whether the conditions need to be migrated to the new format */ class UserGroupAssignment extends DatabaseObject implements IRouteController { diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php index 371bbb319c4..ccadb8fb5c5 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignmentEditor.class.php @@ -4,7 +4,7 @@ use wcf\data\DatabaseObjectEditor; use wcf\data\IEditableCachedObject; -use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; +use wcf\system\cache\eager\UserGroupAssignmentCache; /** * Executes user group assignment-related actions. @@ -29,6 +29,6 @@ class UserGroupAssignmentEditor extends DatabaseObjectEditor implements IEditabl */ public static function resetCache() { - UserGroupAssignmentCacheBuilder::getInstance()->reset(); + (new UserGroupAssignmentCache())->rebuild(); } } diff --git a/wcfsetup/install/files/lib/system/cache/builder/UserGroupAssignmentCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/UserGroupAssignmentCacheBuilder.class.php index 589b86aa876..2283ec66597 100644 --- a/wcfsetup/install/files/lib/system/cache/builder/UserGroupAssignmentCacheBuilder.class.php +++ b/wcfsetup/install/files/lib/system/cache/builder/UserGroupAssignmentCacheBuilder.class.php @@ -2,7 +2,7 @@ namespace wcf\system\cache\builder; -use wcf\data\user\group\assignment\UserGroupAssignmentList; +use wcf\system\cache\eager\UserGroupAssignmentCache; /** * Caches the enabled automatic user group assignments. @@ -10,18 +10,20 @@ * @author Matthias Schmidt * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * + * @deprecated 6.2 use `UserGroupAssignmentCache` instead */ -class UserGroupAssignmentCacheBuilder extends AbstractCacheBuilder +final class UserGroupAssignmentCacheBuilder extends AbstractLegacyCacheBuilder { - /** - * @inheritDoc - */ - protected function rebuild(array $parameters) + #[\Override] + protected function rebuild(array $parameters): array { - $assignmentList = new UserGroupAssignmentList(); - $assignmentList->getConditionBuilder()->add('isDisabled = ?', [0]); - $assignmentList->readObjects(); + return (new UserGroupAssignmentCache())->getCache(); + } - return $assignmentList->getObjects(); + #[\Override] + public function reset(array $parameters = []) + { + (new UserGroupAssignmentCache())->rebuild(); } } diff --git a/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php new file mode 100644 index 00000000000..b87937ac92e --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php @@ -0,0 +1,50 @@ + + * @since 6.3 + * + * @extends AbstractEagerCache> + */ +final class UserGroupAssignmentCache extends AbstractEagerCache +{ + #[\Override] + protected function getCacheData(): array + { + $assignmentList = $this->getUserGroupAssignments(); + + $migrationDone = false; + foreach ($assignmentList as $assignment) { + if ($assignment->needMigration) { + (new UserGroupAssignmentMigrateCondition($assignment))(); + $migrationDone = true; + } + } + + if ($migrationDone) { + // Reload the list to ensure that no disabled assignments are included + return $this->getUserGroupAssignments()->getObjects(); + } else { + return $assignmentList->getObjects(); + } + } + + private function getUserGroupAssignments(): UserGroupAssignmentList + { + $assignmentList = new UserGroupAssignmentList(); + $assignmentList->getConditionBuilder()->add('isDisabled = ?', [0]); + $assignmentList->readObjects(); + + return $assignmentList; + } +} diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 6b5cd81671d..a0a31bb7371 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -42,7 +42,7 @@ public function __construct() new UserInGroupConditionType(), new UserNotInGroupConditionType(), new UserLanguageConditionType(), - new class("avatar", 'avatarFileID') extends AbstractUserIsNullConditionType {}, + new class("avatar", 'avatarFileID', 'userAvatar', 'com.woltlab.wcf.avatar') extends AbstractUserIsNullConditionType {}, new UserSignatureConditionType(), new class("coverPhoto", 'coverPhotoFileID') extends AbstractUserIsNullConditionType {}, new class("isBanned", 'banned') extends AbstractUserBooleanConditionType {}, diff --git a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php index 0fa2fa80c23..769e89d15fb 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php @@ -6,7 +6,7 @@ * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License - * @since 6.2 + * @since 6.3 */ interface IMigrateConditionType { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php index ad6ec8aee91..09dc6949b61 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -7,6 +7,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\BooleanFormField; @@ -20,11 +21,13 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserBooleanConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +abstract class AbstractUserBooleanConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, - public readonly string $columnName + public readonly string $columnName, + public readonly ?string $migrateKeyName = null, + public readonly ?string $migrateConditionObjectType = null, ) { } @@ -65,4 +68,28 @@ public function matches(object $object): bool return !$object->{$this->columnName}; } } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if ($this->migrateKeyName === null || !isset($conditionData[$this->migrateKeyName])) { + return []; + } + + $value = $conditionData[$this->migrateKeyName]; + unset($conditionData[$this->migrateKeyName]); + + return [ + [ + 'identifier' => $this->identifier, + 'value' => \boolval($value), + ], + ]; + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $this->migrateConditionObjectType === null || $this->migrateConditionObjectType === $objectType; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index 197c029365f..d3a216b778a 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -3,7 +3,6 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; -use wcf\system\condition\type\IMigrateConditionType; /** * @author Olaf Braun @@ -11,11 +10,16 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserSignatureConditionType extends AbstractUserBooleanConditionType implements IMigrateConditionType +final class UserSignatureConditionType extends AbstractUserBooleanConditionType { public function __construct() { - parent::__construct('signature', 'signature'); + parent::__construct( + 'signature', + 'signature', + 'userSignature', + 'com.woltlab.wcf.signature' + ); } #[\Override] @@ -43,27 +47,4 @@ public function matches(object $object): bool return $object->signature === '' || $object->signature === null; } } - - public function canMigrateConditionData(string $objectType): bool - { - return $objectType === 'com.woltlab.wcf.signature'; - } - - public function migrateConditionData(array &$conditionData): array - { - if (!isset($conditionData['userSignature'])) { - return []; - } - - $result = [ - [ - 'identifier' => $this->getIdentifier(), - 'value' => $conditionData['userSignature'] === 1, - ], - ]; - - unset($conditionData['userSignature']); - - return $result; - } } diff --git a/wcfsetup/install/files/lib/system/cronjob/UserGroupAssignmentCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/UserGroupAssignmentCronjob.class.php index 8714c867ab6..196b5a06be6 100644 --- a/wcfsetup/install/files/lib/system/cronjob/UserGroupAssignmentCronjob.class.php +++ b/wcfsetup/install/files/lib/system/cronjob/UserGroupAssignmentCronjob.class.php @@ -4,7 +4,7 @@ use wcf\data\cronjob\Cronjob; use wcf\data\user\UserAction; -use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; +use wcf\system\cache\eager\UserGroupAssignmentCache; use wcf\system\user\group\assignment\UserGroupAssignmentHandler; /** @@ -25,7 +25,7 @@ public function execute(Cronjob $cronjob) { parent::execute($cronjob); - $assignments = UserGroupAssignmentCacheBuilder::getInstance()->getData(); + $assignments = (new UserGroupAssignmentCache())->getCache(); $usersToGroup = []; $assignmentCount = 0; diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php index 6d5ef9d5055..b1fdf6df144 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/UserGroupAssignmentHandler.class.php @@ -7,7 +7,7 @@ use wcf\data\user\User; use wcf\data\user\UserAction; use wcf\data\user\UserList; -use wcf\system\cache\builder\UserGroupAssignmentCacheBuilder; +use wcf\system\cache\eager\UserGroupAssignmentCache; use wcf\system\condition\ConditionHandler; use wcf\system\condition\provider\UserConditionProvider; use wcf\system\SingletonFactory; @@ -47,8 +47,7 @@ public function checkUsers(array $userIDs) $userList->setObjectIDs($userIDs); $userList->readObjects(); - /** @var UserGroupAssignment[] $assignments */ - $assignments = UserGroupAssignmentCacheBuilder::getInstance()->getData(); + $assignments = (new UserGroupAssignmentCache())->getCache(); $conditionProvider = new UserConditionProvider(); foreach ($userList as $user) { $groupIDs = $user->getGroupIDs(); diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php new file mode 100644 index 00000000000..4487e242341 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php @@ -0,0 +1,42 @@ + + * @since 6.3 + */ +final class UserGroupAssignmentMigrateCondition +{ + public function __construct( + public readonly UserGroupAssignment $assignment, + ) { + } + + public function __invoke(): void + { + if (!$this->assignment->needMigration) { + return; + } + + $migratedData = ConditionHandler::getInstance()->migrateConditionData( + new UserConditionProvider(), + JSON::decode($this->assignment->conditions) + ); + + $editor = new UserGroupAssignmentEditor($this->assignment); + $editor->update([ + 'conditions' => JSON::encode($migratedData->conditions), + 'needMigration' => 0, + 'isDisabled' => $migratedData->isFullMigrated ? $this->assignment->isDisabled : 1, + ]); + } +} diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index c87795ff860..73d85d9682e 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -1685,7 +1685,8 @@ CREATE TABLE wcf1_user_group_assignment ( groupID INT(10) NOT NULL, title VARCHAR(255) NOT NULL, isDisabled TINYINT(1) NOT NULL DEFAULT 0, - conditions MEDIUMTEXT + conditions MEDIUMTEXT, + needMigration TINYINT(1) NOT NULL DEFAULT 0 ); DROP TABLE IF EXISTS wcf1_user_group_option; From f85ba58b98a34ebccb0f231113fc4d0675b639da Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Wed, 18 Jun 2025 13:08:56 +0200 Subject: [PATCH 56/82] Use a unique structure for the object types for migration. --- ...om.woltlab.wcf_6.3_userGroupAssignment.php | 36 +++++++++++++++++++ .../provider/UserConditionProvider.class.php | 20 +++++------ .../UserHasNotTrophyConditionType.class.php | 29 ++++++++++++++- .../user/UserHasTrophyConditionType.class.php | 29 ++++++++++++++- .../user/UserInGroupConditionType.class.php | 2 +- .../user/UserIsEnabledConditionType.class.php | 2 +- .../user/UserLanguageConditionType.class.php | 29 ++++++++++++++- .../UserNotInGroupConditionType.class.php | 2 +- ...serRegistrationDateConditionType.class.php | 2 +- ...serRegistrationDaysConditionType.class.php | 2 +- .../user/UserSignatureConditionType.class.php | 2 +- 11 files changed, 136 insertions(+), 19 deletions(-) diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php index 28ca7588673..0f740b6a578 100644 --- a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php +++ b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php @@ -15,9 +15,45 @@ WHERE assignmentID = ?"; $statement = WCF::getDB()->prepare($sql); foreach ($exportedConditions as $assignmentID => $conditionData) { + renameObjectTypes($conditionData); + $statement->execute([ JSON::encode($conditionData), 1, $assignmentID, ]); } + +/** + * Rename the object types so that the migration functions can handle them. + * @see \wcf\system\condition\provider\UserConditionProvider + * + * @param array $conditionData + */ +function renameObjectTypes(array &$conditionData): void +{ + $objectTypeMap = [ + 'com.woltlab.wcf.username' => 'com.woltlab.wcf.user.username', + 'com.woltlab.wcf.email' => 'com.woltlab.wcf.user.email', + 'com.woltlab.wcf.userGroup' => 'com.woltlab.wcf.user.userGroup', + 'com.woltlab.wcf.languages' => 'com.woltlab.wcf.user.languages', + 'com.woltlab.wcf.registrationDate' => 'com.woltlab.wcf.user.registrationDate', + 'com.woltlab.wcf.registrationDateInterval' => 'com.woltlab.wcf.user.registrationDateInterval', + 'com.woltlab.wcf.avatar' => 'com.woltlab.wcf.user.avatar', + 'com.woltlab.wcf.signature' => 'com.woltlab.wcf.user.signature', + 'com.woltlab.wcf.coverPhoto' => 'com.woltlab.wcf.user.coverPhoto', + 'com.woltlab.wcf.state' => 'com.woltlab.wcf.user.state', + 'com.woltlab.wcf.activityPoints' => 'com.woltlab.wcf.user.activityPoints', + 'com.woltlab.wcf.likesReceived' => 'com.woltlab.wcf.user.likesReceived', + // TODO 'com.woltlab.wcf.userOptions' + 'com.woltlab.wcf.userTrophyCondition' => 'com.woltlab.wcf.user.trophyCondition', + 'com.woltlab.wcf.trophyPoints' => 'com.woltlab.wcf.user.trophyPoints', + ]; + + foreach ($objectTypeMap as $currentName => $newName) { + if (isset($conditionData[$currentName])) { + $conditionData[$newName] = $conditionData[$currentName]; + unset($conditionData[$currentName]); + } + } +} diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index a0a31bb7371..9fd7f32e569 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -35,25 +35,25 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new class("username", "username", "username", 'com.woltlab.wcf.username') extends AbstractUserStringConditionType {}, - new class("email", "email", "email", 'com.woltlab.wcf.email') extends AbstractUserStringConditionType {}, + new class("username", "username", "username", 'com.woltlab.wcf.user.username') extends AbstractUserStringConditionType {}, + new class("email", "email", "email", 'com.woltlab.wcf.user.email') extends AbstractUserStringConditionType {}, new UserRegistrationDateConditionType(), new UserRegistrationDaysConditionType(), new UserInGroupConditionType(), new UserNotInGroupConditionType(), new UserLanguageConditionType(), - new class("avatar", 'avatarFileID', 'userAvatar', 'com.woltlab.wcf.avatar') extends AbstractUserIsNullConditionType {}, + new class("avatar", 'avatarFileID', 'userAvatar', 'com.woltlab.wcf.user.avatar') extends AbstractUserIsNullConditionType {}, new UserSignatureConditionType(), - new class("coverPhoto", 'coverPhotoFileID') extends AbstractUserIsNullConditionType {}, - new class("isBanned", 'banned') extends AbstractUserBooleanConditionType {}, + new class("coverPhoto", 'coverPhotoFileID', 'userCoverPhoto', 'com.woltlab.wcf.coverPhoto') extends AbstractUserIsNullConditionType {}, + new class("isBanned", 'banned', 'userIsBanned', 'com.woltlab.wcf.user.state') extends AbstractUserBooleanConditionType {}, new UserIsEnabledConditionType(), - new class("isEmailConfirmed", 'emailConfirmed') extends AbstractUserIsNullConditionType {}, - new class("isMultifactorActive", 'multifactorActive') extends AbstractUserBooleanConditionType {}, + new class("isEmailConfirmed", 'emailConfirmed', 'userIsEmailConfirmed', 'com.woltlab.wcf.user.state') extends AbstractUserIsNullConditionType {}, + new class("isMultifactorActive", 'multifactorActive', 'multifactorActive', 'com.woltlab.wcf.user.multifactor') extends AbstractUserBooleanConditionType {}, new UserHasTrophyConditionType(), new UserHasNotTrophyConditionType(), - new class("activityPoints", "activityPoints") extends AbstractUserIntegerConditionType {}, - new class("likesReceived", "likesReceived") extends AbstractUserIntegerConditionType {}, - new class("trophyPoints", "trophyPoints") extends AbstractUserIntegerConditionType {}, + new class("activityPoints", "activityPoints", 'com.woltlab.wcf.user.activityPoints') extends AbstractUserIntegerConditionType {}, + new class("likesReceived", "likesReceived", 'com.woltlab.wcf.user.likesReceived') extends AbstractUserIntegerConditionType {}, + new class("trophyPoints", "trophyPoints", 'com.woltlab.wcf.user.trophyPoints') extends AbstractUserIntegerConditionType {}, ]); // TODO add conditions for user options that implement `ISearchableConditionUserOption` diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php index f19179db225..f519c496277 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php @@ -10,6 +10,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\SelectFormField; use wcf\system\WCF; @@ -24,7 +25,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserHasNotTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserHasNotTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField @@ -87,4 +88,30 @@ private function getTrophies(): array return $trophies; } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['notUserTrophyIDs'])) { + return []; + } + + $result = []; + foreach ($conditionData['notUserTrophyIDs'] as $trophyID) { + $result[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => $trophyID, + ]; + } + + unset($conditionData['notUserTrophyIDs']); + + return $result; + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.user.userTrophyCondition'; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php index 795f6cc6b12..02cc106fdbe 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php @@ -10,6 +10,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\SelectFormField; use wcf\system\WCF; @@ -24,7 +25,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserHasTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserHasTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField @@ -87,4 +88,30 @@ private function getTrophies(): array return $trophies; } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['userTrophyIDs'])) { + return []; + } + + $result = []; + foreach ($conditionData['userTrophyIDs'] as $trophyID) { + $result[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => $trophyID, + ]; + } + + unset($conditionData['userTrophyIDs']); + + return $result; + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.user.userTrophyCondition'; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php index b8ad6b5c9f4..7524b182391 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php @@ -74,7 +74,7 @@ public function matches(object $object): bool #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $objectType === 'com.woltlab.wcf.userGroup'; + return $objectType === 'com.woltlab.wcf.user.userGroup'; } #[\Override] diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php index f689f18b007..0e3ff00049e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php @@ -14,7 +14,7 @@ final class UserIsEnabledConditionType extends AbstractUserBooleanConditionType { public function __construct() { - parent::__construct("isEnabled", 'activationCode'); + parent::__construct("isEnabled", 'activationCode', 'userIsEnabled', 'com.woltlab.wcf.user.state'); } #[\Override] diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php index 42cd3315b12..8e084be9a87 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php @@ -7,6 +7,7 @@ use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; use wcf\system\condition\type\IObjectConditionType; use wcf\system\form\builder\field\SelectFormField; use wcf\system\language\LanguageFactory; @@ -21,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserLanguageConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType +final class UserLanguageConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField @@ -59,4 +60,30 @@ public function matches(object $object): bool { return (int)$this->filter === $object->languageID; } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + if (!isset($conditionData['languageIDs'])) { + return []; + } + + $result = []; + foreach ($conditionData['languageIDs'] as $languageID) { + $result[] = [ + 'identifier' => $this->getIdentifier(), + 'value' => $languageID, + ]; + } + + unset($conditionData['languageIDs']); + + return $result; + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $objectType === 'com.woltlab.wcf.user.languages'; + } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php index e17413c336f..70dd56c4fb4 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php @@ -74,7 +74,7 @@ public function matches(object $object): bool #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $objectType === 'com.woltlab.wcf.userGroup'; + return $objectType === 'com.woltlab.wcf.user.userGroup'; } #[\Override] diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php index b63b1ff725c..71c1af2a86c 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php @@ -129,6 +129,6 @@ private function convertDateStringTimestamp(string $date, int $hour, int $minute #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $objectType === "com.woltlab.wcf.registrationDate"; + return $objectType === "com.woltlab.wcf.user.registrationDate"; } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index d4041e5e9a2..a838128963e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -17,7 +17,7 @@ final class UserRegistrationDaysConditionType extends AbstractUserIntegerConditi { public function __construct() { - parent::__construct('registrationDays', 'registrationDate', 'com.woltlab.wcf.registrationDateInterval'); + parent::__construct('registrationDays', 'registrationDate', 'com.woltlab.wcf.user.registrationDateInterval'); } #[\Override] diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php index d3a216b778a..3f13ec4f600 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php @@ -18,7 +18,7 @@ public function __construct() 'signature', 'signature', 'userSignature', - 'com.woltlab.wcf.signature' + 'com.woltlab.wcf.user.signature' ); } From e91a9ee2f242ee88488df805442aa44ea786f6bd Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:06:24 +0200 Subject: [PATCH 57/82] Rename 'needMigration' to 'isLegacy' --- .../files/acp/database/update_com.woltlab.wcf_6.3_step1.php | 2 +- .../acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php | 2 +- .../files/lib/acp/form/UserGroupAssignmentEditForm.class.php | 2 +- .../data/user/group/assignment/UserGroupAssignment.class.php | 2 +- .../lib/system/cache/eager/UserGroupAssignmentCache.class.php | 2 +- .../command/UserGroupAssignmentMigrateCondition.class.php | 4 ++-- wcfsetup/setup/db/install.sql | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php index 2ab540695d9..1a24a49c9b2 100644 --- a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php +++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php @@ -16,6 +16,6 @@ PartialDatabaseTable::create('wcf1_user_group_assignment') ->columns([ MediumtextDatabaseTableColumn::create('conditions'), - DefaultFalseBooleanDatabaseTableColumn::create('needMigration'), + DefaultFalseBooleanDatabaseTableColumn::create('isLegacy'), ]), ]; diff --git a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php index 0f740b6a578..fafe2d74d07 100644 --- a/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php +++ b/wcfsetup/install/files/acp/update_com.woltlab.wcf_6.3_userGroupAssignment.php @@ -11,7 +11,7 @@ $sql = "UPDATE wcf1_user_group_assignment SET conditions = ?, - needMigration = ? + isLegacy = ? WHERE assignmentID = ?"; $statement = WCF::getDB()->prepare($sql); foreach ($exportedConditions as $assignmentID => $conditionData) { diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php index 4c3e12241ce..d2b4e1cf56b 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php @@ -46,7 +46,7 @@ public function readParameters() throw new IllegalLinkException(); } - if ($this->formObject->needMigration) { + if ($this->formObject->isLegacy) { (new UserGroupAssignmentMigrateCondition($this->formObject))(); $this->formObject = new UserGroupAssignment(\intval($_REQUEST['id'])); } diff --git a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php index aaff99c0dd9..e2be3ebac6f 100644 --- a/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php +++ b/wcfsetup/install/files/lib/data/user/group/assignment/UserGroupAssignment.class.php @@ -19,7 +19,7 @@ * @property-read string $title title of the automatic user group assignment * @property-read int $isDisabled is `1` if the user group assignment is disabled and thus not checked for automatic assignments, otherwise `0` * @property-read string $conditions JSON-encoded string containing the conditions of the automatic user group assignment - * @property-read bool $needMigration indicates whether the conditions need to be migrated to the new format + * @property-read bool $isLegacy indicates whether the conditions need to be migrated to the new format */ class UserGroupAssignment extends DatabaseObject implements IRouteController { diff --git a/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php index b87937ac92e..203dbf5144b 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php @@ -25,7 +25,7 @@ protected function getCacheData(): array $migrationDone = false; foreach ($assignmentList as $assignment) { - if ($assignment->needMigration) { + if ($assignment->isLegacy) { (new UserGroupAssignmentMigrateCondition($assignment))(); $migrationDone = true; } diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php index 4487e242341..c6add5662e8 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php @@ -23,7 +23,7 @@ public function __construct( public function __invoke(): void { - if (!$this->assignment->needMigration) { + if (!$this->assignment->isLegacy) { return; } @@ -35,7 +35,7 @@ public function __invoke(): void $editor = new UserGroupAssignmentEditor($this->assignment); $editor->update([ 'conditions' => JSON::encode($migratedData->conditions), - 'needMigration' => 0, + 'isLegacy' => 0, 'isDisabled' => $migratedData->isFullMigrated ? $this->assignment->isDisabled : 1, ]); } diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index 73d85d9682e..1eb3e7d493e 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -1686,7 +1686,7 @@ CREATE TABLE wcf1_user_group_assignment ( title VARCHAR(255) NOT NULL, isDisabled TINYINT(1) NOT NULL DEFAULT 0, conditions MEDIUMTEXT, - needMigration TINYINT(1) NOT NULL DEFAULT 0 + isLegacy TINYINT(1) NOT NULL DEFAULT 0 ); DROP TABLE IF EXISTS wcf1_user_group_option; From c6c7ae8cede443bbb08e35eacb51afe8a09a9d71 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Sun, 29 Jun 2025 18:10:22 +0200 Subject: [PATCH 58/82] Apply suggestions from code review Co-authored-by: Alexander Ebert --- .../files/lib/system/condition/ConditionHandler.class.php | 3 ++- .../type/user/AbstractUserIntegerConditionType.class.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index 41718475dc8..07f53620213 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -191,7 +191,7 @@ public function exportConditions(string $definitionName): array $conditionBuilder = new PreparedStatementConditionBuilder(); $conditionBuilder->add('objectTypeID IN (?)', [ - \array_map(static fn (ObjectType $objectType): int => $objectType->objectTypeID, $objectTypes), + \array_column($objectTypes, 'objectTypeID'), ]); $sql = "SELECT * @@ -251,6 +251,7 @@ public function migrateConditionData(AbstractConditionProvider $provider, array } } } + \unset($condition); return ConditionMigration::for($conditionData, $migratedData); } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php index 903ca0c50ca..ea1c729872e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php @@ -94,7 +94,7 @@ protected function getConditions(): array #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $this->migrateConditionObjectType !== null && $objectType === $this->migrateConditionObjectType; + return $objectType === $this->migrateConditionObjectType; } #[\Override] From 4f105b082fc5416a8244a3a50d106c45fb3ecab5 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:21:10 +0200 Subject: [PATCH 59/82] Refactor `ConditionMigration` --- .../lib/system/condition/ConditionHandler.class.php | 7 +++---- .../{provider => }/ConditionMigration.class.php | 12 ++++++------ .../UserGroupAssignmentMigrateCondition.class.php | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) rename wcfsetup/install/files/lib/system/condition/{provider => }/ConditionMigration.class.php (71%) diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index 07f53620213..713037840b9 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -9,7 +9,6 @@ use wcf\data\object\type\ObjectTypeCache; use wcf\system\cache\builder\ConditionCacheBuilder; use wcf\system\condition\provider\AbstractConditionProvider; -use wcf\system\condition\provider\ConditionMigration; use wcf\system\condition\type\IConditionType; use wcf\system\condition\type\IMigrateConditionType; use wcf\system\database\util\PreparedStatementConditionBuilder; @@ -220,7 +219,7 @@ public function exportConditions(string $definitionName): array public function migrateConditionData(AbstractConditionProvider $provider, array $conditionData): ConditionMigration { if ($conditionData === []) { - return ConditionMigration::forNoConditions(); + return ConditionMigration::withoutData(); } $migratedData = []; @@ -231,7 +230,7 @@ public function migrateConditionData(AbstractConditionProvider $provider, array ); if ($conditionTypes === []) { - return ConditionMigration::forNoConditions(); + return ConditionMigration::withoutData(); } foreach ($conditionData as $objectType => &$condition) { @@ -253,6 +252,6 @@ public function migrateConditionData(AbstractConditionProvider $provider, array } \unset($condition); - return ConditionMigration::for($conditionData, $migratedData); + return ConditionMigration::fromData($conditionData, $migratedData); } } diff --git a/wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php b/wcfsetup/install/files/lib/system/condition/ConditionMigration.class.php similarity index 71% rename from wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php rename to wcfsetup/install/files/lib/system/condition/ConditionMigration.class.php index ac87bd9939a..e07130dad89 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/ConditionMigration.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionMigration.class.php @@ -1,6 +1,6 @@ update([ 'conditions' => JSON::encode($migratedData->conditions), 'isLegacy' => 0, - 'isDisabled' => $migratedData->isFullMigrated ? $this->assignment->isDisabled : 1, + 'isDisabled' => $migratedData->isFullyMigrated ? $this->assignment->isDisabled : 1, ]); } } From aae31ac2a23c7445d050e3f2f5e866c8f26b5985 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:23:51 +0200 Subject: [PATCH 60/82] Refactor migration condition checks for consistency --- .../type/user/AbstractUserBooleanConditionType.class.php | 2 +- .../type/user/AbstractUserStringConditionType.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php index 09dc6949b61..3a2d76a9e94 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -90,6 +90,6 @@ public function migrateConditionData(array &$conditionData): array #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $this->migrateConditionObjectType === null || $this->migrateConditionObjectType === $objectType; + return $this->migrateConditionObjectType === $objectType; } } diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php index ad6f9621df5..c1ab6bdc698 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -110,7 +110,7 @@ private function getConditions(): array #[\Override] public function canMigrateConditionData(string $objectType): bool { - return $this->migrateConditionObjectType !== null && $objectType === $this->migrateConditionObjectType; + return $objectType === $this->migrateConditionObjectType; } #[\Override] From c63a223e06bb4c3846d6bd079367c8fae306ed83 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:25:55 +0200 Subject: [PATCH 61/82] Refactor condition data migration logic to improve null handling --- .../type/user/AbstractUserBooleanConditionType.class.php | 4 ++-- .../type/user/AbstractUserStringConditionType.class.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php index 3a2d76a9e94..d514e7aeeea 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php @@ -72,11 +72,11 @@ public function matches(object $object): bool #[\Override] public function migrateConditionData(array &$conditionData): array { - if ($this->migrateKeyName === null || !isset($conditionData[$this->migrateKeyName])) { + $value = $conditionData[$this->columnName] ?? null; + if ($value === null) { return []; } - $value = $conditionData[$this->migrateKeyName]; unset($conditionData[$this->migrateKeyName]); return [ diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php index c1ab6bdc698..5095c473277 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php @@ -116,11 +116,11 @@ public function canMigrateConditionData(string $objectType): bool #[\Override] public function migrateConditionData(array &$conditionData): array { - if ($this->migrateKeyName === null || !isset($conditionData[$this->migrateKeyName])) { + $value = $conditionData[$this->migrateKeyName] ?? null; + if ($value === null) { return []; } - $value = $conditionData[$this->migrateKeyName]; unset($conditionData[$this->migrateKeyName]); return [ From 713642ba11bb79c04a001bd6925f25e34b2e8a85 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:29:12 +0200 Subject: [PATCH 62/82] Migrate user condition types to use named parameters for improved readability --- .../provider/UserConditionProvider.class.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 9fd7f32e569..0d066f712c8 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -35,25 +35,25 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new class("username", "username", "username", 'com.woltlab.wcf.user.username') extends AbstractUserStringConditionType {}, - new class("email", "email", "email", 'com.woltlab.wcf.user.email') extends AbstractUserStringConditionType {}, + new class(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username') extends AbstractUserStringConditionType {}, + new class(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email') extends AbstractUserStringConditionType {}, new UserRegistrationDateConditionType(), new UserRegistrationDaysConditionType(), new UserInGroupConditionType(), new UserNotInGroupConditionType(), new UserLanguageConditionType(), - new class("avatar", 'avatarFileID', 'userAvatar', 'com.woltlab.wcf.user.avatar') extends AbstractUserIsNullConditionType {}, + new class(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar') extends AbstractUserIsNullConditionType {}, new UserSignatureConditionType(), - new class("coverPhoto", 'coverPhotoFileID', 'userCoverPhoto', 'com.woltlab.wcf.coverPhoto') extends AbstractUserIsNullConditionType {}, - new class("isBanned", 'banned', 'userIsBanned', 'com.woltlab.wcf.user.state') extends AbstractUserBooleanConditionType {}, + new class(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto') extends AbstractUserIsNullConditionType {}, + new class(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractUserBooleanConditionType {}, new UserIsEnabledConditionType(), - new class("isEmailConfirmed", 'emailConfirmed', 'userIsEmailConfirmed', 'com.woltlab.wcf.user.state') extends AbstractUserIsNullConditionType {}, - new class("isMultifactorActive", 'multifactorActive', 'multifactorActive', 'com.woltlab.wcf.user.multifactor') extends AbstractUserBooleanConditionType {}, + new class(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractUserIsNullConditionType {}, + new class(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor') extends AbstractUserBooleanConditionType {}, new UserHasTrophyConditionType(), new UserHasNotTrophyConditionType(), - new class("activityPoints", "activityPoints", 'com.woltlab.wcf.user.activityPoints') extends AbstractUserIntegerConditionType {}, - new class("likesReceived", "likesReceived", 'com.woltlab.wcf.user.likesReceived') extends AbstractUserIntegerConditionType {}, - new class("trophyPoints", "trophyPoints", 'com.woltlab.wcf.user.trophyPoints') extends AbstractUserIntegerConditionType {}, + new class(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints') extends AbstractUserIntegerConditionType {}, + new class(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived') extends AbstractUserIntegerConditionType {}, + new class(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints') extends AbstractUserIntegerConditionType {}, ]); // TODO add conditions for user options that implement `ISearchableConditionUserOption` From a47ba70791f21f1800268225b0ac49d91fe52b56 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:54:59 +0200 Subject: [PATCH 63/82] Add legacy notice for automatic user group assignments and implement migration worker --- .../acp/templates/userGroupAssignmentList.tpl | 6 +++ .../UserGroupAssignmentEditForm.class.php | 8 ++-- .../UserGroupAssignmentListPage.class.php | 22 +++++++++++ .../files/lib/bootstrap/com.woltlab.wcf.php | 1 + .../eager/UserGroupAssignmentCache.class.php | 24 +----------- ...GroupAssignmentRebuildDataWorker.class.php | 38 +++++++++++++++++++ wcfsetup/install/lang/de.xml | 3 ++ wcfsetup/install/lang/en.xml | 3 ++ 8 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php diff --git a/wcfsetup/install/files/acp/templates/userGroupAssignmentList.tpl b/wcfsetup/install/files/acp/templates/userGroupAssignmentList.tpl index aa70b14a94a..3ea56dd5ab6 100644 --- a/wcfsetup/install/files/acp/templates/userGroupAssignmentList.tpl +++ b/wcfsetup/install/files/acp/templates/userGroupAssignmentList.tpl @@ -14,6 +14,12 @@ +{if $hasLegacyObjects} + + {lang}wcf.acp.group.assignment.legacyNotice{/lang} + +{/if} +
{unsafe:$gridView->render()}
diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php index d2b4e1cf56b..ee4e11dd9b1 100644 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAssignmentEditForm.class.php @@ -5,11 +5,12 @@ use wcf\acp\page\UserGroupAssignmentListPage; use wcf\data\user\group\assignment\UserGroupAssignment; use wcf\system\exception\IllegalLinkException; -use wcf\system\user\group\assignment\command\UserGroupAssignmentMigrateCondition; +use wcf\system\exception\NamedUserException; use wcf\system\interaction\admin\UserGroupAssignmentInteractions; use wcf\system\interaction\StandaloneInteractionContextMenuComponent; use wcf\system\request\LinkHandler; use wcf\system\WCF; +use wcf\util\HtmlString; /** * Shows the form to edit an existing automatic user group assignment. @@ -47,8 +48,9 @@ public function readParameters() } if ($this->formObject->isLegacy) { - (new UserGroupAssignmentMigrateCondition($this->formObject))(); - $this->formObject = new UserGroupAssignment(\intval($_REQUEST['id'])); + throw new NamedUserException( + HtmlString::fromSafeHtml(WCF::getLanguage()->getDynamicVariable('wcf.acp.group.assignment.legacyNotice')) + ); } } diff --git a/wcfsetup/install/files/lib/acp/page/UserGroupAssignmentListPage.class.php b/wcfsetup/install/files/lib/acp/page/UserGroupAssignmentListPage.class.php index d296fd146b0..baa50dbcfe9 100644 --- a/wcfsetup/install/files/lib/acp/page/UserGroupAssignmentListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/UserGroupAssignmentListPage.class.php @@ -4,6 +4,7 @@ use wcf\page\AbstractGridViewPage; use wcf\system\gridView\admin\UserGroupAssignmentGridView; +use wcf\system\WCF; /** * Lists the available automatic user group assignments. @@ -31,4 +32,25 @@ protected function createGridView(): UserGroupAssignmentGridView { return new UserGroupAssignmentGridView(); } + + #[\Override] + public function assignVariables() + { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'hasLegacyObjects' => $this->hasLegacyObjects(), + ]); + } + + private function hasLegacyObjects(): bool + { + $sql = "SELECT COUNT(*) AS count + FROM wcf1_user_group_assignment + WHERE isLegacy = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([1]); + + return $statement->fetchColumn() > 0; + } } diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index e3205fd1895..6fd9e328f4c 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -100,6 +100,7 @@ static function (\wcf\event\worker\RebuildWorkerCollecting $event) { $event->register(\wcf\system\worker\UnfurlUrlRebuildDataWorker::class, 450); $event->register(\wcf\system\worker\FileRebuildDataWorker::class, 475); $event->register(\wcf\system\worker\SitemapRebuildWorker::class, 500); + $event->register(\wcf\system\worker\UserGroupAssignmentRebuildDataWorker::class, 600); $event->register(\wcf\system\worker\StatDailyRebuildDataWorker::class, 800); } ); diff --git a/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php index 203dbf5144b..ee6219b1bca 100644 --- a/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/eager/UserGroupAssignmentCache.class.php @@ -4,7 +4,6 @@ use wcf\data\user\group\assignment\UserGroupAssignment; use wcf\data\user\group\assignment\UserGroupAssignmentList; -use wcf\system\user\group\assignment\command\UserGroupAssignmentMigrateCondition; /** * Caches the enabled automatic user group assignments. @@ -20,31 +19,12 @@ final class UserGroupAssignmentCache extends AbstractEagerCache { #[\Override] protected function getCacheData(): array - { - $assignmentList = $this->getUserGroupAssignments(); - - $migrationDone = false; - foreach ($assignmentList as $assignment) { - if ($assignment->isLegacy) { - (new UserGroupAssignmentMigrateCondition($assignment))(); - $migrationDone = true; - } - } - - if ($migrationDone) { - // Reload the list to ensure that no disabled assignments are included - return $this->getUserGroupAssignments()->getObjects(); - } else { - return $assignmentList->getObjects(); - } - } - - private function getUserGroupAssignments(): UserGroupAssignmentList { $assignmentList = new UserGroupAssignmentList(); $assignmentList->getConditionBuilder()->add('isDisabled = ?', [0]); + $assignmentList->getConditionBuilder()->add('isLegacy = ?', [0]); $assignmentList->readObjects(); - return $assignmentList; + return $assignmentList->getObjects(); } } diff --git a/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php new file mode 100644 index 00000000000..b8cbd6e3d95 --- /dev/null +++ b/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php @@ -0,0 +1,38 @@ + + * + * @extends AbstractLinearRebuildDataWorker + */ +final class UserGroupAssignmentRebuildDataWorker extends AbstractLinearRebuildDataWorker +{ + /** + * @inheritDoc + */ + protected $objectListClassName = UserGroupAssignmentList::class; + + /** + * @inheritDoc + */ + protected $limit = 100; + + #[\Override] + public function execute() + { + parent::execute(); + + foreach ($this->objectList as $assignment) { + (new UserGroupAssignmentMigrateCondition($assignment))(); + } + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index feb120254af..0bc66977bd0 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -870,6 +870,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE + Anzeigen aktualisieren durch.]]> @@ -2700,6 +2701,8 @@ Abschnitte dürfen nicht leer sein und nur folgende Zeichen enthalten: [a-z + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index cfaa778488b..7bc39d09016 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -846,6 +846,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru + Rebuild Data.]]> @@ -2627,6 +2628,8 @@ If you have already bought the licenses for the listed apps, th + + From e9b67b438bb943fca0b6f01359cb5aad8fd83a2e Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:56:58 +0200 Subject: [PATCH 64/82] Rename `UserGroupAssignmentMigrateCondition` to `MigrateLegacyCondition` --- ...teCondition.class.php => MigrateLegacyCondition.class.php} | 4 +++- .../worker/UserGroupAssignmentRebuildDataWorker.class.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) rename wcfsetup/install/files/lib/system/user/group/assignment/command/{UserGroupAssignmentMigrateCondition.class.php => MigrateLegacyCondition.class.php} (90%) diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php similarity index 90% rename from wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php rename to wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php index 21283273539..b5a6796646b 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/command/UserGroupAssignmentMigrateCondition.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php @@ -9,12 +9,14 @@ use wcf\util\JSON; /** + * Command to migrate legacy user group assignment conditions, to the new structure. + * * @author Olaf Braun * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 */ -final class UserGroupAssignmentMigrateCondition +final class MigrateLegacyCondition { public function __construct( public readonly UserGroupAssignment $assignment, diff --git a/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php index b8cbd6e3d95..3be1fbaf549 100644 --- a/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php +++ b/wcfsetup/install/files/lib/system/worker/UserGroupAssignmentRebuildDataWorker.class.php @@ -3,7 +3,7 @@ namespace wcf\system\worker; use wcf\data\user\group\assignment\UserGroupAssignmentList; -use wcf\system\user\group\assignment\command\UserGroupAssignmentMigrateCondition; +use wcf\system\user\group\assignment\command\MigrateLegacyCondition; /** * Worker implementation for updating user group assignments. @@ -32,7 +32,7 @@ public function execute() parent::execute(); foreach ($this->objectList as $assignment) { - (new UserGroupAssignmentMigrateCondition($assignment))(); + (new MigrateLegacyCondition($assignment))(); } } } From d6009bb6c2ab116b0229e63212e8d63521c3ab31 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 18:58:33 +0200 Subject: [PATCH 65/82] Fix typo in `$field` --- .../type/user/UserRegistrationDaysConditionType.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php index a838128963e..0c987dfc290 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php @@ -24,9 +24,9 @@ public function __construct() public function getFormField(string $id): PrefixConditionFormFieldContainer { $container = parent::getFormField($id); - $filed = $container->getField(); - \assert($filed instanceof IntegerFormField); - $filed->suffix("wcf.acp.option.suffix.days"); + $field = $container->getField(); + \assert($field instanceof IntegerFormField); + $field->suffix("wcf.acp.option.suffix.days"); return $container; } From 9033e8f9b1ca89ef32d9ac957d75debdcb5c99ff Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 19:02:28 +0200 Subject: [PATCH 66/82] Add error handling for JSON decoding in condition migration --- .../command/MigrateLegacyCondition.class.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php b/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php index b5a6796646b..797018ea938 100644 --- a/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php +++ b/wcfsetup/install/files/lib/system/user/group/assignment/command/MigrateLegacyCondition.class.php @@ -6,6 +6,7 @@ use wcf\data\user\group\assignment\UserGroupAssignmentEditor; use wcf\system\condition\ConditionHandler; use wcf\system\condition\provider\UserConditionProvider; +use wcf\system\exception\SystemException; use wcf\util\JSON; /** @@ -29,10 +30,15 @@ public function __invoke(): void return; } - $migratedData = ConditionHandler::getInstance()->migrateConditionData( - new UserConditionProvider(), - JSON::decode($this->assignment->conditions) - ); + try { + $json = JSON::decode($this->assignment->conditions); + } catch (SystemException $ex) { + $ex->getExceptionID(); // Log the exception if JSON decoding fails + + return; + } + + $migratedData = ConditionHandler::getInstance()->migrateConditionData(new UserConditionProvider(), $json); $editor = new UserGroupAssignmentEditor($this->assignment); $editor->update([ From a78416b52588d45a9f9367c253e31b397b22914d Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 19:02:36 +0200 Subject: [PATCH 67/82] Fix incorrect usage of unset function in ConditionHandler --- .../files/lib/system/condition/ConditionHandler.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php index 713037840b9..cf0e193a276 100644 --- a/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php +++ b/wcfsetup/install/files/lib/system/condition/ConditionHandler.class.php @@ -250,7 +250,7 @@ public function migrateConditionData(AbstractConditionProvider $provider, array } } } - \unset($condition); + unset($condition); return ConditionMigration::fromData($conditionData, $migratedData); } From 206f69e60dd8279e8ab4d0c96aa205c392d1e404 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Sun, 29 Jun 2025 19:03:36 +0200 Subject: [PATCH 68/82] Use `DateTimeImmutable` instead of `\DateTime` --- .../type/user/UserRegistrationDateConditionType.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php index 71c1af2a86c..3f724d0a0f6 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php @@ -120,8 +120,8 @@ public function migrateConditionData(array &$conditionData): array private function convertDateStringTimestamp(string $date, int $hour, int $minute, int $seconds): int { - $dateTime = new \DateTime($date, new \DateTimeZone(TIMEZONE)); - $dateTime->setTime($hour, $minute, $seconds); + $dateTime = new \DateTimeImmutable($date, new \DateTimeZone(TIMEZONE)); + $dateTime = $dateTime->setTime($hour, $minute, $seconds); return $dateTime->getTimestamp(); } From 899599a0bc44af26c9d8e5559286018ea4e6947d Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Mon, 30 Jun 2025 17:23:58 +0200 Subject: [PATCH 69/82] Expand the PHPDoc for methods in `IMigrateConditionType` --- .../type/IMigrateConditionType.class.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php index 769e89d15fb..ec0fa98196e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/IMigrateConditionType.class.php @@ -11,13 +11,25 @@ interface IMigrateConditionType { /** - * Converts the old condition structure to the new one. All migrated values must be removed from the `$conditionData` array. + * Migrates old condition data to the new condition format by removing all successfully migrated entries from the `$conditionData` + * and returns a list of condition-data in the new structure. The remaining entries are assumed to be unprocessed and are handled + * by other condition types and must remain untouched. + * + * Note: + * - Remove entries that you have successfully migrated. + * - Leave unrecognized or unsupported entries untouched. + * - If no data can be migrated, return an empty array. + * + * This allows `ConditionHandler::migrateConditionData()` to check whether all data has been migrated correctly and completely. * * @param array $conditionData * - * @return array{identifier: string, value: mixed}[] + * @return list */ public function migrateConditionData(array &$conditionData): array; + /** + * Returns `true` if the method `migrateConditionData()` can migrate data for the given `$objectType` and `false` otherwise. + */ public function canMigrateConditionData(string $objectType): bool; } From 88c4d1475cc24c7bbaf843837050832c4cc622e8 Mon Sep 17 00:00:00 2001 From: Olaf Braun Date: Mon, 7 Jul 2025 09:49:45 +0200 Subject: [PATCH 70/82] Apply suggestions from code review Co-authored-by: Alexander Ebert --- wcfsetup/install/lang/de.xml | 2 +- wcfsetup/install/lang/en.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 0bc66977bd0..0691adcc124 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -870,7 +870,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE - Anzeigen aktualisieren durch.]]> + Anzeigen aktualisieren durch.]]> diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 7bc39d09016..b6c7cca1aa6 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -846,7 +846,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru - Rebuild Data.]]> + Rebuild Data.]]> From 94550e4c7e24be102a52bf60fd31ee023d81f82e Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Mon, 7 Jul 2025 10:48:08 +0200 Subject: [PATCH 71/82] Add a status box if content needs to be recalculated using Rebuild Data. --- .../box/MigrationCollecting.class.php | 37 +++++++++++++++++ .../StatusMessageAcpDashboardBox.class.php | 40 +++++++++++++++++++ wcfsetup/install/lang/de.xml | 2 + wcfsetup/install/lang/en.xml | 2 + 4 files changed, 81 insertions(+) create mode 100644 wcfsetup/install/files/lib/event/acp/dashboard/box/MigrationCollecting.class.php diff --git a/wcfsetup/install/files/lib/event/acp/dashboard/box/MigrationCollecting.class.php b/wcfsetup/install/files/lib/event/acp/dashboard/box/MigrationCollecting.class.php new file mode 100644 index 00000000000..d7cc204ece6 --- /dev/null +++ b/wcfsetup/install/files/lib/event/acp/dashboard/box/MigrationCollecting.class.php @@ -0,0 +1,37 @@ + + * @since 6.2 + */ +final class MigrationCollecting implements IPsr14Event +{ + /** + * @var string[] + */ + private array $needsMigration = []; + + /** + * Adds the name of objects that still need to be migrated on the `RebuildDataPage` + */ + public function migrationNeeded(string $title): void + { + $this->needsMigration[] = $title; + } + + /** + * @return string[] + */ + public function needsMigration(): array + { + return $this->needsMigration; + } +} diff --git a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php index c74b847361e..31e04f8c0ee 100644 --- a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php +++ b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php @@ -3,6 +3,7 @@ namespace wcf\system\acp\dashboard\box; use wcf\data\devtools\missing\language\item\DevtoolsMissingLanguageItemList; +use wcf\event\acp\dashboard\box\MigrationCollecting; use wcf\event\acp\dashboard\box\PHPExtensionCollecting; use wcf\event\acp\dashboard\box\StatusMessageCollecting; use wcf\system\application\ApplicationHandler; @@ -60,6 +61,7 @@ private function getMessages(): array $this->getPHPExtensionMessage(), $this->getEvaluationMessages(), $this->getBasicMessages(), + $this->getMigrationMessage(), $this->getCustomMessages() ); } @@ -271,4 +273,42 @@ private function getPHPExtensionMessage(): array return []; } + + /** + * @return StatusMessage[] + * + * @since 6.2 + */ + private function getMigrationMessage(): array + { + $event = new MigrationCollecting(); + EventHandler::getInstance()->fire($event); + if ($this->userGroupAssignmentHasLegacyObjects()) { + $event->migrationNeeded(WCF::getLanguage()->get('wcf.acp.group.assignment')); + } + + if ($event->needsMigration() !== []) { + return [ + new StatusMessage( + StatusMessageType::Warning, + WCF::getLanguage()->getDynamicVariable('wcf.acp.dashboard.box.migrationNeeded', [ + 'titles' => $event->needsMigration(), + ]) + ), + ]; + } + + return []; + } + + private function userGroupAssignmentHasLegacyObjects(): bool + { + $sql = "SELECT COUNT(*) AS count + FROM wcf1_user_group_assignment + WHERE isLegacy = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([1]); + + return $statement->fetchColumn() > 0; + } } diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 0691adcc124..25d0fd359e2 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -860,6 +860,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE + @@ -978,6 +979,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE + {$title}{/implode} wurden noch nicht aktualisiert. Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}führe{else}führen Sie{/if} dies unter Anzeigen-Aktualisierung durch.]]> Apps verwalten.]]> diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index b6c7cca1aa6..33f25beadfa 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -836,6 +836,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru + @@ -954,6 +955,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru + {$title}{/implode} need to be recalculated. Please perform this task by visiting Rebuild Data.]]> Manage Apps.]]> From 6d38f37a788221306c9703e048019efdaa71e83a Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Thu, 10 Jul 2025 10:27:54 +0200 Subject: [PATCH 72/82] Invert check if migrations needed --- .../StatusMessageAcpDashboardBox.class.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php index 31e04f8c0ee..c70653a43b8 100644 --- a/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php +++ b/wcfsetup/install/files/lib/system/acp/dashboard/box/StatusMessageAcpDashboardBox.class.php @@ -287,18 +287,18 @@ private function getMigrationMessage(): array $event->migrationNeeded(WCF::getLanguage()->get('wcf.acp.group.assignment')); } - if ($event->needsMigration() !== []) { - return [ - new StatusMessage( - StatusMessageType::Warning, - WCF::getLanguage()->getDynamicVariable('wcf.acp.dashboard.box.migrationNeeded', [ - 'titles' => $event->needsMigration(), - ]) - ), - ]; + if ($event->needsMigration() === []) { + return []; } - return []; + return [ + new StatusMessage( + StatusMessageType::Warning, + WCF::getLanguage()->getDynamicVariable('wcf.acp.dashboard.box.migrationNeeded', [ + 'titles' => $event->needsMigration(), + ]) + ), + ]; } private function userGroupAssignmentHasLegacyObjects(): bool From 926d312a853ce36b86e846820e187341d00bad9a Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 10 Jul 2025 23:28:24 +0200 Subject: [PATCH 73/82] Avoid inheriting from concrete implementations --- .../AbstractUserIsNullConditionType.class.php | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php index 1638e24a65a..5298376bab3 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php @@ -3,6 +3,11 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; +use wcf\system\condition\type\AbstractConditionType; +use wcf\system\condition\type\IDatabaseObjectListConditionType; +use wcf\system\condition\type\IMigrateConditionType; +use wcf\system\condition\type\IObjectConditionType; +use wcf\system\form\builder\field\BooleanFormField; /** * @author Olaf Braun @@ -10,8 +15,33 @@ * @license GNU Lesser General Public License * @since 6.3 */ -abstract class AbstractUserIsNullConditionType extends AbstractUserBooleanConditionType +abstract class AbstractUserIsNullConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { + public function __construct( + public readonly string $identifier, + public readonly string $columnName, + public readonly ?string $migrateKeyName = null, + public readonly ?string $migrateConditionObjectType = null, + ) {} + + #[\Override] + public function getIdentifier(): string + { + return $this->identifier; + } + + #[\Override] + public function getLabel(): string + { + return "wcf.condition.user.{$this->identifier}"; + } + + #[\Override] + public function getFormField(string $id): BooleanFormField + { + return BooleanFormField::create($id); + } + #[\Override] public function applyFilter(DatabaseObjectList $objectList): void { @@ -31,4 +61,28 @@ public function matches(object $object): bool return $object->{$this->columnName} === null; } } + + #[\Override] + public function migrateConditionData(array &$conditionData): array + { + $value = $conditionData[$this->columnName] ?? null; + if ($value === null) { + return []; + } + + unset($conditionData[$this->migrateKeyName]); + + return [ + [ + 'identifier' => $this->identifier, + 'value' => \boolval($value), + ], + ]; + } + + #[\Override] + public function canMigrateConditionData(string $objectType): bool + { + return $this->migrateConditionObjectType === $objectType; + } } From 13c7979f143c304cc802706ca4f7941caf944b3d Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 10 Jul 2025 23:36:10 +0200 Subject: [PATCH 74/82] Fix the class naming to be consistent --- .../provider/UserConditionProvider.class.php | 60 +++++++++---------- ...bstractBooleanUserConditionType.class.php} | 2 +- ...bstractIntegerUserConditionType.class.php} | 2 +- ...AbstractIsNullUserConditionType.class.php} | 2 +- ...AbstractStringUserConditionType.class.php} | 2 +- ...> HasNotTrophyUserConditionType.class.php} | 2 +- ...p => HasTrophyUserConditionType.class.php} | 2 +- ...php => InGroupUserConditionType.class.php} | 2 +- ...p => IsEnabledUserConditionType.class.php} | 2 +- ...hp => LanguageUserConditionType.class.php} | 2 +- ... => NotInGroupUserConditionType.class.php} | 2 +- ...gistrationDateUserConditionType.class.php} | 2 +- ...gistrationDaysUserConditionType.class.php} | 2 +- ...p => SignatureUserConditionType.class.php} | 2 +- 14 files changed, 43 insertions(+), 43 deletions(-) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractUserBooleanConditionType.class.php => AbstractBooleanUserConditionType.class.php} (97%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractUserIntegerConditionType.class.php => AbstractIntegerUserConditionType.class.php} (98%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractUserIsNullConditionType.class.php => AbstractIsNullUserConditionType.class.php} (97%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractUserStringConditionType.class.php => AbstractStringUserConditionType.class.php} (98%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserHasNotTrophyConditionType.class.php => HasNotTrophyUserConditionType.class.php} (98%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserHasTrophyConditionType.class.php => HasTrophyUserConditionType.class.php} (98%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserInGroupConditionType.class.php => InGroupUserConditionType.class.php} (97%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserIsEnabledConditionType.class.php => IsEnabledUserConditionType.class.php} (92%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserLanguageConditionType.class.php => LanguageUserConditionType.class.php} (97%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserNotInGroupConditionType.class.php => NotInGroupUserConditionType.class.php} (97%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserRegistrationDateConditionType.class.php => RegistrationDateUserConditionType.class.php} (98%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserRegistrationDaysConditionType.class.php => RegistrationDaysUserConditionType.class.php} (96%) rename wcfsetup/install/files/lib/system/condition/type/user/{UserSignatureConditionType.class.php => SignatureUserConditionType.class.php} (94%) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 0d066f712c8..f2199c47193 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -7,19 +7,19 @@ use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\condition\type\user\AbstractUserBooleanConditionType; -use wcf\system\condition\type\user\AbstractUserIntegerConditionType; -use wcf\system\condition\type\user\AbstractUserIsNullConditionType; -use wcf\system\condition\type\user\AbstractUserStringConditionType; -use wcf\system\condition\type\user\UserHasNotTrophyConditionType; -use wcf\system\condition\type\user\UserHasTrophyConditionType; -use wcf\system\condition\type\user\UserInGroupConditionType; +use wcf\system\condition\type\user\AbstractBooleanUserConditionType; +use wcf\system\condition\type\user\AbstractIntegerUserConditionType; +use wcf\system\condition\type\user\AbstractIsNullUserConditionType; +use wcf\system\condition\type\user\AbstractStringUserConditionType; +use wcf\system\condition\type\user\HasNotTrophyUserConditionType; +use wcf\system\condition\type\user\HasTrophyUserConditionType; +use wcf\system\condition\type\user\InGroupUserConditionType; use wcf\system\condition\type\user\UserIsEnabledConditionType; -use wcf\system\condition\type\user\UserLanguageConditionType; -use wcf\system\condition\type\user\UserNotInGroupConditionType; -use wcf\system\condition\type\user\UserRegistrationDateConditionType; -use wcf\system\condition\type\user\UserRegistrationDaysConditionType; -use wcf\system\condition\type\user\UserSignatureConditionType; +use wcf\system\condition\type\user\LanguageUserConditionType; +use wcf\system\condition\type\user\NotInGroupUserConditionType; +use wcf\system\condition\type\user\RegistrationDateUserConditionType; +use wcf\system\condition\type\user\RegistrationDaysUserConditionType; +use wcf\system\condition\type\user\SignatureUserConditionType; use wcf\system\event\EventHandler; /** @@ -35,25 +35,25 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new class(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username') extends AbstractUserStringConditionType {}, - new class(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email') extends AbstractUserStringConditionType {}, - new UserRegistrationDateConditionType(), - new UserRegistrationDaysConditionType(), - new UserInGroupConditionType(), - new UserNotInGroupConditionType(), - new UserLanguageConditionType(), - new class(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar') extends AbstractUserIsNullConditionType {}, - new UserSignatureConditionType(), - new class(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto') extends AbstractUserIsNullConditionType {}, - new class(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractUserBooleanConditionType {}, + new class(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username') extends AbstractStringUserConditionType {}, + new class(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email') extends AbstractStringUserConditionType {}, + new RegistrationDateUserConditionType(), + new RegistrationDaysUserConditionType(), + new InGroupUserConditionType(), + new NotInGroupUserConditionType(), + new LanguageUserConditionType(), + new class(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar') extends AbstractIsNullUserConditionType {}, + new SignatureUserConditionType(), + new class(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto') extends AbstractIsNullUserConditionType {}, + new class(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractBooleanUserConditionType {}, new UserIsEnabledConditionType(), - new class(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractUserIsNullConditionType {}, - new class(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor') extends AbstractUserBooleanConditionType {}, - new UserHasTrophyConditionType(), - new UserHasNotTrophyConditionType(), - new class(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints') extends AbstractUserIntegerConditionType {}, - new class(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived') extends AbstractUserIntegerConditionType {}, - new class(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints') extends AbstractUserIntegerConditionType {}, + new class(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractIsNullUserConditionType {}, + new class(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor') extends AbstractBooleanUserConditionType {}, + new HasTrophyUserConditionType(), + new HasNotTrophyUserConditionType(), + new class(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints') extends AbstractIntegerUserConditionType {}, + new class(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived') extends AbstractIntegerUserConditionType {}, + new class(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints') extends AbstractIntegerUserConditionType {}, ]); // TODO add conditions for user options that implement `ISearchableConditionUserOption` diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php index d514e7aeeea..cce807e9355 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserBooleanConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php @@ -21,7 +21,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserBooleanConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +abstract class AbstractBooleanUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php index ea1c729872e..0618b3bafc1 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIntegerConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php @@ -24,7 +24,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserIntegerConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +abstract class AbstractIntegerUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php index 5298376bab3..eccc50dc578 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserIsNullConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php @@ -15,7 +15,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -abstract class AbstractUserIsNullConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +abstract class AbstractIsNullUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php index 5095c473277..df928d154b9 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractUserStringConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php @@ -24,7 +24,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractUserStringConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +abstract class AbstractStringUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/HasNotTrophyUserConditionType.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/HasNotTrophyUserConditionType.class.php index f519c496277..2c2306ae1b7 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasNotTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/HasNotTrophyUserConditionType.class.php @@ -25,7 +25,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserHasNotTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class HasNotTrophyUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/HasTrophyUserConditionType.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/HasTrophyUserConditionType.class.php index 02cc106fdbe..72b710fb7d5 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserHasTrophyConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/HasTrophyUserConditionType.class.php @@ -25,7 +25,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserHasTrophyConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class HasTrophyUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/InGroupUserConditionType.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/InGroupUserConditionType.class.php index 7524b182391..3b57a266753 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/InGroupUserConditionType.class.php @@ -22,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class InGroupUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php similarity index 92% rename from wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php index 0e3ff00049e..fa7d5293612 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserIsEnabledConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserIsEnabledConditionType extends AbstractUserBooleanConditionType +final class UserIsEnabledConditionType extends AbstractBooleanUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/LanguageUserConditionType.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/LanguageUserConditionType.class.php index 8e084be9a87..ba74a10afd4 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserLanguageConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/LanguageUserConditionType.class.php @@ -22,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserLanguageConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class LanguageUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/NotInGroupUserConditionType.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/NotInGroupUserConditionType.class.php index 70dd56c4fb4..12731c3864e 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserNotInGroupConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/NotInGroupUserConditionType.class.php @@ -22,7 +22,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserNotInGroupConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class NotInGroupUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): SelectFormField diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateUserConditionType.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateUserConditionType.class.php index 3f724d0a0f6..80b3f8a95a0 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDateConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDateUserConditionType.class.php @@ -24,7 +24,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -final class UserRegistrationDateConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +final class RegistrationDateUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { #[\Override] public function getFormField(string $id): PrefixConditionFormFieldContainer diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php similarity index 96% rename from wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php index 0c987dfc290..81b84ddf774 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserRegistrationDaysConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php @@ -13,7 +13,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserRegistrationDaysConditionType extends AbstractUserIntegerConditionType +final class RegistrationDaysUserConditionType extends AbstractIntegerUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php similarity index 94% rename from wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php index 3f13ec4f600..becd4ab4d65 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/UserSignatureConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserSignatureConditionType extends AbstractUserBooleanConditionType +final class SignatureUserConditionType extends AbstractBooleanUserConditionType { public function __construct() { From d9bf06cef90a1dd1d3c0c2665ab4f3a30eae93a2 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 10 Jul 2025 23:40:39 +0200 Subject: [PATCH 75/82] `\strtolower()` is not binary-safe --- .../type/user/AbstractStringUserConditionType.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php index df928d154b9..a2224ef833d 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php @@ -84,8 +84,8 @@ public function applyFilter(DatabaseObjectList $objectList): void public function matches(object $object): bool { ["condition" => $condition, "value" => $value] = $this->filter; - $value = \strtolower($value); - $objectValue = \strtolower($object->{$this->columnName}); + $value = \mb_strtolower($value); + $objectValue = \mb_strtolower($object->{$this->columnName}); return match ($condition) { "_%" => \str_starts_with($objectValue, $value), From 0f8d7517b3fbe31628e6d9e79538c1c58206a908 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 10 Jul 2025 23:42:34 +0200 Subject: [PATCH 76/82] Fix the handling of condition types --- .../user/AbstractStringUserConditionType.class.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php index a2224ef833d..407118b769c 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php @@ -12,6 +12,7 @@ use wcf\system\form\builder\container\PrefixConditionFormFieldContainer; use wcf\system\form\builder\field\SingleSelectionFormField; use wcf\system\form\builder\field\TextFormField; +use wcf\system\WCF; /** * @author Olaf Braun @@ -31,8 +32,7 @@ public function __construct( public readonly string $columnName, public readonly ?string $migrateKeyName = null, public readonly ?string $migrateConditionObjectType = null, - ) { - } + ) {} #[\Override] public function getFormField(string $id): PrefixConditionFormFieldContainer @@ -65,13 +65,13 @@ public function getLabel(): string public function applyFilter(DatabaseObjectList $objectList): void { ["condition" => $condition, "value" => $value] = $this->filter; - $value = \addcslashes($value, '_%'); + $value = WCF::getDB()->escapeLikeValue($value); $filter = match ($condition) { "_%" => $value . '%', "%_%" => '%' . $value . '%', "%_" => '%' . $value, - default => '', + default => throw new \InvalidArgumentException("Unknown condition: {$condition}"), }; $objectList->getConditionBuilder()->add( @@ -91,7 +91,7 @@ public function matches(object $object): bool "_%" => \str_starts_with($objectValue, $value), "%_%" => \str_contains($objectValue, $value), "%_" => \str_ends_with($objectValue, $value), - default => false, + default => throw new \InvalidArgumentException("Unknown condition: {$condition}"), }; } From 53448d3dd98e2d77b58fd6d2467da33c4f459a8c Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 10 Jul 2025 23:54:47 +0200 Subject: [PATCH 77/82] Mark the abstract user conditions as non-abstract If we're going to instantiate them via anonymous classes, we can just drop the `abstract` because their entire implementation is non-abstract anyway. --- .../provider/UserConditionProvider.class.php | 28 +++++++++---------- ...php => BooleanUserConditionType.class.php} | 2 +- ...php => IntegerUserConditionType.class.php} | 2 +- .../user/IsEnabledUserConditionType.class.php | 2 +- ....php => IsNullUserConditionType.class.php} | 2 +- ...egistrationDaysUserConditionType.class.php | 2 +- .../user/SignatureUserConditionType.class.php | 2 +- ....php => StringUserConditionType.class.php} | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractBooleanUserConditionType.class.php => BooleanUserConditionType.class.php} (94%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractIntegerUserConditionType.class.php => IntegerUserConditionType.class.php} (95%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractIsNullUserConditionType.class.php => IsNullUserConditionType.class.php} (93%) rename wcfsetup/install/files/lib/system/condition/type/user/{AbstractStringUserConditionType.class.php => StringUserConditionType.class.php} (96%) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index f2199c47193..dbdb471b80c 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -7,19 +7,19 @@ use wcf\event\condition\provider\UserConditionProviderCollecting; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IObjectConditionType; -use wcf\system\condition\type\user\AbstractBooleanUserConditionType; -use wcf\system\condition\type\user\AbstractIntegerUserConditionType; -use wcf\system\condition\type\user\AbstractIsNullUserConditionType; -use wcf\system\condition\type\user\AbstractStringUserConditionType; +use wcf\system\condition\type\user\BooleanUserConditionType; use wcf\system\condition\type\user\HasNotTrophyUserConditionType; use wcf\system\condition\type\user\HasTrophyUserConditionType; use wcf\system\condition\type\user\InGroupUserConditionType; +use wcf\system\condition\type\user\IntegerUserConditionType; +use wcf\system\condition\type\user\IsNullUserConditionType; use wcf\system\condition\type\user\UserIsEnabledConditionType; use wcf\system\condition\type\user\LanguageUserConditionType; use wcf\system\condition\type\user\NotInGroupUserConditionType; use wcf\system\condition\type\user\RegistrationDateUserConditionType; use wcf\system\condition\type\user\RegistrationDaysUserConditionType; use wcf\system\condition\type\user\SignatureUserConditionType; +use wcf\system\condition\type\user\StringUserConditionType; use wcf\system\event\EventHandler; /** @@ -35,25 +35,25 @@ final class UserConditionProvider extends AbstractConditionProvider public function __construct() { $this->addConditions([ - new class(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username') extends AbstractStringUserConditionType {}, - new class(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email') extends AbstractStringUserConditionType {}, + new StringUserConditionType(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username'), + new StringUserConditionType(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email'), new RegistrationDateUserConditionType(), new RegistrationDaysUserConditionType(), new InGroupUserConditionType(), new NotInGroupUserConditionType(), new LanguageUserConditionType(), - new class(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar') extends AbstractIsNullUserConditionType {}, + new IsNullUserConditionType(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar'), new SignatureUserConditionType(), - new class(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto') extends AbstractIsNullUserConditionType {}, - new class(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractBooleanUserConditionType {}, + new IsNullUserConditionType(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto'), + new BooleanUserConditionType(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state'), new UserIsEnabledConditionType(), - new class(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state') extends AbstractIsNullUserConditionType {}, - new class(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor') extends AbstractBooleanUserConditionType {}, + new IsNullUserConditionType(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state'), + new BooleanUserConditionType(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor'), new HasTrophyUserConditionType(), new HasNotTrophyUserConditionType(), - new class(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints') extends AbstractIntegerUserConditionType {}, - new class(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived') extends AbstractIntegerUserConditionType {}, - new class(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints') extends AbstractIntegerUserConditionType {}, + new IntegerUserConditionType(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints'), + new IntegerUserConditionType(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived'), + new IntegerUserConditionType(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints'), ]); // TODO add conditions for user options that implement `ISearchableConditionUserOption` diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/BooleanUserConditionType.class.php similarity index 94% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/BooleanUserConditionType.class.php index cce807e9355..c2ee9504da4 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractBooleanUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/BooleanUserConditionType.class.php @@ -21,7 +21,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractBooleanUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +class BooleanUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IntegerUserConditionType.class.php similarity index 95% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/IntegerUserConditionType.class.php index 0618b3bafc1..196d520968f 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractIntegerUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IntegerUserConditionType.class.php @@ -24,7 +24,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractIntegerUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +class IntegerUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php index fa7d5293612..0bb1ee86557 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserIsEnabledConditionType extends AbstractBooleanUserConditionType +final class UserIsEnabledConditionType extends BooleanUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php similarity index 93% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php index eccc50dc578..186efe45c1a 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractIsNullUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php @@ -15,7 +15,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -abstract class AbstractIsNullUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +class IsNullUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, diff --git a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php index 81b84ddf774..d281a3fc796 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php @@ -13,7 +13,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class RegistrationDaysUserConditionType extends AbstractIntegerUserConditionType +final class RegistrationDaysUserConditionType extends IntegerUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php index becd4ab4d65..30e5e74b8ad 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/SignatureUserConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class SignatureUserConditionType extends AbstractBooleanUserConditionType +final class SignatureUserConditionType extends BooleanUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/StringUserConditionType.class.php similarity index 96% rename from wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/StringUserConditionType.class.php index 407118b769c..9093decab28 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/AbstractStringUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/StringUserConditionType.class.php @@ -25,7 +25,7 @@ * @implements IObjectConditionType * @extends AbstractConditionType */ -abstract class AbstractStringUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType +class StringUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { public function __construct( public readonly string $identifier, From d2c251d63a212bd07c1890421f0e2d180284d565 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Fri, 11 Jul 2025 00:00:48 +0200 Subject: [PATCH 78/82] Register the conditions sequentually There is very little gain in offering `addConditions()` plus it requires us to validate an arbitrary array in contrast to relying on `addCondition()` with its type hin. --- .../AbstractConditionProvider.class.php | 12 -- .../provider/UserConditionProvider.class.php | 107 ++++++++++++++++-- 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php index 78d0a58f30f..1d26c45869e 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/AbstractConditionProvider.class.php @@ -30,18 +30,6 @@ public function addCondition(IConditionType $conditionType): void $this->conditionTypes[$conditionType->getIdentifier()] = $conditionType; } - /** - * Adds multiple condition types to this provider. - * - * @param TCondition[] $conditionTypes - */ - public function addConditions(array $conditionTypes): void - { - foreach ($conditionTypes as $conditionType) { - $this->addCondition($conditionType); - } - } - final public function getFieldId(string $containerId, string $identifier, int $index): string { return "{$containerId}_{$identifier}_{$index}"; diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index dbdb471b80c..48f16c050eb 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -34,27 +34,110 @@ final class UserConditionProvider extends AbstractConditionProvider { public function __construct() { - $this->addConditions([ - new StringUserConditionType(identifier: "username", columnName: "username", migrateKeyName: "username", migrateConditionObjectType: 'com.woltlab.wcf.user.username'), - new StringUserConditionType(identifier: "email", columnName: "email", migrateKeyName: "email", migrateConditionObjectType: 'com.woltlab.wcf.user.email'), + $this->addCondition( + new StringUserConditionType( + identifier: "username", + columnName: "username", + migrateKeyName: "username", + migrateConditionObjectType: 'com.woltlab.wcf.user.username' + ), + ); + $this->addCondition( + new StringUserConditionType( + identifier: "email", + columnName: "email", + migrateKeyName: "email", + migrateConditionObjectType: 'com.woltlab.wcf.user.email' + ), + ); + $this->addCondition( new RegistrationDateUserConditionType(), + ); + $this->addCondition( new RegistrationDaysUserConditionType(), + ); + $this->addCondition( new InGroupUserConditionType(), + ); + $this->addCondition( new NotInGroupUserConditionType(), + ); + $this->addCondition( new LanguageUserConditionType(), - new IsNullUserConditionType(identifier: "avatar", columnName: 'avatarFileID', migrateKeyName: 'userAvatar', migrateConditionObjectType: 'com.woltlab.wcf.user.avatar'), + ); + $this->addCondition( + new IsNullUserConditionType( + identifier: "avatar", + columnName: 'avatarFileID', + migrateKeyName: 'userAvatar', + migrateConditionObjectType: 'com.woltlab.wcf.user.avatar' + ), + ); + $this->addCondition( new SignatureUserConditionType(), - new IsNullUserConditionType(identifier: "coverPhoto", columnName: 'coverPhotoFileID', migrateKeyName: 'userCoverPhoto', migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto'), - new BooleanUserConditionType(identifier: "isBanned", columnName: 'banned', migrateKeyName: 'userIsBanned', migrateConditionObjectType: 'com.woltlab.wcf.user.state'), + ); + $this->addCondition( + new IsNullUserConditionType( + identifier: "coverPhoto", + columnName: 'coverPhotoFileID', + migrateKeyName: 'userCoverPhoto', + migrateConditionObjectType: 'com.woltlab.wcf.coverPhoto' + ), + ); + $this->addCondition( + new BooleanUserConditionType( + identifier: "isBanned", + columnName: 'banned', + migrateKeyName: 'userIsBanned', + migrateConditionObjectType: 'com.woltlab.wcf.user.state' + ), + ); + $this->addCondition( new UserIsEnabledConditionType(), - new IsNullUserConditionType(identifier: "isEmailConfirmed", columnName: 'emailConfirmed', migrateKeyName: 'userIsEmailConfirmed', migrateConditionObjectType: 'com.woltlab.wcf.user.state'), - new BooleanUserConditionType(identifier: "isMultifactorActive", columnName: 'multifactorActive', migrateKeyName: 'multifactorActive', migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor'), + ); + $this->addCondition( + new IsNullUserConditionType( + identifier: "isEmailConfirmed", + columnName: 'emailConfirmed', + migrateKeyName: 'userIsEmailConfirmed', + migrateConditionObjectType: 'com.woltlab.wcf.user.state' + ), + ); + $this->addCondition( + new BooleanUserConditionType( + identifier: "isMultifactorActive", + columnName: 'multifactorActive', + migrateKeyName: 'multifactorActive', + migrateConditionObjectType: 'com.woltlab.wcf.user.multifactor' + ), + ); + $this->addCondition( new HasTrophyUserConditionType(), + ); + $this->addCondition( new HasNotTrophyUserConditionType(), - new IntegerUserConditionType(identifier: "activityPoints", columnName: "activityPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints'), - new IntegerUserConditionType(identifier: "likesReceived", columnName: "likesReceived", migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived'), - new IntegerUserConditionType(identifier: "trophyPoints", columnName: "trophyPoints", migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints'), - ]); + ); + $this->addCondition( + new IntegerUserConditionType( + identifier: "activityPoints", + columnName: "activityPoints", + migrateConditionObjectType: 'com.woltlab.wcf.user.activityPoints' + ), + ); + $this->addCondition( + new IntegerUserConditionType( + identifier: "likesReceived", + columnName: "likesReceived", + migrateConditionObjectType: 'com.woltlab.wcf.user.likesReceived' + ), + ); + $this->addCondition( + new IntegerUserConditionType( + identifier: "trophyPoints", + columnName: "trophyPoints", + migrateConditionObjectType: 'com.woltlab.wcf.user.trophyPoints' + ), + ); // TODO add conditions for user options that implement `ISearchableConditionUserOption` From a25534bbe0d1c59c440812500d5f6067f94fdeb3 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Fri, 11 Jul 2025 00:09:33 +0200 Subject: [PATCH 79/82] Fix the direction of the comparison --- .../user/RegistrationDaysUserConditionType.class.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php index d281a3fc796..c0e2cfe97f1 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/RegistrationDaysUserConditionType.class.php @@ -37,7 +37,7 @@ public function applyFilter(DatabaseObjectList $objectList): void ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); $objectList->getConditionBuilder()->add( - "? {$condition} {$objectList->getDatabaseTableAlias()}.registrationDate", + "{$objectList->getDatabaseTableAlias()}.registrationDate {$condition} ?", [$timestamp] ); } @@ -48,10 +48,10 @@ public function matches(object $object): bool ["condition" => $condition, "timestamp" => $timestamp] = $this->getParsedFilter(); return match ($condition) { - '>' => $timestamp > $object->registrationDate, - '<' => $timestamp < $object->registrationDate, - '>=' => $timestamp >= $object->registrationDate, - '<=' => $timestamp <= $object->registrationDate, + '>' => $object->registrationDate > $timestamp, + '<' => $object->registrationDate < $timestamp, + '>=' => $object->registrationDate >= $timestamp, + '<=' => $object->registrationDate <= $timestamp, default => throw new \InvalidArgumentException("Unknown condition: {$condition}"), }; } From 2b356a442ab210772c2bede5efb6d35a8d96d0f0 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Fri, 11 Jul 2025 12:16:02 +0200 Subject: [PATCH 80/82] Simplify the condition names Avoiding the repetition makes it easier to find conditions. --- wcfsetup/install/lang/de.xml | 34 +++++++++++++++++----------------- wcfsetup/install/lang/en.xml | 36 ++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 25d0fd359e2..e03f179e516 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3539,25 +3539,25 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt - - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 33f25beadfa..a8ad1c068f0 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3462,25 +3462,25 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + From 28550ca991d8dda95ddb42c27aa19282c38b5096 Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 11 Jul 2025 13:01:17 +0200 Subject: [PATCH 81/82] Rename the class to the correct name Add phpstan docs --- .../condition/provider/UserConditionProvider.class.php | 4 ++-- ...itionType.class.php => IsEnabledConditionType.class.php} | 2 +- .../condition/type/user/IsNullUserConditionType.class.php | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) rename wcfsetup/install/files/lib/system/condition/type/user/{IsEnabledUserConditionType.class.php => IsEnabledConditionType.class.php} (93%) diff --git a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php index 48f16c050eb..1e622a03036 100644 --- a/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php +++ b/wcfsetup/install/files/lib/system/condition/provider/UserConditionProvider.class.php @@ -12,8 +12,8 @@ use wcf\system\condition\type\user\HasTrophyUserConditionType; use wcf\system\condition\type\user\InGroupUserConditionType; use wcf\system\condition\type\user\IntegerUserConditionType; +use wcf\system\condition\type\user\IsEnabledConditionType; use wcf\system\condition\type\user\IsNullUserConditionType; -use wcf\system\condition\type\user\UserIsEnabledConditionType; use wcf\system\condition\type\user\LanguageUserConditionType; use wcf\system\condition\type\user\NotInGroupUserConditionType; use wcf\system\condition\type\user\RegistrationDateUserConditionType; @@ -93,7 +93,7 @@ public function __construct() ), ); $this->addCondition( - new UserIsEnabledConditionType(), + new IsEnabledConditionType(), ); $this->addCondition( new IsNullUserConditionType( diff --git a/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledConditionType.class.php similarity index 93% rename from wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php rename to wcfsetup/install/files/lib/system/condition/type/user/IsEnabledConditionType.class.php index 0bb1ee86557..1a81c66eb14 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IsEnabledConditionType.class.php @@ -10,7 +10,7 @@ * @license GNU Lesser General Public License * @since 6.3 */ -final class UserIsEnabledConditionType extends BooleanUserConditionType +final class IsEnabledConditionType extends BooleanUserConditionType { public function __construct() { diff --git a/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php b/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php index 186efe45c1a..6f243bc89ff 100644 --- a/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php +++ b/wcfsetup/install/files/lib/system/condition/type/user/IsNullUserConditionType.class.php @@ -3,6 +3,8 @@ namespace wcf\system\condition\type\user; use wcf\data\DatabaseObjectList; +use wcf\data\user\User; +use wcf\data\user\UserList; use wcf\system\condition\type\AbstractConditionType; use wcf\system\condition\type\IDatabaseObjectListConditionType; use wcf\system\condition\type\IMigrateConditionType; @@ -14,6 +16,10 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.3 + * + * @implements IDatabaseObjectListConditionType, bool> + * @implements IObjectConditionType + * @extends AbstractConditionType */ class IsNullUserConditionType extends AbstractConditionType implements IDatabaseObjectListConditionType, IObjectConditionType, IMigrateConditionType { From 2abe3298062f83c237a695cf1dad6119f1ca2d9f Mon Sep 17 00:00:00 2001 From: Cyperghost Date: Fri, 11 Jul 2025 13:02:28 +0200 Subject: [PATCH 82/82] Adding language variable The content of `wcf.condition.add` has been swapped between German and English. --- wcfsetup/install/lang/de.xml | 3 ++- wcfsetup/install/lang/en.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index e03f179e516..69319c89e62 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3538,7 +3538,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt - + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index a8ad1c068f0..e7d1bff1f71 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3461,7 +3461,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi - + +