diff --git a/com.woltlab.wcf/templates/boxTodaysBirthdays.tpl b/com.woltlab.wcf/templates/boxTodaysBirthdays.tpl index 138655a2654..84e3ebecda1 100644 --- a/com.woltlab.wcf/templates/boxTodaysBirthdays.tpl +++ b/com.woltlab.wcf/templates/boxTodaysBirthdays.tpl @@ -24,21 +24,13 @@ diff --git a/com.woltlab.wcf/templates/pollParticipantListViewFilterButtons.tpl b/com.woltlab.wcf/templates/pollParticipantListViewFilterButtons.tpl new file mode 100644 index 00000000000..4d4a242a799 --- /dev/null +++ b/com.woltlab.wcf/templates/pollParticipantListViewFilterButtons.tpl @@ -0,0 +1,27 @@ + + +{foreach from=$options item='option'} + +{/foreach} + + diff --git a/com.woltlab.wcf/templates/reactionSummaryDetailsFilterButtons.tpl b/com.woltlab.wcf/templates/reactionSummaryDetailsFilterButtons.tpl index fcde2fdd7c2..f4e0b2c421b 100644 --- a/com.woltlab.wcf/templates/reactionSummaryDetailsFilterButtons.tpl +++ b/com.woltlab.wcf/templates/reactionSummaryDetailsFilterButtons.tpl @@ -4,7 +4,7 @@ data-filter-reaction-type-id="0" data-list-view-id="{$view->getID()}" > - {lang}wcf.like.reaction.all{/lang} + {lang}wcf.global.button.all{/lang} {#$totalCount} diff --git a/com.woltlab.wcf/templates/shared_simpleUserListItems.tpl b/com.woltlab.wcf/templates/shared_simpleUserListItems.tpl new file mode 100644 index 00000000000..651972e604e --- /dev/null +++ b/com.woltlab.wcf/templates/shared_simpleUserListItems.tpl @@ -0,0 +1,29 @@ +{foreach from=$view->getItems() item='user'} +
+
+ {unsafe:$user->getAvatar()->getImageTag(96)} +
+ +
+
+

+ {unsafe:$user->getFormattedUsername()} +

+ {if MODULE_USER_RANK && $user->getUserTitle()} + {$user->getUserTitle()} + {/if} +
+ {if $view->getItemDescription($user)} +
+ {unsafe:$view->getItemDescription($user)} +
+ {/if} +
+ +
+
+ {unsafe:$view->renderInteractionContextMenuButton($user)} +
+
+
+{/foreach} diff --git a/ts/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.ts b/ts/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.ts new file mode 100644 index 00000000000..fa150cdf293 --- /dev/null +++ b/ts/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.ts @@ -0,0 +1,35 @@ +/** + * Handles the filter buttons in the poll participants dialog. + * + * @author Marcel Werk + * @copyright 2001-2026 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ + +export function setup(listViewId: string): void { + document + .querySelectorAll(`[data-filter-option-id][data-list-view-id="${listViewId}"]`) + .forEach((button) => { + button.addEventListener("click", () => { + if (button.classList.contains("active")) { + return; + } + + document + .querySelectorAll(`[data-filter-option-id][data-list-view-id="${listViewId}"]`) + .forEach((button) => { + button.classList.remove("active"); + }); + + button.classList.add("active"); + + let optionID: string | undefined = undefined; + if (button.dataset.filterOptionId && button.dataset.filterOptionId !== "0") { + optionID = button.dataset.filterOptionId; + } + const listView = document.getElementById(`${listViewId}_items`); + listView!.dispatchEvent(new CustomEvent("interaction:set-parameters", { detail: { optionID } })); + }); + }); +} diff --git a/ts/WoltLabSuite/Core/Component/User/List.ts b/ts/WoltLabSuite/Core/Component/User/List.ts index b4980d4fd9a..55da76bad1e 100644 --- a/ts/WoltLabSuite/Core/Component/User/List.ts +++ b/ts/WoltLabSuite/Core/Component/User/List.ts @@ -5,6 +5,7 @@ * @copyright 2001-2022 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.0 + * @deprecated 6.3 Use list views instead. */ import { dboAction } from "../../Ajax"; diff --git a/ts/WoltLabSuite/Core/Controller/User/Profile.ts b/ts/WoltLabSuite/Core/Controller/User/Profile.ts index 7d62cbe83a8..2c0fe49ab08 100644 --- a/ts/WoltLabSuite/Core/Controller/User/Profile.ts +++ b/ts/WoltLabSuite/Core/Controller/User/Profile.ts @@ -7,44 +7,39 @@ */ import { dboAction } from "WoltLabSuite/Core/Ajax"; -import { UserList } from "../../Component/User/List"; import * as EventHandler from "WoltLabSuite/Core/Event/Handler"; import { insertHtml } from "WoltLabSuite/Core/Dom/Util"; import { trigger as triggerDomChange } from "WoltLabSuite/Core/Dom/Change/Listener"; import { getTabMenu } from "WoltLabSuite/Core/Ui/TabMenu"; +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; +import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; function setupUserList(userId: number, buttonId: string, className: string): void { const button = document.getElementById(buttonId) as HTMLElement; - if (button) { - let userList: UserList; - - button.addEventListener("click", () => { - if (userList === undefined) { - userList = new UserList( - { - className: className, - parameters: { - userID: userId, - }, - }, - button.dataset.dialogTitle!, - ); - } - userList.open(); - }); + if (!button) { + return; } + + button.addEventListener( + "click", + promiseMutex(() => { + return dialogFactory() + .usingListView() + .fromPreset(button.dataset.dialogTitle!, className, new Map([["userID", userId.toString()]])); + }), + ); } function setupFollowingList(userId: number): void { - setupUserList(userId, "followingAll", "wcf\\data\\user\\follow\\UserFollowingAction"); + setupUserList(userId, "followingAll", "wcf\\system\\listView\\user\\FollowingListView"); } function setupFollowerList(userId: number): void { - setupUserList(userId, "followerAll", "wcf\\data\\user\\follow\\UserFollowAction"); + setupUserList(userId, "followerAll", "wcf\\system\\listView\\user\\FollowerListView"); } function setupVisitorList(userId: number): void { - setupUserList(userId, "visitorAll", "wcf\\data\\user\\profile\\visitor\\UserProfileVisitorAction"); + setupUserList(userId, "visitorAll", "wcf\\system\\listView\\user\\UserProfileVisitorListView"); } const tabContentLoaded = new Map(); diff --git a/ts/WoltLabSuite/Core/Ui/Poll/View/Participants.ts b/ts/WoltLabSuite/Core/Ui/Poll/View/Participants.ts index 3b70c48e687..4d7ae5f932f 100644 --- a/ts/WoltLabSuite/Core/Ui/Poll/View/Participants.ts +++ b/ts/WoltLabSuite/Core/Ui/Poll/View/Participants.ts @@ -8,12 +8,12 @@ */ import { Poll } from "../Poll"; -import { UserList } from "../../../Component/User/List"; +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; +import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; export class Participants { protected readonly pollManager: Poll; private button: HTMLButtonElement; - private userList?: UserList = undefined; public constructor(manager: Poll) { this.pollManager = manager; @@ -25,29 +25,18 @@ export class Participants { ); } this.button = button; - this.button.addEventListener("click", (event) => { - if (event) { - event.preventDefault(); - } - - this.open(); - }); - } - - private open(): void { - if (!this.userList) { - this.userList = new UserList( - { - className: "wcf\\data\\poll\\PollAction", - parameters: { - pollID: this.pollManager.pollId, - }, - }, - this.pollManager.question, - ); - } - - this.userList.open(); + this.button.addEventListener( + "click", + promiseMutex(() => { + return dialogFactory() + .usingListView() + .fromPreset( + this.pollManager.question, + "wcf\\system\\listView\\user\\PollParticipantListView", + new Map([["pollID", this.pollManager.pollId.toString()]]), + ); + }), + ); } public showButton(): void { diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.js new file mode 100644 index 00000000000..25e977553ce --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/Poll/ParticipantFilterButtons.js @@ -0,0 +1,36 @@ +/** + * Handles the filter buttons in the poll participants dialog. + * + * @author Marcel Werk + * @copyright 2001-2026 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.setup = setup; + function setup(listViewId) { + document + .querySelectorAll(`[data-filter-option-id][data-list-view-id="${listViewId}"]`) + .forEach((button) => { + button.addEventListener("click", () => { + if (button.classList.contains("active")) { + return; + } + document + .querySelectorAll(`[data-filter-option-id][data-list-view-id="${listViewId}"]`) + .forEach((button) => { + button.classList.remove("active"); + }); + button.classList.add("active"); + let optionID = undefined; + if (button.dataset.filterOptionId && button.dataset.filterOptionId !== "0") { + optionID = button.dataset.filterOptionId; + } + const listView = document.getElementById(`${listViewId}_items`); + listView.dispatchEvent(new CustomEvent("interaction:set-parameters", { detail: { optionID } })); + }); + }); + } +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/User/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/User/List.js index 8906a8195f0..859e2f3c4eb 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/User/List.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/User/List.js @@ -5,6 +5,7 @@ * @copyright 2001-2022 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.0 + * @deprecated 6.3 Use list views instead. */ define(["require", "exports", "tslib", "../../Ajax", "../../Ui/Pagination", "../Dialog", "WoltLabSuite/Core/Dom/Util"], function (require, exports, tslib_1, Ajax_1, Pagination_1, Dialog_1, Util_1) { "use strict"; diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/User/Profile.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/User/Profile.js index a474ebeacb6..a3a7539d825 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/User/Profile.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/User/Profile.js @@ -5,36 +5,30 @@ * @copyright 2001-2022 WoltLab GmbH * @license GNU Lesser General Public License */ -define(["require", "exports", "tslib", "WoltLabSuite/Core/Ajax", "../../Component/User/List", "WoltLabSuite/Core/Event/Handler", "WoltLabSuite/Core/Dom/Util", "WoltLabSuite/Core/Dom/Change/Listener", "WoltLabSuite/Core/Ui/TabMenu"], function (require, exports, tslib_1, Ajax_1, List_1, EventHandler, Util_1, Listener_1, TabMenu_1) { +define(["require", "exports", "tslib", "WoltLabSuite/Core/Ajax", "WoltLabSuite/Core/Event/Handler", "WoltLabSuite/Core/Dom/Util", "WoltLabSuite/Core/Dom/Change/Listener", "WoltLabSuite/Core/Ui/TabMenu", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Component/Dialog"], function (require, exports, tslib_1, Ajax_1, EventHandler, Util_1, Listener_1, TabMenu_1, PromiseMutex_1, Dialog_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setup = setup; EventHandler = tslib_1.__importStar(EventHandler); function setupUserList(userId, buttonId, className) { const button = document.getElementById(buttonId); - if (button) { - let userList; - button.addEventListener("click", () => { - if (userList === undefined) { - userList = new List_1.UserList({ - className: className, - parameters: { - userID: userId, - }, - }, button.dataset.dialogTitle); - } - userList.open(); - }); + if (!button) { + return; } + button.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(() => { + return (0, Dialog_1.dialogFactory)() + .usingListView() + .fromPreset(button.dataset.dialogTitle, className, new Map([["userID", userId.toString()]])); + })); } function setupFollowingList(userId) { - setupUserList(userId, "followingAll", "wcf\\data\\user\\follow\\UserFollowingAction"); + setupUserList(userId, "followingAll", "wcf\\system\\listView\\user\\FollowingListView"); } function setupFollowerList(userId) { - setupUserList(userId, "followerAll", "wcf\\data\\user\\follow\\UserFollowAction"); + setupUserList(userId, "followerAll", "wcf\\system\\listView\\user\\FollowerListView"); } function setupVisitorList(userId) { - setupUserList(userId, "visitorAll", "wcf\\data\\user\\profile\\visitor\\UserProfileVisitorAction"); + setupUserList(userId, "visitorAll", "wcf\\system\\listView\\user\\UserProfileVisitorListView"); } const tabContentLoaded = new Map(); function setupTabMenu(userId) { diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Poll/View/Participants.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Poll/View/Participants.js index a54237b35e7..4dd2df3ad0a 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Poll/View/Participants.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Poll/View/Participants.js @@ -6,14 +6,13 @@ * @license GNU Lesser General Public License * @since 5.5 */ -define(["require", "exports", "../../../Component/User/List"], function (require, exports, List_1) { +define(["require", "exports", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Component/Dialog"], function (require, exports, PromiseMutex_1, Dialog_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Participants = void 0; class Participants { pollManager; button; - userList = undefined; constructor(manager) { this.pollManager = manager; const button = this.pollManager.getElement().querySelector(".showPollParticipantsButton"); @@ -21,23 +20,11 @@ define(["require", "exports", "../../../Component/User/List"], function (require throw new Error(`Could not find button with selector "showPollParticipantsButton" for poll "${this.pollManager.pollId}"`); } this.button = button; - this.button.addEventListener("click", (event) => { - if (event) { - event.preventDefault(); - } - this.open(); - }); - } - open() { - if (!this.userList) { - this.userList = new List_1.UserList({ - className: "wcf\\data\\poll\\PollAction", - parameters: { - pollID: this.pollManager.pollId, - }, - }, this.pollManager.question); - } - this.userList.open(); + this.button.addEventListener("click", (0, PromiseMutex_1.promiseMutex)(() => { + return (0, Dialog_1.dialogFactory)() + .usingListView() + .fromPreset(this.pollManager.question, "wcf\\system\\listView\\user\\PollParticipantListView", new Map([["pollID", this.pollManager.pollId.toString()]])); + })); } showButton() { this.button.hidden = false; diff --git a/wcfsetup/install/files/lib/data/poll/PollAction.class.php b/wcfsetup/install/files/lib/data/poll/PollAction.class.php index 6d5ee72ddf7..cf94aa3f8e6 100644 --- a/wcfsetup/install/files/lib/data/poll/PollAction.class.php +++ b/wcfsetup/install/files/lib/data/poll/PollAction.class.php @@ -299,6 +299,9 @@ public function vote() ]; } + /** + * @deprecated 6.3 No longer in use. + */ #[\Override] public function validateGetGroupedUserList() { @@ -386,6 +389,9 @@ private function loadRelatedObject(): void $this->poll->setRelatedObject($relatedObject); } + /** + * @deprecated 6.3 No longer in use. + */ #[\Override] public function getGroupedUserList() { diff --git a/wcfsetup/install/files/lib/data/user/User.class.php b/wcfsetup/install/files/lib/data/user/User.class.php index 48944468f90..5c921ceb462 100644 --- a/wcfsetup/install/files/lib/data/user/User.class.php +++ b/wcfsetup/install/files/lib/data/user/User.class.php @@ -77,7 +77,7 @@ * @property-read int $trophyPoints total number of user's trophies in active categories * @property-read string $timezone */ -final class User extends DatabaseObject implements IPopoverObject, IRouteController, IUserContent +final class User extends DatabaseObject implements IPopoverObject, IRouteController, IUserContent, \Stringable { /** * list of group ids @@ -487,6 +487,7 @@ public static function getUsers(array $userIDs) /** * Returns the username. */ + #[\Override] public function __toString(): string { return $this->getTitle(); diff --git a/wcfsetup/install/files/lib/data/user/UserBirthdayAction.class.php b/wcfsetup/install/files/lib/data/user/UserBirthdayAction.class.php index 9cc58d74f95..fb08ddbbe89 100644 --- a/wcfsetup/install/files/lib/data/user/UserBirthdayAction.class.php +++ b/wcfsetup/install/files/lib/data/user/UserBirthdayAction.class.php @@ -17,6 +17,7 @@ * @author Marcel Werk * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * @deprecated 6.3 No longer in use. */ class UserBirthdayAction extends UserProfileAction implements IGroupedUserListAction { diff --git a/wcfsetup/install/files/lib/data/user/follow/UserFollowAction.class.php b/wcfsetup/install/files/lib/data/user/follow/UserFollowAction.class.php index 40c100a0a00..4f779e165ff 100644 --- a/wcfsetup/install/files/lib/data/user/follow/UserFollowAction.class.php +++ b/wcfsetup/install/files/lib/data/user/follow/UserFollowAction.class.php @@ -152,6 +152,9 @@ public function delete() } #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function validateGetGroupedUserList() { $this->readInteger('pageNo'); @@ -171,6 +174,9 @@ public function validateGetGroupedUserList() } #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function getGroupedUserList() { // resolve page count diff --git a/wcfsetup/install/files/lib/data/user/follow/UserFollowingAction.class.php b/wcfsetup/install/files/lib/data/user/follow/UserFollowingAction.class.php index 5fb1f9f4c17..4c72407d8d2 100644 --- a/wcfsetup/install/files/lib/data/user/follow/UserFollowingAction.class.php +++ b/wcfsetup/install/files/lib/data/user/follow/UserFollowingAction.class.php @@ -23,6 +23,9 @@ class UserFollowingAction extends UserFollowAction protected $className = UserFollowEditor::class; #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function validateGetGroupedUserList() { $this->readInteger('pageNo'); @@ -42,6 +45,9 @@ public function validateGetGroupedUserList() } #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function getGroupedUserList() { // resolve page count diff --git a/wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorAction.class.php b/wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorAction.class.php index 22a25ef7a05..c44455532bd 100644 --- a/wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorAction.class.php +++ b/wcfsetup/install/files/lib/data/user/profile/visitor/UserProfileVisitorAction.class.php @@ -35,6 +35,9 @@ class UserProfileVisitorAction extends AbstractDatabaseObjectAction implements I public $userProfile; #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function validateGetGroupedUserList() { $this->readInteger('pageNo'); @@ -54,6 +57,9 @@ public function validateGetGroupedUserList() } #[\Override] + /** + * @deprecated 6.3 No longer in use. + */ public function getGroupedUserList() { // resolve page count diff --git a/wcfsetup/install/files/lib/event/listView/user/FollowerListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/FollowerListViewInitialized.class.php new file mode 100644 index 00000000000..5bd301bde0d --- /dev/null +++ b/wcfsetup/install/files/lib/event/listView/user/FollowerListViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class FollowerListViewInitialized implements IPsr14Event +{ + public function __construct(public readonly FollowerListView $listView) {} +} diff --git a/wcfsetup/install/files/lib/event/listView/user/FollowingListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/FollowingListViewInitialized.class.php new file mode 100644 index 00000000000..63d29d195de --- /dev/null +++ b/wcfsetup/install/files/lib/event/listView/user/FollowingListViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class FollowingListViewInitialized implements IPsr14Event +{ + public function __construct(public readonly FollowingListView $listView) {} +} diff --git a/wcfsetup/install/files/lib/event/listView/user/PollParticipantListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/PollParticipantListViewInitialized.class.php new file mode 100644 index 00000000000..a63ce620f8f --- /dev/null +++ b/wcfsetup/install/files/lib/event/listView/user/PollParticipantListViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class PollParticipantListViewInitialized implements IPsr14Event +{ + public function __construct(public readonly PollParticipantListView $listView) {} +} diff --git a/wcfsetup/install/files/lib/event/listView/user/UserBirthdayListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/UserBirthdayListViewInitialized.class.php new file mode 100644 index 00000000000..aa122ebe7eb --- /dev/null +++ b/wcfsetup/install/files/lib/event/listView/user/UserBirthdayListViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class UserBirthdayListViewInitialized implements IPsr14Event +{ + public function __construct(public readonly UserBirthdayListView $listView) {} +} diff --git a/wcfsetup/install/files/lib/event/listView/user/UserProfileVisitorListViewInitialized.class.php b/wcfsetup/install/files/lib/event/listView/user/UserProfileVisitorListViewInitialized.class.php new file mode 100644 index 00000000000..12b4deb4aa7 --- /dev/null +++ b/wcfsetup/install/files/lib/event/listView/user/UserProfileVisitorListViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class UserProfileVisitorListViewInitialized implements IPsr14Event +{ + public function __construct(public readonly UserProfileVisitorListView $listView) {} +} diff --git a/wcfsetup/install/files/lib/system/listView/user/AbstractSimpleUserListView.class.php b/wcfsetup/install/files/lib/system/listView/user/AbstractSimpleUserListView.class.php new file mode 100644 index 00000000000..bc102e222d0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/AbstractSimpleUserListView.class.php @@ -0,0 +1,51 @@ + + * @since 6.3 + * + * @extends AbstractListView + */ +abstract class AbstractSimpleUserListView extends AbstractListView +{ + public function __construct() + { + $this->addAvailableSortFields([ + new ListViewSortField( + 'username', + 'wcf.user.username', + ), + ]); + + $this->setAllowSorting(false); + $this->setInteractionProvider(new UserProfileInteractionsWithFollow()); + $this->setDefaultSortField('username'); + $this->setItemsPerPage(100); + $this->setCssClassName('simpleUserList'); + $this->setContainerCssClassName('simpleUserList__container'); + } + + #[\Override] + public function renderItems(): string + { + return WCF::getTPL()->render('wcf', 'shared_simpleUserListItems', ['view' => $this]); + } + + public function getItemDescription(UserProfile $user): string + { + return ''; + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/FollowerListView.class.php b/wcfsetup/install/files/lib/system/listView/user/FollowerListView.class.php new file mode 100644 index 00000000000..cf46eef343b --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/FollowerListView.class.php @@ -0,0 +1,61 @@ + + * @since 6.3 + */ +class FollowerListView extends AbstractSimpleUserListView +{ + public function __construct( + public readonly int $userID + ) { + parent::__construct(); + } + + #[\Override] + protected function createObjectList(): UserProfileList + { + $list = new UserProfileList(); + $list->getConditionBuilder()->add( + "user_table.userID IN (SELECT userID FROM wcf1_user_follow WHERE followUserID = ?)", + [$this->userID] + ); + + return $list; + } + + #[\Override] + public function isAccessible(): bool + { + $userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID); + if ($userProfile === null) { + return false; + } + + return !$userProfile->isProtected(); + } + + #[\Override] + protected function getInitializedEvent(): FollowerListViewInitialized + { + return new FollowerListViewInitialized($this); + } + + #[\Override] + public function getParameters(): array + { + return [ + 'userID' => $this->userID, + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/FollowingListView.class.php b/wcfsetup/install/files/lib/system/listView/user/FollowingListView.class.php new file mode 100644 index 00000000000..aa996208d5b --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/FollowingListView.class.php @@ -0,0 +1,61 @@ + + * @since 6.3 + */ +class FollowingListView extends AbstractSimpleUserListView +{ + public function __construct( + public readonly int $userID + ) { + parent::__construct(); + } + + #[\Override] + protected function createObjectList(): UserProfileList + { + $list = new UserProfileList(); + $list->getConditionBuilder()->add( + "user_table.userID IN (SELECT followUserID FROM wcf1_user_follow WHERE userID = ?)", + [$this->userID] + ); + + return $list; + } + + #[\Override] + public function isAccessible(): bool + { + $userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID); + if ($userProfile === null) { + return false; + } + + return !$userProfile->isProtected(); + } + + #[\Override] + protected function getInitializedEvent(): FollowingListViewInitialized + { + return new FollowingListViewInitialized($this); + } + + #[\Override] + public function getParameters(): array + { + return [ + 'userID' => $this->userID, + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/PollParticipantListView.class.php b/wcfsetup/install/files/lib/system/listView/user/PollParticipantListView.class.php new file mode 100644 index 00000000000..4e70fb01f76 --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/PollParticipantListView.class.php @@ -0,0 +1,162 @@ + + * @since 6.3 + */ +class PollParticipantListView extends AbstractSimpleUserListView +{ + /** + * @var array> + */ + private array $userVotes; + + /** + * @var array + */ + private array $pollOptions; + private Poll $poll; + + public function __construct( + public readonly int $pollID, + public readonly ?int $optionID = null, + ) { + parent::__construct(); + + $this->poll = new Poll($pollID); + $this->pollOptions = $this->poll->getOptions(); + + $this->setAdditionalHeaderContent($this->getSimpleFilterButtons()); + } + + #[\Override] + protected function createObjectList(): UserProfileList + { + $list = new UserProfileList(); + + if ($this->optionID !== null) { + $list->getConditionBuilder()->add( + "user_table.userID IN (SELECT userID FROM wcf1_poll_option_vote WHERE pollID = ? AND optionID = ?)", + [$this->pollID, $this->optionID] + ); + } else { + $list->getConditionBuilder()->add( + "user_table.userID IN (SELECT userID FROM wcf1_poll_option_vote WHERE pollID = ?)", + [$this->pollID] + ); + } + + return $list; + } + + #[\Override] + public function isAccessible(): bool + { + if ($this->poll->isNil()) { + return false; + } elseif (!$this->poll->canViewParticipants()) { + return false; + } + + return true; + } + + #[\Override] + protected function getInitializedEvent(): PollParticipantListViewInitialized + { + return new PollParticipantListViewInitialized($this); + } + + #[\Override] + public function getParameters(): array + { + $parameters = [ + 'pollID' => $this->pollID, + ]; + + if ($this->optionID !== null) { + $parameters['optionID'] = $this->optionID; + } + + return $parameters; + } + + #[\Override] + public function getItemDescription(UserProfile $user): string + { + return StringUtil::encodeHTML( + \implode(', ', \array_map( + fn($optionID) => $this->pollOptions[$optionID]->optionValue, + $this->getUserVote($user->userID) + )) + ); + } + + private function loadUserVotes(): void + { + if (isset($this->userVotes)) { + return; + } + + $sql = "SELECT userID, optionID FROM wcf1_poll_option_vote WHERE pollID = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$this->pollID]); + $this->userVotes = $statement->fetchMap('userID', 'optionID', false); + } + + /** + * @return list + */ + private function getUserVote(int $userID): array + { + $this->loadUserVotes(); + + return $this->userVotes[$userID]; + } + + private function getSimpleFilterButtons(): string + { + $sql = "SELECT COUNT(*) AS count, optionID FROM wcf1_poll_option_vote WHERE pollID = ? GROUP BY optionID"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$this->pollID]); + $voteCounts = $statement->fetchMap('optionID', 'count'); + if (\count($voteCounts) <= 1) { + // Skip filtering if only one type is present. + return ''; + } + + $totalCount = 0; + foreach ($voteCounts as $count) { + $totalCount += $count; + } + + return WCF::getTPL()->render( + 'wcf', + 'pollParticipantListViewFilterButtons', + [ + 'view' => $this, + 'totalCount' => $totalCount, + 'voteCounts' => $voteCounts, + 'options' => \array_filter( + $this->poll->getOptions(), + static fn($option) => isset($voteCounts[$option->optionID]) + ), + 'optionID' => $this->optionID, + ] + ); + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/UserBirthdayListView.class.php b/wcfsetup/install/files/lib/system/listView/user/UserBirthdayListView.class.php new file mode 100644 index 00000000000..df1a9a5c5c6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/UserBirthdayListView.class.php @@ -0,0 +1,121 @@ + + * @since 6.3 + */ +class UserBirthdayListView extends AbstractSimpleUserListView +{ + public function __construct( + public readonly string $date + ) { + parent::__construct(); + } + + #[\Override] + protected function createObjectList(): UserProfileList + { + ['day' => $day, 'month' => $month, 'year' => $year] = $this->getDate(); + + $userIDs = []; + $userOptions = UserOptionCacheBuilder::getInstance()->getData([], 'options'); + if (isset($userOptions['birthday'])) { + $birthdayUserOption = $userOptions['birthday']; + \assert($birthdayUserOption instanceof UserOption); + + $userProfiles = UserProfileRuntimeCache::getInstance()->getObjects( + UserBirthdayCache::getInstance()->getBirthdays($month, $day) + ); + + foreach ($userProfiles as $user) { + $birthdayUserOption->setUser($user->getDecoratedObject()); + + if (!$user->isProtected() && $birthdayUserOption->isVisible() && $user->getAge($year) >= 0) { + $userIDs[] = $user->userID; + } + } + } + + $list = new UserProfileList(); + if ($userIDs !== []) { + $list->setObjectIDs($userIDs); + } else { + $list->getConditionBuilder()->add('1=0'); + } + + return $list; + } + + #[\Override] + public function isAccessible(): bool + { + if (!\MODULE_MEMBERS_LIST || !WCF::getSession()->hasPermission('user.profile.canViewMembersList')) { + return false; + } + + if (!\preg_match('/^\d{4}-\d{2}-\d{2}$/', $this->date)) { + return false; + } + + return true; + } + + #[\Override] + protected function getInitializedEvent(): UserBirthdayListViewInitialized + { + return new UserBirthdayListViewInitialized($this); + } + + #[\Override] + public function getItemDescription(UserProfile $user): string + { + return StringUtil::encodeHTML( + $user->getBirthday($this->getDate()['year']) + ); + } + + #[\Override] + public function getParameters(): array + { + return [ + 'date' => $this->date, + ]; + } + + /** + * @return array{ + * day: int, + * month: int, + * year: int + * } + */ + private function getDate(): array + { + $value = \explode('-', $this->date); + if (\count($value) !== 3) { + throw new \LogicException('unreachable'); + } + + return [ + 'day' => \intval($value[2]), + 'month' => \intval($value[1]), + 'year' => \intval($value[0]), + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/UserProfileVisitorListView.class.php b/wcfsetup/install/files/lib/system/listView/user/UserProfileVisitorListView.class.php new file mode 100644 index 00000000000..3f597e8a7f0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/listView/user/UserProfileVisitorListView.class.php @@ -0,0 +1,61 @@ + + * @since 6.3 + */ +class UserProfileVisitorListView extends AbstractSimpleUserListView +{ + public function __construct( + public readonly int $userID + ) { + parent::__construct(); + } + + #[\Override] + protected function createObjectList(): UserProfileList + { + $list = new UserProfileList(); + $list->getConditionBuilder()->add( + "user_table.userID IN (SELECT userID FROM wcf1_user_profile_visitor WHERE ownerID = ?)", + [$this->userID] + ); + + return $list; + } + + #[\Override] + public function isAccessible(): bool + { + $userProfile = UserProfileRuntimeCache::getInstance()->getObject($this->userID); + if ($userProfile === null) { + return false; + } + + return !$userProfile->isProtected(); + } + + #[\Override] + protected function getInitializedEvent(): UserProfileVisitorListViewInitialized + { + return new UserProfileVisitorListViewInitialized($this); + } + + #[\Override] + public function getParameters(): array + { + return [ + 'userID' => $this->userID, + ]; + } +} diff --git a/wcfsetup/install/files/lib/system/listView/user/UserSearchResultsListView.class.php b/wcfsetup/install/files/lib/system/listView/user/UserSearchResultsListView.class.php index c49a9552fa2..a3290852ad5 100644 --- a/wcfsetup/install/files/lib/system/listView/user/UserSearchResultsListView.class.php +++ b/wcfsetup/install/files/lib/system/listView/user/UserSearchResultsListView.class.php @@ -3,9 +3,7 @@ namespace wcf\system\listView\user; use wcf\data\search\Search; -use wcf\data\user\UserProfile; use wcf\data\user\UserProfileList; -use wcf\system\listView\AbstractListView; use wcf\system\WCF; /** diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index cfcf6c1620c..a03805d1a16 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3944,6 +3944,7 @@ Dateianhänge: + @@ -4224,7 +4225,6 @@ Erlaubte Dateiendungen: gif, jpg, jpeg, png, webp]]> getTitle()} × {#$count}{if $other} und {if $other == 1}eine weitere Reaktion{else}{#$other} weitere Reaktionen{/if}{/if}]]> - diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 2e0366cfc22..dfbd2cc2dac 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3890,6 +3890,7 @@ Attachments: + @@ -4170,7 +4171,6 @@ Allowed extensions: gif, jpg, jpeg, png, webp]]> getTitle()} × {#$count}{if $other} and {if $other == 1}one other reaction{else}{#$other} other reactions{/if}{/if}]]> -