--- /dev/null
+/**
+ * Handles the user follow buttons.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+
+import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
+import { getPhrase } from "WoltLabSuite/Core/Language";
+
+function toggleFollow(button: HTMLButtonElement): void {
+ if (button.dataset.following != "1") {
+ button.dataset.following = "1";
+ button.dataset.tooltip = getPhrase("wcf.user.button.unfollow");
+ button.querySelector("fa-icon")?.setIcon("circle-minus");
+ void prepareRequest(button.dataset.endpoint!)
+ .post({
+ action: "follow",
+ })
+ .fetchAsResponse();
+ } else {
+ button.dataset.following = "0";
+ button.dataset.tooltip = getPhrase("wcf.user.button.follow");
+ button.querySelector("fa-icon")?.setIcon("circle-plus");
+ void prepareRequest(button.dataset.endpoint!)
+ .post({
+ action: "unfollow",
+ })
+ .fetchAsResponse();
+ }
+}
+
+export function setup(): void {
+ document.querySelectorAll<HTMLButtonElement>(".jsFollowButton").forEach((button) => {
+ button.addEventListener("click", () => {
+ toggleFollow(button);
+ });
+ });
+}
if (COMPILER_TARGET_DEFAULT) {
/**
* Handles user follow and unfollow links.
+ *
+ * @deprecated 6.1 use `WoltLabSuite/Core/Component/User/Follow` instead
*/
WCF.User.Action.Follow = Class.extend({
/**
--- /dev/null
+/**
+ * Handles the user follow buttons.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "WoltLabSuite/Core/Language"], function (require, exports, Backend_1, Language_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.setup = void 0;
+ function toggleFollow(button) {
+ if (button.dataset.following != "1") {
+ button.dataset.following = "1";
+ button.dataset.tooltip = (0, Language_1.getPhrase)("wcf.user.button.unfollow");
+ button.querySelector("fa-icon")?.setIcon("circle-minus");
+ void (0, Backend_1.prepareRequest)(button.dataset.endpoint)
+ .post({
+ action: "follow",
+ })
+ .fetchAsResponse();
+ }
+ else {
+ button.dataset.following = "0";
+ button.dataset.tooltip = (0, Language_1.getPhrase)("wcf.user.button.follow");
+ button.querySelector("fa-icon")?.setIcon("circle-plus");
+ void (0, Backend_1.prepareRequest)(button.dataset.endpoint)
+ .post({
+ action: "unfollow",
+ })
+ .fetchAsResponse();
+ }
+ }
+ function setup() {
+ document.querySelectorAll(".jsFollowButton").forEach((button) => {
+ button.addEventListener("click", () => {
+ toggleFollow(button);
+ });
+ });
+ }
+ exports.setup = setup;
+});
--- /dev/null
+<?php
+
+namespace wcf\action;
+
+use Laminas\Diactoros\Response\EmptyResponse;
+use Laminas\Diactoros\Response\TextResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\data\user\User;
+use wcf\http\Helper;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\user\command\Follow;
+use wcf\system\user\command\Unfollow;
+use wcf\system\WCF;
+
+/**
+ * Handles user follows.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2023 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class UserFollowAction implements RequestHandlerInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $parameters = Helper::mapQueryParameters(
+ $request->getQueryParams(),
+ <<<'EOT'
+ array {
+ id: positive-int
+ }
+ EOT
+ );
+
+ $this->assertUserIsLoggedIn();
+
+ $user = new User($parameters['id']);
+ $this->assertTargetCanBeFollowed($user);
+
+ if ($request->getMethod() === 'GET') {
+ return new TextResponse('Unsupported', 400);
+ } elseif ($request->getMethod() === 'POST') {
+ $bodyParameters = Helper::mapRequestBody(
+ $request->getParsedBody(),
+ <<<'EOT'
+ array {
+ action: "follow" | "unfollow"
+ }
+ EOT
+ );
+
+ if ($bodyParameters['action'] == 'follow') {
+ $this->assertUserIsNotIgnored($user);
+
+ $command = new Follow(WCF::getUser(), $user);
+ $command();
+ } else {
+ $command = new Unfollow(WCF::getUser(), $user);
+ $command();
+ }
+
+ return new EmptyResponse();
+ } else {
+ throw new \LogicException('Unreachable');
+ }
+ }
+
+ private function assertUserIsLoggedIn(): void
+ {
+ if (!WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ private function assertUserIsNotIgnored(User $target): void
+ {
+ $sql = "SELECT ignoreID
+ FROM wcf" . WCF_N . "_user_ignore
+ WHERE userID = ?
+ AND ignoreUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([
+ $target->userID,
+ WCF::getUser()->userID,
+ ]);
+
+ $ignoreID = $statement->fetchSingleColumn();
+ if ($ignoreID !== false) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ private function assertTargetCanBeFollowed(User $target): void
+ {
+ if (!$target->userID) {
+ throw new IllegalLinkException();
+ }
+
+ if ($target->userID === WCF::getUser()->userID) {
+ throw new IllegalLinkException();
+ }
+ }
+}
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\IGroupedUserListAction;
-use wcf\data\user\UserProfile;
+use wcf\data\user\User;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\UserInputException;
use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\command\Follow;
+use wcf\system\user\command\Unfollow;
use wcf\system\user\GroupedUserList;
-use wcf\system\user\notification\object\UserFollowUserNotificationObject;
-use wcf\system\user\notification\UserNotificationHandler;
use wcf\system\user\storage\UserStorageHandler;
use wcf\system\WCF;
/**
* Validates given parameters.
+ *
+ * @deprecated 6.1 use `wcf\action\UserFollowAction` instead
*/
public function validateFollow()
{
* Follows a user.
*
* @return array
+ * @deprecated 6.1 use `wcf\action\UserFollowAction` instead
*/
public function follow()
{
- /** @var UserFollow $follow */
- $follow = UserFollowEditor::createOrIgnore([
- 'userID' => WCF::getUser()->userID,
- 'followUserID' => $this->parameters['data']['userID'],
- 'time' => TIME_NOW,
- ]);
-
- if ($follow !== null) {
- // send notification
- UserNotificationHandler::getInstance()->fireEvent(
- 'following',
- 'com.woltlab.wcf.user.follow',
- new UserFollowUserNotificationObject($follow),
- [$follow->followUserID]
- );
-
- // fire activity event
- UserActivityEventHandler::getInstance()->fireEvent(
- 'com.woltlab.wcf.user.recentActivityEvent.follow',
- $this->parameters['data']['userID']
- );
-
- // reset storage
- UserStorageHandler::getInstance()->reset([$this->parameters['data']['userID']], 'followerUserIDs');
- UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'followingUserIDs');
- }
+ $command = new Follow(WCF::getUser(), new User($this->parameters['data']['userID']));
+ $command();
return [
'following' => 1,
/**
* @inheritDoc
+ * @deprecated 6.1 use `wcf\action\UserFollowAction` instead
*/
public function validateUnfollow()
{
* Stops following a user.
*
* @return array
+ * @deprecated 6.1 use `wcf\action\UserFollowAction` instead
*/
public function unfollow()
{
- $follow = UserFollow::getFollow(WCF::getUser()->userID, $this->parameters['data']['userID']);
-
- if ($follow->followID) {
- $followEditor = new UserFollowEditor($follow);
- $followEditor->delete();
-
- // remove activity event
- UserActivityEventHandler::getInstance()->removeEvent(
- 'com.woltlab.wcf.user.recentActivityEvent.follow',
- $this->parameters['data']['userID']
- );
- }
-
- // reset storage
- UserStorageHandler::getInstance()->reset([$this->parameters['data']['userID']], 'followerUserIDs');
- UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'followingUserIDs');
+ $command = new Unfollow(WCF::getUser(), new User($this->parameters['data']['userID']));
+ $command();
return [
'following' => 0,
--- /dev/null
+<?php
+
+namespace wcf\system\user\command;
+
+use wcf\data\user\follow\UserFollow;
+use wcf\data\user\follow\UserFollowEditor;
+use wcf\data\user\User;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\object\UserFollowUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
+
+/**
+ * Saves that a user is following another user.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class Follow
+{
+ public function __construct(private readonly User $user, private readonly User $target)
+ {
+ }
+
+ public function __invoke(): void
+ {
+ $follow = UserFollowEditor::createOrIgnore([
+ 'userID' => $this->user->userID,
+ 'followUserID' => $this->target->userID,
+ 'time' => TIME_NOW,
+ ]);
+
+ if ($follow === null) {
+ return;
+ }
+
+ \assert($follow instanceof UserFollow);
+ $this->sendNotification($follow);
+ $this->fireActivityEvent();
+ $this->resetUserStorage();
+ }
+
+ private function sendNotification(UserFollow $follow): void
+ {
+ UserNotificationHandler::getInstance()->fireEvent(
+ 'following',
+ 'com.woltlab.wcf.user.follow',
+ new UserFollowUserNotificationObject($follow),
+ [$follow->followUserID]
+ );
+ }
+
+ private function fireActivityEvent(): void
+ {
+ UserActivityEventHandler::getInstance()->fireEvent(
+ 'com.woltlab.wcf.user.recentActivityEvent.follow',
+ $this->target->userID
+ );
+ }
+
+ private function resetUserStorage(): void
+ {
+ UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs');
+ UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs');
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\user\command;
+
+use wcf\data\user\follow\UserFollow;
+use wcf\data\user\follow\UserFollowEditor;
+use wcf\data\user\User;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\storage\UserStorageHandler;
+
+/**
+ * Saves that a user is unfollowing another user.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class Unfollow
+{
+ public function __construct(private readonly User $user, private readonly User $target)
+ {
+ }
+
+ public function __invoke(): void
+ {
+ $follow = UserFollow::getFollow($this->user->userID, $this->target->userID);
+
+ if ($follow->followID) {
+ $followEditor = new UserFollowEditor($follow);
+ $followEditor->delete();
+
+ $this->removeActivityEvent();
+ }
+
+ $this->resetUserStorage();
+ }
+
+ private function removeActivityEvent(): void
+ {
+ UserActivityEventHandler::getInstance()->removeEvent(
+ 'com.woltlab.wcf.user.recentActivityEvent.follow',
+ $this->target->userID
+ );
+ }
+
+ private function resetUserStorage(): void
+ {
+ UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs');
+ UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs');
+ }
+}