From: Matthias Schmidt Date: Thu, 15 Apr 2021 12:39:47 +0000 (+0200) Subject: Merge branch '5.3' X-Git-Tag: 5.4.0_Alpha_1~86 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=18eb3f3f30c9d87961204c0c4b63dda9ff697789;p=GitHub%2FWoltLab%2FWCF.git Merge branch '5.3' --- 18eb3f3f30c9d87961204c0c4b63dda9ff697789 diff --cc wcfsetup/install/files/lib/data/user/notification/UserNotificationAction.class.php index ae19e7ad99,db1d7a55aa..a53114281e --- a/wcfsetup/install/files/lib/data/user/notification/UserNotificationAction.class.php +++ b/wcfsetup/install/files/lib/data/user/notification/UserNotificationAction.class.php @@@ -11,289 -9,281 +11,288 @@@ use wcf\system\WCF /** * Executes user notification-related actions. - * - * @author Marcel Werk - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @package WoltLabSuite\Core\Data\User\Notification - * - * @method UserNotificationEditor[] getObjects() - * @method UserNotificationEditor getSingleObject() + * + * @author Marcel Werk + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @package WoltLabSuite\Core\Data\User\Notification + * + * @method UserNotificationEditor[] getObjects() + * @method UserNotificationEditor getSingleObject() */ -class UserNotificationAction extends AbstractDatabaseObjectAction { - /** - * notification editor object - * @var UserNotificationEditor - */ - public $notificationEditor = null; - - /** - * @inheritDoc - * @return UserNotification - */ - public function create() { - /** @var UserNotification $notification */ - $notification = parent::create(); - - $sql = "INSERT INTO wcf".WCF_N."_user_notification_to_user - (notificationID, userID) - VALUES (?, ?)"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute([ - $notification->notificationID, - $notification->userID - ]); - - return $notification; - } - - /** - * Creates a simple notification without stacking support, applies to legacy notifications too. - * - * @return mixed[][] - */ - public function createDefault() { - $notifications = []; - foreach ($this->parameters['recipients'] as $recipient) { - $this->parameters['data']['userID'] = $recipient->userID; - $this->parameters['data']['mailNotified'] = (($recipient->mailNotificationType == 'none' || $recipient->mailNotificationType == 'instant') ? 1 : 0); - $notification = $this->create(); - - $notifications[$recipient->userID] = [ - 'isNew' => true, - 'object' => $notification - ]; - } - - // insert author - $sql = "INSERT INTO wcf".WCF_N."_user_notification_author - (notificationID, authorID, time) - VALUES (?, ?, ?)"; - $statement = WCF::getDB()->prepareStatement($sql); - - WCF::getDB()->beginTransaction(); - foreach ($notifications as $notificationData) { - $statement->execute([ - $notificationData['object']->notificationID, - $this->parameters['authorID'] ?: null, - TIME_NOW - ]); - } - WCF::getDB()->commitTransaction(); - - return $notifications; - } - - /** - * Creates a notification or adds another author to an existing one. - * - * @return mixed[][] - */ - public function createStackable() { - // get existing notifications - $notificationList = new UserNotificationList(); - $notificationList->getConditionBuilder()->add("eventID = ?", [$this->parameters['data']['eventID']]); - $notificationList->getConditionBuilder()->add("eventHash = ?", [$this->parameters['data']['eventHash']]); - $notificationList->getConditionBuilder()->add("userID IN (?)", [array_keys($this->parameters['recipients'])]); - $notificationList->getConditionBuilder()->add("confirmTime = ?", [0]); - $notificationList->readObjects(); - $existingNotifications = []; - foreach ($notificationList as $notification) { - $existingNotifications[$notification->userID] = $notification; - } - - $notifications = []; - foreach ($this->parameters['recipients'] as $recipient) { - $notification = (isset($existingNotifications[$recipient->userID]) ? $existingNotifications[$recipient->userID] : null); - $isNew = ($notification === null); - - if ($notification === null) { - $this->parameters['data']['userID'] = $recipient->userID; - $this->parameters['data']['mailNotified'] = (($recipient->mailNotificationType == 'none' || $recipient->mailNotificationType == 'instant') ? 1 : 0); - $notification = $this->create(); - } - - $notifications[$recipient->userID] = [ - 'isNew' => $isNew, - 'object' => $notification - ]; - } - - uasort($notifications, function ($a, $b) { - if ($a['object']->notificationID == $b['object']->notificationID) { - return 0; - } - else if ($a['object']->notificationID < $b['object']->notificationID) { - return -1; - } - - return 1; - }); - - // insert author - $sql = "INSERT IGNORE INTO wcf".WCF_N."_user_notification_author - (notificationID, authorID, time) - VALUES (?, ?, ?)"; - $authorStatement = WCF::getDB()->prepareStatement($sql); - - // update trigger count - $sql = "UPDATE wcf".WCF_N."_user_notification - SET timesTriggered = timesTriggered + ?, - guestTimesTriggered = guestTimesTriggered + ? - WHERE notificationID = ?"; - $triggerStatement = WCF::getDB()->prepareStatement($sql); - - WCF::getDB()->beginTransaction(); - $notificationIDs = []; - foreach ($notifications as $notificationData) { - $notificationIDs[] = $notificationData['object']->notificationID; - - $authorStatement->execute([ - $notificationData['object']->notificationID, - $this->parameters['authorID'] ?: null, - TIME_NOW - ]); - $triggerStatement->execute([ - 1, - $this->parameters['authorID'] ? 0 : 1, - $notificationData['object']->notificationID - ]); - } - WCF::getDB()->commitTransaction(); - - $notificationList = new UserNotificationList(); - $notificationList->setObjectIDs($notificationIDs); - $notificationList->readObjects(); - $updatedNotifications = $notificationList->getObjects(); - - $notifications = array_map(function ($notificationData) use ($updatedNotifications) { - $notificationData['object'] = $updatedNotifications[$notificationData['object']->notificationID]; - return $notificationData; - }, $notifications); - - return $notifications; - } - - /** - * Validates the 'getOutstandingNotifications' action. - */ - public function validateGetOutstandingNotifications() { - // does nothing - } - - /** - * Loads user notifications. - * - * @return mixed[] - */ - public function getOutstandingNotifications() { - $notifications = UserNotificationHandler::getInstance()->getMixedNotifications(); - WCF::getTPL()->assign([ - 'notifications' => $notifications - ]); - - return [ - 'template' => WCF::getTPL()->fetch('notificationListUserPanel'), - 'totalCount' => $notifications['notificationCount'] - ]; - } - - /** - * Validates parameters to mark a notification as confirmed. - */ - public function validateMarkAsConfirmed() { - $this->notificationEditor = $this->getSingleObject(); - if ($this->notificationEditor->userID != WCF::getUser()->userID) { - throw new PermissionDeniedException(); - } - } - - /** - * Marks a notification as confirmed. - * - * @return array - */ - public function markAsConfirmed() { - UserNotificationHandler::getInstance()->markAsConfirmedByID($this->notificationEditor->notificationID); - - return [ - 'markAsRead' => $this->notificationEditor->notificationID, - 'totalCount' => UserNotificationHandler::getInstance()->getNotificationCount(true) - ]; - } - - /** - * Validates parameters to mark all notifications of current user as confirmed. - */ - public function validateMarkAllAsConfirmed() { - // does nothing - } - - /** - * Marks all notifications of current user as confirmed. - * - * @return boolean[] - */ - public function markAllAsConfirmed() { - // Step 1) Find the IDs of the unread notifications. - // This is done in a separate step, because this allows the UPDATE query to - // leverage fine-grained locking of exact rows based off the PRIMARY KEY. - // Simply updating all notifications belonging to a specific user will need - // to prevent concurrent threads from inserting new notifications for proper - // consistency, possibly leading to deadlocks. - $sql = "SELECT notificationID - FROM wcf".WCF_N."_user_notification - WHERE userID = ? - AND confirmTime = ? - AND time < ?"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute([ - WCF::getUser()->userID, - 0, - TIME_NOW, - ]); - $notificationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); - - if (!empty($notificationIDs)) { - // Step 2) Mark the notifications as read. - $condition = new PreparedStatementConditionBuilder(); - $condition->add('notificationID IN (?)', [$notificationIDs]); - - $sql = "UPDATE wcf".WCF_N."_user_notification - SET confirmTime = ? - {$condition}"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute(\array_merge( - [TIME_NOW], - $condition->getParameters() - )); - - // Step 3) Delete notification_to_user assignments (mimic legacy notification system) - - // This conditions technically is not required, because notificationIDs are unique. - // As this is not enforced at the database layer we play safe until this legacy table - // finally is removed. - $condition->add('userID = ?', [WCF::getUser()->userID]); - - $sql = "DELETE FROM wcf".WCF_N."_user_notification_to_user - {$condition}"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute($condition->getParameters()); - } - - // Step 4) Clear cached values. - UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'userNotificationCount'); - - return [ - 'markAllAsRead' => true - ]; - } +class UserNotificationAction extends AbstractDatabaseObjectAction +{ + /** + * notification editor object + * @var UserNotificationEditor + */ + public $notificationEditor; + + /** + * @inheritDoc + * @return UserNotification + */ + public function create() + { + /** @var UserNotification $notification */ + $notification = parent::create(); + + $sql = "INSERT INTO wcf" . WCF_N . "_user_notification_to_user + (notificationID, userID) + VALUES (?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute([ + $notification->notificationID, + $notification->userID, + ]); + + return $notification; + } + + /** + * Creates a simple notification without stacking support, applies to legacy notifications too. + * + * @return mixed[][] + */ + public function createDefault() + { + $notifications = []; + foreach ($this->parameters['recipients'] as $recipient) { + $this->parameters['data']['userID'] = $recipient->userID; + $this->parameters['data']['mailNotified'] = (($recipient->mailNotificationType == 'none' || $recipient->mailNotificationType == 'instant') ? 1 : 0); + $notification = $this->create(); + + $notifications[$recipient->userID] = [ + 'isNew' => true, + 'object' => $notification, + ]; + } + + // insert author + $sql = "INSERT INTO wcf" . WCF_N . "_user_notification_author + (notificationID, authorID, time) + VALUES (?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($notifications as $notificationData) { + $statement->execute([ + $notificationData['object']->notificationID, + $this->parameters['authorID'] ?: null, + TIME_NOW, + ]); + } + WCF::getDB()->commitTransaction(); + + return $notifications; + } + + /** + * Creates a notification or adds another author to an existing one. + * + * @return mixed[][] + */ + public function createStackable() + { + // get existing notifications + $notificationList = new UserNotificationList(); + $notificationList->getConditionBuilder()->add("eventID = ?", [$this->parameters['data']['eventID']]); + $notificationList->getConditionBuilder()->add("eventHash = ?", [$this->parameters['data']['eventHash']]); + $notificationList->getConditionBuilder()->add("userID IN (?)", [\array_keys($this->parameters['recipients'])]); + $notificationList->getConditionBuilder()->add("confirmTime = ?", [0]); + $notificationList->readObjects(); + $existingNotifications = []; + foreach ($notificationList as $notification) { + $existingNotifications[$notification->userID] = $notification; + } + + $notifications = []; + foreach ($this->parameters['recipients'] as $recipient) { + $notification = ($existingNotifications[$recipient->userID] ?? null); + $isNew = ($notification === null); + + if ($notification === null) { + $this->parameters['data']['userID'] = $recipient->userID; + $this->parameters['data']['mailNotified'] = (($recipient->mailNotificationType == 'none' || $recipient->mailNotificationType == 'instant') ? 1 : 0); + $notification = $this->create(); + } + + $notifications[$recipient->userID] = [ + 'isNew' => $isNew, + 'object' => $notification, + ]; + } + + \uasort($notifications, static function ($a, $b) { + if ($a['object']->notificationID == $b['object']->notificationID) { + return 0; + } elseif ($a['object']->notificationID < $b['object']->notificationID) { + return -1; + } + + return 1; + }); + + // insert author + $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_notification_author + (notificationID, authorID, time) + VALUES (?, ?, ?)"; + $authorStatement = WCF::getDB()->prepareStatement($sql); + + // update trigger count + $sql = "UPDATE wcf" . WCF_N . "_user_notification + SET timesTriggered = timesTriggered + ?, + guestTimesTriggered = guestTimesTriggered + ? + WHERE notificationID = ?"; + $triggerStatement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + $notificationIDs = []; + foreach ($notifications as $notificationData) { + $notificationIDs[] = $notificationData['object']->notificationID; + + $authorStatement->execute([ + $notificationData['object']->notificationID, + $this->parameters['authorID'] ?: null, + TIME_NOW, + ]); + $triggerStatement->execute([ + 1, + $this->parameters['authorID'] ? 0 : 1, + $notificationData['object']->notificationID, + ]); + } + WCF::getDB()->commitTransaction(); + + $notificationList = new UserNotificationList(); + $notificationList->setObjectIDs($notificationIDs); + $notificationList->readObjects(); + $updatedNotifications = $notificationList->getObjects(); + + $notifications = \array_map(static function ($notificationData) use ($updatedNotifications) { + $notificationData['object'] = $updatedNotifications[$notificationData['object']->notificationID]; + + return $notificationData; + }, $notifications); + + return $notifications; + } + + /** + * Validates the 'getOutstandingNotifications' action. + */ + public function validateGetOutstandingNotifications() + { + // does nothing + } + + /** + * Loads user notifications. + * + * @return mixed[] + */ + public function getOutstandingNotifications() + { + $notifications = UserNotificationHandler::getInstance()->getMixedNotifications(); + WCF::getTPL()->assign([ + 'notifications' => $notifications, + ]); + + return [ + 'template' => WCF::getTPL()->fetch('notificationListUserPanel'), + 'totalCount' => $notifications['notificationCount'], + ]; + } + + /** + * Validates parameters to mark a notification as confirmed. + */ + public function validateMarkAsConfirmed() + { + $this->notificationEditor = $this->getSingleObject(); + if ($this->notificationEditor->userID != WCF::getUser()->userID) { + throw new PermissionDeniedException(); + } + } + + /** + * Marks a notification as confirmed. + * + * @return array + */ + public function markAsConfirmed() + { + UserNotificationHandler::getInstance()->markAsConfirmedByID($this->notificationEditor->notificationID); + + return [ + 'markAsRead' => $this->notificationEditor->notificationID, + 'totalCount' => UserNotificationHandler::getInstance()->getNotificationCount(true), + ]; + } + + /** + * Validates parameters to mark all notifications of current user as confirmed. + */ + public function validateMarkAllAsConfirmed() + { + // does nothing + } + + /** + * Marks all notifications of current user as confirmed. + * + * @return bool[] + */ + public function markAllAsConfirmed() + { + // Step 1) Find the IDs of the unread notifications. + // This is done in a separate step, because this allows the UPDATE query to + // leverage fine-grained locking of exact rows based off the PRIMARY KEY. + // Simply updating all notifications belonging to a specific user will need + // to prevent concurrent threads from inserting new notifications for proper + // consistency, possibly leading to deadlocks. + $sql = "SELECT notificationID + FROM wcf" . WCF_N . "_user_notification + WHERE userID = ? + AND confirmTime = ? + AND time < ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute([ + WCF::getUser()->userID, + 0, + TIME_NOW, + ]); + $notificationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); + - // Step 2) Mark the notifications as read. - $condition = new PreparedStatementConditionBuilder(); - $condition->add('notificationID IN (?)', [$notificationIDs]); ++ if (!empty($notificationIDs)) { ++ // Step 2) Mark the notifications as read. ++ $condition = new PreparedStatementConditionBuilder(); ++ $condition->add('notificationID IN (?)', [$notificationIDs]); + - $sql = "UPDATE wcf" . WCF_N . "_user_notification ++ $sql = "UPDATE wcf" . WCF_N . "_user_notification + SET confirmTime = ? + {$condition}"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute(\array_merge( - [TIME_NOW], - $condition->getParameters() - )); ++ $statement = WCF::getDB()->prepareStatement($sql); ++ $statement->execute(\array_merge([TIME_NOW], $condition->getParameters())); + - // Step 3) Delete notification_to_user assignments (mimic legacy notification system) ++ // Step 3) Delete notification_to_user assignments (mimic legacy notification system) + - // This conditions technically is not required, because notificationIDs are unique. - // As this is not enforced at the database layer we play safe until this legacy table - // finally is removed. - $condition->add('userID = ?', [WCF::getUser()->userID]); ++ // This conditions technically is not required, because notificationIDs are unique. ++ // As this is not enforced at the database layer we play safe until this legacy table ++ // finally is removed. ++ $condition->add('userID = ?', [WCF::getUser()->userID]); + - $sql = "DELETE FROM wcf" . WCF_N . "_user_notification_to_user ++ $sql = "DELETE FROM wcf" . WCF_N . "_user_notification_to_user + {$condition}"; - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute($condition->getParameters()); ++ $statement = WCF::getDB()->prepareStatement($sql); ++ $statement->execute($condition->getParameters()); ++ } + + // Step 4) Clear cached values. + UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'userNotificationCount'); + + return [ + 'markAllAsRead' => true, + ]; + } }