diff --git a/docs/migration/wsc62/deprecations_removals.md b/docs/migration/wsc62/deprecations_removals.md index 34aee8fab..f4249ab7d 100644 --- a/docs/migration/wsc62/deprecations_removals.md +++ b/docs/migration/wsc62/deprecations_removals.md @@ -126,6 +126,23 @@ With version 6.3, we have deprecated certain components and removed several othe - `wcf\data\notice\NoticeEditor` - `wcf\data\ad\AdAction` - `wcf\data\ad\AdEditor` +- `wcf\acp\form\UserMergeForm` +- `wcf\system\event\listener\AbstractUserMergeListener` +- `wcf\system\event\listener\AbstractUserActionRenameListener` +- `wcf\data\user\UserEditor` +- `wcf\data\user\UserAction` +- `wcf\data\user\UserProfileAction` +- `wcf\data\user\option\UserOptionAction` +- `wcf\data\user\option\UserOptionEditor` +- `wcf\data\user\option\ViewableUserOption` +- `wcf\data\user\option\category\UserOptionCategoryAction` +- `wcf\data\user\option\category\UserOptionCategoryEditor` +- `wcf\system\condition\UserMultifactorCondition` +- `wcf\system\condition\UserUserIDCondition` +- `wcf\system\option\ISearchableConditionUserOption` +- `wcf\system\option\ISearchableUserOption` +- `wcf\system\option\user\IUserOptionOutput` +- `wcf\system\worker\SendNewPasswordWorker` #### Methods @@ -359,6 +376,11 @@ With version 6.3, we have deprecated certain components and removed several othe - `calendar\data\event\date\EventDateEditor::rebuildParticipants()` - `wcf\data\notice\Notice::getConditions()` - `wcf\data\user\group\assignment\UserGroupAssignment::getConditions()` +- `wcf\data\user\profile\menu\item\UserProfileMenuItemAction::validateGetContent()` +- `wcf\data\user\profile\menu\item\UserProfileMenuItemAction::getContent()` +- `wcf\data\user\UserProfile::setFileAvatar()` +- `wcf\system\view\user\profile\UserProfileHeaderView::isInAccessibleGroup()` +- `wcf\util\UserRegistrationUtil::getRandomFieldName()` #### Properties @@ -371,6 +393,7 @@ With version 6.3, we have deprecated certain components and removed several othe #### Object Type Definitions - `com.woltlab.wcf.sitemap.object`, use the event `wcf\event\sitemap\SitemapObjectCollecting` instead +- `com.woltlab.wcf.condition.userSearch`, use the event `wcf\event\gridView\admin\UserGridViewInitialized` to add filters to the user list instead ### JavaScript @@ -378,6 +401,7 @@ With version 6.3, we have deprecated certain components and removed several othe - `WoltLabSuite/Core/Ui/Sortable/List` - `WoltLabSuite/Core/Component/GoogleMaps/MarkerLoader::setup()` - `WoltLabSuite/Core/Ui/User/Sortable/ObjectWatch` +- `WoltLabSuite/Core/Acp/Ui/User/Action/Handler/SendNewPassword` ## Removals @@ -449,6 +473,33 @@ With version 6.3, we have deprecated certain components and removed several othe - `wcf\data\acp\session\ACPSessionList` - `calendar\data\user\FollowingBirthdayAction` - `calendar\data\category\CalendarCategoryAction` +- `wcf\acp\action\UserQuickSearchAction` +- `wcf\acp\form\UserAssignToGroupForm` +- `wcf\acp\form\UserContentRevertChangesForm` +- `wcf\acp\form\UserEmailAddressExportForm` +- `wcf\acp\form\UserOptionListForm` +- `wcf\acp\form\UserSearchForm` +- `wcf\action\EmailValidationAction` +- `wcf\action\UsernameValidationAction` +- `wcf\data\user\UserContentAction` +- `wcf\data\user\UserRegistrationAction` +- `wcf\form\UserSearchForm` +- `wcf\system\clipboard\action\UserClipboardAction` +- `wcf\system\clipboard\action\UserContentClipboardAction` +- `wcf\system\condition\UserOptionsCondition` +- `wcf\system\listView\user\UserSearchResultsListView` +- `wcf\system\option\user\BirthdayUserOptionOutput` +- `wcf\system\option\user\DateUserOptionOutput` +- `wcf\system\option\user\FacebookUserOptionOutput` +- `wcf\system\option\user\FloatUserOptionOutput` +- `wcf\system\option\user\ImageUserOptionOutput` +- `wcf\system\option\user\LabeledUrlUserOptionOutput` +- `wcf\system\option\user\MessageUserOptionOutput` +- `wcf\system\option\user\NewlineToBreakUserOptionOutput` +- `wcf\system\option\user\SelectOptionsUserOptionOutput` +- `wcf\system\option\user\TwitterUserOptionOutput` +- `wcf\system\option\user\URLUserOptionOutput` +- `wcf\system\option\user\UserOptionHandler` #### Methods @@ -558,6 +609,18 @@ With version 6.3, we have deprecated certain components and removed several othe - `calendar\data\event\date\participation\EventDateParticipationAction::invite()` - `wcf\data\package\update\PackageUpdateAction::validateSearchForUpdates()` - `wcf\data\package\update\PackageUpdateAction::searchForUpdates()` +- `wcf\data\user\UserAction::validatePrepareRemoveContent()` +- `wcf\data\user\UserAction::prepareRemoveContent()` +- `wcf\data\user\UserProfileAction::validateBeginEdit()` +- `wcf\data\user\UserProfileAction::beginEdit()` +- `wcf\data\user\UserProfileAction::validateSave()` +- `wcf\data\user\UserProfileAction::save()` +- `wcf\data\user\UserProfileAction::getOptionHandler()` +- `wcf\data\user\option\UserOption::setUser()` +- `wcf\data\user\option\UserOption::modifyEditableOption()` +- `wcf\data\user\option\UserOption::modifyVisibleOption()` +- `wcf\data\user\option\UserOption::setRequired()` +- `wcf\data\user\option\ViewableUserOption::getOutputObject()` #### Properties @@ -574,6 +637,10 @@ With version 6.3, we have deprecated certain components and removed several othe - `wcf\data\comment\ViewableComment::$userProfile` - `wcf\data\comment\response\StructuredCommentResponse::$userProfile` - `wcf\data\comment\response\ViewableCommentResponse::$userProfile` +- `wcf\data\user\option\UserOption::$optionValue` +- `wcf\data\user\option\UserOption::$user` +- `wcf\data\user\option\ViewableUserOption::$outputObjects` +- `wcf\page\MembersListPage::$searchID` ### JavaScript @@ -627,3 +694,23 @@ With version 6.3, we have deprecated certain components and removed several othe - `WCF.ACP.Package.Update.Search` - `WoltLabSuite/Calendar/Ui/Category/MarkAllAsRead` - `WCF.ACP.Ad.LocationHandler` +- `WoltLabSuite/Core/Acp/Component/Tag/SynonymsHandler` +- `WoltLabSuite/Core/Acp/Controller/User/List` +- `WoltLabSuite/Core/Acp/Ui/User/Action/Abstract` +- `WoltLabSuite/Core/Acp/Ui/User/Action/BanAction` +- `WoltLabSuite/Core/Acp/Ui/User/Action/DeleteAction` +- `WoltLabSuite/Core/Acp/Ui/User/Action/DisableAction` +- `WoltLabSuite/Core/Acp/Ui/User/Action/Handler/Ban` +- `WoltLabSuite/Core/Acp/Ui/User/Action/Handler/Ban/Dialog` +- `WoltLabSuite/Core/Acp/Ui/User/Action/Handler/Delete` +- `WoltLabSuite/Core/Acp/Ui/User/Action/SendNewPasswordAction` +- `WoltLabSuite/Core/Acp/Ui/User/Action/ToggleConfirmEmailAction` +- `WoltLabSuite/Core/Acp/Ui/User/Content/Remove/Clipboard` +- `WoltLabSuite/Core/Acp/Ui/User/Content/Remove/Handler` +- `WoltLabSuite/Core/Acp/Ui/User/Editor` +- `WoltLabSuite/Core/Ui/User/Editor` +- `WCF.User.Profile.Editor` +- `WCF.User.Registration` +- `WCF.User.Registration.Validation` +- `WCF.User.Registration.Validation.EmailAddress` +- `WCF.User.Registration.Validation.Username` diff --git a/docs/migration/wsc62/php.md b/docs/migration/wsc62/php.md index 4d2032ce0..d74f0fe09 100644 --- a/docs/migration/wsc62/php.md +++ b/docs/migration/wsc62/php.md @@ -391,3 +391,495 @@ A condition like `userID NOT IN (?)` excludes all events of guests and must be w Implementations of `IUserActivityEvent` that require a registered user, for example to look up the author of a comment, must handle guests. The `TCommentResponseUserActivityEvent` trait provides `getCommentAuthor()` that returns a guest profile for comments written by guests. + +## User Merging + +The ACP form `wcf\acp\form\UserMergeForm` has been replaced by a dialog of the bulk interactions in the user list, the users are merged by the command `wcf\command\user\MergeUsers`. +`UserMergeForm` and `wcf\system\event\listener\AbstractUserMergeListener` are deprecated. +Event listeners for the `save` event of `UserMergeForm` are still executed, but should be migrated. + +The PSR-14 event `wcf\event\user\UsersMerging` is fired before the merged users are deleted. +Instead of extending `AbstractUserMergeListener`, a bootstrap script registers the database columns that reference users through `UsersMerging::reassignUserColumn()`. +Unlike `AbstractUserMergeListener`, the column containing the username is not updated unless it is explicitly passed as `usernameColumn`. + +```php +// Previously +class FooUserMergeListener extends AbstractUserMergeListener +{ + protected $databaseTables = [ + 'foo1_foo', + [ + 'name' => 'foo1_foo', + 'userID' => 'editorID', + 'username' => 'editor', + ], + [ + 'name' => 'foo1_bar', + 'username' => null, + 'ignore' => true, + ], + ]; +} + +// Since 6.3 +EventHandler::getInstance()->register( + \wcf\event\user\UsersMerging::class, + static function (\wcf\event\user\UsersMerging $event) { + $event->reassignUserColumn('foo1_foo', usernameColumn: 'username'); + $event->reassignUserColumn('foo1_foo', 'editorID', 'editor'); + $event->reassignUserColumn('foo1_bar', ignore: true); + } +); +``` + +The registered columns are updated after all event listeners have been executed. +Listeners that need to perform other changes can access the destination user and the merged users through the properties `destinationUser` and `mergedUsers` of the event. + +## User Renaming + +Users are renamed through the command `wcf\command\user\UpdateUser`, which fires the PSR-14 event `wcf\event\user\UserRenamed`. +`wcf\system\event\listener\AbstractUserActionRenameListener` is deprecated. +Event listeners for the `rename` event of `wcf\data\user\UserAction` are still executed, but should be migrated. + +Instead of extending `AbstractUserActionRenameListener`, a bootstrap script registers the database columns containing the username through `UserRenamed::updateUsernameColumn()`. + +```php +// Previously +class FooUserActionRenameListener extends AbstractUserActionRenameListener +{ + protected $databaseTables = [ + 'foo1_foo', + [ + 'name' => 'foo1_foo', + 'userID' => 'editorID', + 'username' => 'editor', + ], + ]; +} + +// Since 6.3 +EventHandler::getInstance()->register( + \wcf\event\user\UserRenamed::class, + static function (\wcf\event\user\UserRenamed $event) { + $event->updateUsernameColumn('foo1_foo'); + $event->updateUsernameColumn('foo1_foo', 'editorID', 'editor'); + } +); +``` + +The registered columns are updated after all event listeners have been executed. +Listeners that need to perform other changes can access the renamed user and the previous username through the properties `user` and `oldUsername` of the event. + +## Creating and Updating Users + +`wcf\data\user\UserAction` and `wcf\data\user\UserEditor` are deprecated, users are created and modified through the [database object builder](../../php/database-object-builder.md) `wcf\data\user\UserBuilder` and the commands in the `wcf\command\user` namespace. +The commands take care of all side effects, such as updating the user rank, the user online marking and the automatic user group assignments. + +```php +// Previously +$action = new UserAction([], 'create', [ + 'data' => [ + 'username' => 'Foo', + 'email' => 'foo@example.com', + 'password' => $password, + ], + 'groups' => [$groupID], + 'options' => [ + User::getUserOptionID('location') => 'Berlin', + ], +]); +$user = $action->executeAction()['returnValues']; + +// Since 6.3 +$user = (new CreateUser( + UserBuilder::forCreate() + ->setUsername('Foo') + ->setEmail('foo@example.com') + ->setPassword($password) + ->setRegistrationDate(\TIME_NOW) + ->setGroupIDs([ + ...UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::USERS]), + $groupID, + ]) + ->setUserOption(User::getUserOptionID('location'), 'Berlin') +))(); +``` + +```php +// Previously +$action = new UserAction([$user], 'update', [ + 'data' => [ + 'userTitle' => 'Bar', + 'fooColumn' => 1, + ], +]); +$action->executeAction(); + +// Since 6.3 +$user = (new UpdateUser( + UserBuilder::forUpdate($user) + ->setUserTitle('Bar') + ->setCustomProperty('fooColumn', 1) +))(); +``` + +The user options, the user groups and the content languages are stored in separate tables that are written after the user itself. +The object returned by `UserBuilder::create()` and `UserBuilder::update()` is therefore loaded before these tables have been written, always use the `CreateUser` and `UpdateUser` commands which return an up-to-date object. +`UserBuilder::delete()` only removes the database row, use the `DeleteUser` command to delete a user including the associated data. + +The methods of `UserAction` and `UserProfileAction` have been replaced as follows: + +| Method | Replacement | +| ------ | ----------- | +| `UserAction::create()` | `wcf\command\user\CreateUser` | +| `UserAction::update()` | `wcf\command\user\UpdateUser` | +| `UserAction::delete()` | `wcf\command\user\DeleteUser` | +| `UserAction::addToGroups()` | `wcf\command\user\AddUserToGroups` | +| `UserAction::removeFromGroups()` | `wcf\command\user\RemoveUserFromGroups` | +| `UserAction::ban()` / `unban()` | `wcf\command\user\BanUser` / `UnbanUser` | +| `UserAction::enable()` / `disable()` | `wcf\command\user\EnableUser` / `DisableUser` | +| `UserAction::confirmEmail()` / `unconfirmEmail()` | `wcf\command\user\ConfirmUserEmail` / `UnconfirmUserEmail` | +| `UserAction::enableAvatar()` / `disableAvatar()` | `wcf\command\user\EnableUserAvatar` / `DisableUserAvatar` | +| `UserAction::enableCoverPhoto()` / `disableCoverPhoto()` | `wcf\command\user\EnableUserCoverPhoto` / `DisableUserCoverPhoto` | +| `UserAction::enableSignature()` / `disableSignature()` | `wcf\command\user\EnableUserSignature` / `DisableUserSignature` | +| `UserAction::resendActivationMail()` | `wcf\command\user\ResendActivationMail` | +| `UserAction::saveUserConsent()` | RPC endpoint `wcf\system\endpoint\controller\core\users\SaveUserConsent` | +| `UserAction::devtoolsSetLanguage()` | RPC endpoint `wcf\system\endpoint\controller\core\devtools\SetLanguage` | +| `UserProfileAction::getMessagePreview()` | RPC endpoint `wcf\system\endpoint\controller\core\users\RenderSignaturePreview` | +| `UserProfileAction::getDetailedActivityPointList()` | RPC endpoint `wcf\system\endpoint\controller\core\users\GetUserActivityPointList` | +| `UserProfileAction::updateUserRank()` | `wcf\command\user\RefreshUserRanks` | +| `UserProfileAction::updateUserOnlineMarking()` | `wcf\command\user\RefreshUserOnlineMarkings` | +| `UserProfileAction::updateSpecialTrophies()` | `wcf\command\user\SetSpecialTrophies` | +| `SendNewPasswordWorker` | `wcf\command\user\SendNewPassword` | + +Additionally, the commands `DisableUserMultifactor`, `DisconnectThirdPartyLogin` and `MergeUsers` have been added. + +!!! warning "WoltLab Suite no longer uses `UserAction` to create, update or delete users. Event listeners for the `initializeAction` and `finalizeAction` events of `UserAction` are no longer executed for these operations and must be migrated to the PSR-14 events below. Conversely, the deprecated methods `UserAction::create()`, `UserAction::update()` and `UserAction::delete()` do not fire the new events." + +### Events + +The commands fire the following PSR-14 events: + +| Event | Description | +| ----- | ----------- | +| `wcf\event\user\UserCreated` | Fired after a user has been created, provides the user and the builder. | +| `wcf\event\user\UserUpdated` | Fired after a user has been updated, provides the updated user and the builder. | +| `wcf\event\user\UserDeleted` | Fired after a user has been deleted. | + +All commands that modify a user, such as `BanUser` or `AddUserToGroups`, use `UpdateUser` internally and therefore fire `UserUpdated`. +The builder exposes the changes, `getUserOptions()`, `getGroupIDs()` and `getLanguageIDs()` return `null` if the respective data has not been changed. +The user prior to the update is available through `$event->builder->getObject()`. + +```php +EventHandler::getInstance()->register( + \wcf\event\user\UserUpdated::class, + static function (\wcf\event\user\UserUpdated $event) { + if ($event->builder->getGroupIDs() === null) { + return; + } + + // The user groups have been changed. + } +); +``` + +### Legacy Events of `UserProfile` + +The legacy events `getAvatar` and `isAccessible` of `wcf\data\user\UserProfile` have been removed in favor of PSR-14 events. + +```php +// Previously +final class FooUserProfileListener implements IParameterizedEventListener +{ + public function execute($eventObj, $className, $eventName, array &$parameters) + { + if ($eventName === 'getAvatar') { + $parameters['avatar'] = new FooAvatar($eventObj->getDecoratedObject()); + } elseif ($eventName === 'isAccessible' && $parameters['name'] === 'fooPrivacySetting') { + $parameters['result'] = FooPrivacy::isAccessible($eventObj->userID, $parameters['userID']); + } + } +} + +// Since 6.3 +EventHandler::getInstance()->register( + \wcf\event\user\UserAvatarLoading::class, + static function (\wcf\event\user\UserAvatarLoading $event) { + $event->setAvatar(new FooAvatar($event->user)); + } +); + +EventHandler::getInstance()->register( + \wcf\event\user\UserPrivacySettingChecking::class, + static function (\wcf\event\user\UserPrivacySettingChecking $event) { + if ($event->name === 'fooPrivacySetting') { + $event->setAccessible(FooPrivacy::isAccessible($event->user->userID, $event->viewerUserID)); + } + } +); +``` + +## Permissions and Profile Data of Users + +The permission checks for the administration of users are available as methods of `wcf\data\user\User`, which are used by the interactions, the RPC endpoints and the forms: + +- `isManageable(bool $allowSelf = false)` returns `true` if all user groups of the user are accessible to the active user and, unless `$allowSelf` is set, the user is not the active user. +- `canEnable()`, `canConfirmEmail()`, `canBan()`, `canDisableAvatar()`, `canDisableCoverPhoto()`, `canDisableSignature()`, `canSendNewPassword()`, `canDisconnectThirdPartyLogin()`, `canDisableMultifactor()`, `canResendActivationMail()`, `canDelete()`, `canExportGdpr()` and `canMail()` check the permission for the respective action. + +The result of `canConfirmEmail()` can be modified through the PSR-14 event `wcf\event\user\UserEmailConfirmationChecking`. +Do not confuse this method with the existing `canEmailConfirm()`, which checks whether the user can confirm the email address themselves. + +The avatar, the rank, the user title, the online status and the privacy settings are also available on `User` through `getAvatar()`, `getRank()`, `getUserTitle()`, `isOnline()` and `isAccessible()`, it is no longer necessary to create a `UserProfile` for these. +The data required for these methods is loaded for all users of the same collection at once (`wcf\data\user\UserCollection`). +The corresponding methods of `UserProfile` remain available. +The constants `UserProfile::ACCESS_*` are aliases of the new constants `User::ACCESS_*`. + +## ACP User Add and Edit Form + +`wcf\acp\form\UserAddForm` and `wcf\acp\form\UserEditForm` have been migrated to the form builder and are based on `AbstractDatabaseObjectBuilderForm` using `UserBuilder`. +All properties representing the form values and all template events of the `userAdd` template except for `contentHeaderNavigation` have been removed. +The options to ban a user, to disable the avatar, cover photo and signature of a user, to disconnect a third party login and to disable the multi-factor authentication have been moved to the interactions on the edit page. + +Additional form fields can be added through the `createForm` event of `UserAddForm`, which is inherited by `UserEditForm`. +The values are written into the builder using `saveValueCallback()` and read from the user using `loadValueCallback()`. + +```php +final class FooUserAddFormListener implements IParameterizedEventListener +{ + public function execute($eventObj, $className, $eventName, array &$parameters) + { + $eventObj->form->getNodeById('general')->appendChild( + BooleanFormField::create('fooEnabled') + ->label('wcf.acp.user.fooEnabled') + ->saveValueCallback(static function (UserBuilder $builder, BooleanFormField $field) { + $builder->setCustomProperty('fooEnabled', $field->getSaveValue()); + }) + ->loadValueCallback(static function (User $user, BooleanFormField $field) { + $field->value($user->fooEnabled); + }) + ); + } +} +``` + +```xml + + admin + wcf\acp\form\UserAddForm + createForm + wcf\system\event\listener\FooUserAddFormListener + 1 + +``` + +The form consists of the tab menu `userTabs` with the tabs `generalTab`, `profileTab`, `settingsTab` and `signatureTab`. +The general data is contained in the form container `general`. +Data that is stored in other tables can be saved using the `UserCreated` and `UserUpdated` events. + +`wcf\form\RegisterForm` and `wcf\form\SettingsForm` have also been migrated to the form builder and are now final. +The template events `generalFields`, `honeyPotFields` and `sections` of the `register` template and `languageFields`, `styleFields`, `trophyFields` and `sections` of the `settings` template have been removed. + +## User List + +The user list in the ACP (`wcf\acp\page\UserListPage`) has been migrated to a [grid view](../../php/api/grid_views.md) (`wcf\system\gridView\admin\UserGridView`) and is now final. +The bulk actions of the user list are provided as [bulk interactions](../../php/api/interactions.md#bulk-interactions) instead of the clipboard. +The grid view, the interactions and the bulk interactions can be extended through the events `wcf\event\gridView\admin\UserGridViewInitialized`, `wcf\event\interaction\admin\UserInteractionCollecting` and `wcf\event\interaction\bulk\admin\UserBulkInteractionCollecting`. + +```php +EventHandler::getInstance()->register( + \wcf\event\gridView\admin\UserGridViewInitialized::class, + static function (\wcf\event\gridView\admin\UserGridViewInitialized $event) { + $event->gridView->addColumnAfter( + GridViewColumn::for('fooCount') + ->label('wcf.acp.user.fooCount') + ->renderer(new NumberColumnRenderer()) + ->sortable() + ->hidden(), + 'lastActivityTime' + ); + $event->gridView->addAvailableFilters([ + new BooleanFilter('fooEnabled', 'wcf.acp.user.fooEnabled'), + ]); + } +); + +EventHandler::getInstance()->register( + \wcf\event\interaction\bulk\admin\UserBulkInteractionCollecting::class, + static function (\wcf\event\interaction\bulk\admin\UserBulkInteractionCollecting $event) { + $event->provider->addInteraction( + new BulkLinkInteraction('foo', FooForm::class, 'wcf.acp.user.foo') + ); + } +); +``` + +The user search in the ACP (`wcf\acp\form\UserSearchForm`) and in the frontend (`wcf\form\UserSearchForm`) has been removed in favor of the filters of the user list and of the members list. +Links to a filtered user list can be generated using the `filters` parameter, e.g. `UserList?filters[banned]=1`. +The object type definition `com.woltlab.wcf.condition.userSearch` is deprecated and its object types are no longer used, use the `UserGridViewInitialized` event to add custom filters instead. + +The forms to assign users to groups, to export email addresses, to merge users and to revert content changes have been replaced by dialogs of the bulk interactions. + +## User Profile Header in the ACP + +The edit page of a user in the ACP shows the profile header of the user (`wcf\system\view\user\profile\AdminUserProfileHeaderView`), which provides the interactions of the user. +The header fires the existing events `wcf\event\user\profile\UserProfileStatItemCollecting` and `wcf\event\user\profile\UserProfileHeaderSearchContentLinkCollecting`, which means that their listeners are also executed in the ACP. +Links of stat items must be absolute, e.g. `$user->getLink() . '#foo'` instead of `#foo`. + +## User Options + +The user options have been migrated to [form options](../../php/api/form_builder/form_options.md), which provide the input field, the formatted output, the database column and the filter of an option. +`wcf\data\user\option\UserOption` no longer extends `wcf\data\option\Option`. + +### Package Installation Plugin + +The value of `` in the [userOption PIP](../../package/pip/user-option.md) is the id of a form option. +The legacy option types `aboutMe`, `message`, `multiSelect`, `URL` and `labeledUrl` are converted automatically, the fields `outputclass`, `enableoptions`, `contentpattern` and `messageObjectType` as well as arbitrary additional fields are ignored. +Values of `checkboxes` options, including the default value, are separated by commas instead of newlines. + +The new option type `socialMedia` replaces `labeledUrl` and plain text options that contain the profile name on a social media platform: + +```xml + + + + + +``` + +Existing user options are converted during the upgrade to 6.3. + +### Custom Option Types + +Custom option types (`wcf\system\option\IOptionType`) and output classes (`wcf\system\option\user\IUserOptionOutput`) are no longer used for user options, options with an unknown type are treated as `text` options. +Custom types must be implemented as a form option (`wcf\system\form\option\IFormOption`) and registered through the `wcf\event\form\option\FormOptionCollecting` event. +The column of the option in `wcf1_user_option_value` is created based on `IFormOption::getDatabaseTableColumn()`. + +The new method `IFormOption::matchesFilterValue()` must be implemented by form options that do not extend `AbstractFormOption`. + +### Formatted Values + +The output classes have been replaced by `UserOption::getFormattedValue()`, `wcf\data\user\option\ViewableUserOption` is deprecated. + +```php +// Previously +$option = new ViewableUserOption($userOption); +$option->setOptionValue($userProfile->getDecoratedObject()); +$output = $option->optionValue; + +// Since 6.3 +$output = $userProfile->getFormattedUserOption('foo'); +// or +$output = $userOption->getFormattedValue($user->getUserOption('foo'), $user); +``` + +`UserProfile::getFormattedUserOption()` returns an empty string if the option has no value or is not visible to the active user, while `UserOption::getFormattedValue()` does not check the visibility. + +### Visibility and Editability + +`UserOption::setUser()` has been removed, the user is passed to `isVisible()` and `isEditable()` instead. +This avoids side effects caused by modifying the shared option objects of the cache. + +```php +// Previously +$userOption->setUser($user); +if ($userOption->isVisible()) { + // … +} + +// Since 6.3 +if ($userOption->isVisible($user)) { + // … +} +``` + +### Forms and Lists + +`wcf\system\option\user\UserOptionHandler`, `wcf\acp\form\UserOptionListForm` and the templates `userOptionFieldList` and `userProfileOptionFieldList` have been removed. +The following traits provide the building blocks to integrate user options: + +- `wcf\system\option\user\TUserOptionsByCategory` returns the options of a category tree grouped by the subcategories (`getUserOptionsByCategory()`) and the title of a category (`getUserOptionCategoryTitle()`). +- `wcf\system\form\option\user\TUserOptionFormFields` creates form containers and form fields for user options in forms based on `AbstractDatabaseObjectBuilderForm` using `UserBuilder`. +- `wcf\system\option\user\TJoinUserOptionValues` joins the values of the user options to a database object list of users. + +```php +final class FooForm extends AbstractDatabaseObjectBuilderForm +{ + use TUserOptionFormFields; + + /** + * @var array + */ + private array $options; + + #[\Override] + public function readParameters(): void + { + parent::readParameters(); + + $this->formObject = new User(WCF::getUser()->userID); + $this->options = $this->getUserOptionsByCategory( + 'settings.foo', + static fn(UserOption $option) => $option->validateOptions() + && $option->validatePermissions() + && $option->isEditable() + ); + } + + #[\Override] + protected function createForm(): void + { + parent::createForm(); + + $this->form->appendChildren($this->getUserOptionFormContainers($this->options)); + } + + #[\Override] + protected function getDatabaseObjectBuilder(): UserBuilder + { + return UserBuilder::forUpdate($this->formObject); + } + + #[\Override] + protected function getCommand(DatabaseObjectBuilder $builder): callable + { + return new UpdateUser($builder); + } +} +``` + +### Builders and Commands + +`UserOptionAction` and `UserOptionEditor` are deprecated in favor of `wcf\data\user\option\UserOptionBuilder` and the commands `CreateUserOption`, `UpdateUserOption` and `DeleteOption` in the `wcf\command\user\option` namespace. +`UserOptionCategoryAction` and `UserOptionCategoryEditor` are deprecated in favor of `wcf\data\user\option\category\UserOptionCategoryBuilder` and the commands `CreateUserOptionCategory` and `UpdateUserOptionCategory`, which fire the events `UserOptionCategoryCreated` and `UserOptionCategoryUpdated`. + +The titles of user option categories are stored in the localized storage, use `UserOptionCategory::getTitle()` instead of the language item `wcf.user.option.category.{categoryName}`. +The descriptions of user option categories are no longer displayed. + +### Filters + +User options of the `profile` category tree are automatically available as filters, which replace the user search and the `com.woltlab.wcf.userOptions` conditions: + +- the filters of the user list in the ACP, which also provides a hidden column for each option, +- the filters of the members list for options that are `searchable` and visible to other users than the owner, +- the object filters of ads, notices and automatic user group assignments (`wcf\system\object\filter\user\UserOptionObjectFilter`). + +No additional code is required, the filter is provided by the form option of the user option. + +## Grid View Column Visibility + +Grid views can allow users to select the visible columns, see [Column Visibility](../../php/api/grid_views.md#column-visibility). +`AbstractGridViewPage` reads the selected columns from the `columns` parameter into the new property `$columns`, which may conflict with an existing property of the same name in a subclass. diff --git a/docs/package/pip/user-option.md b/docs/package/pip/user-option.md index 120cf0804..4c47985f3 100644 --- a/docs/package/pip/user-option.md +++ b/docs/package/pip/user-option.md @@ -1,27 +1,129 @@ # User Option Package Installation Plugin Registers new user options (profile fields / user settings). -The behaviour of this package installation plugin closely follows the [option](option.md) PIP. + +The input field and the output of a user option are provided by [form options](../../php/api/form_builder/form_options.md), the same infrastructure that is used for contact form options and other custom fields. +User options of the `profile` category tree are also automatically available as filters in the user list of the ACP, in the members list (if they are `searchable`) and in object filters, e.g. for ads, notices and automatic user group assignments. ## Category Components -The category definition works exactly like the option PIP. +Each category is described as an `` element with the mandatory attribute `name`. + +### `` + +Optional + +The category’s parent category. + +### `` + +Optional + +Specifies the order of this category within the parent category. + +### `` + +Optional + +The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown. + +### `` + +Optional + +The permissions element can contain a comma-separated list of permissions of which the active user needs to have at least one for the category to be shown. ## Option Components -The fields `hidden`, `supporti18n` and `requirei18n` do not apply. -The following extra fields are defined: +Each option is described as an `