/**
* Executes user notification-related actions.
- *
- * @author Marcel Werk
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @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 <http://opensource.org/licenses/lgpl-license.php>
+ * @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,
+ ];
+ }
}