2 namespace wcf\system\user\notification
;
3 use wcf\data\
object\type\ObjectTypeCache
;
4 use wcf\data\user\notification\event\recipient\UserNotificationEventRecipientList
;
5 use wcf\data\user\notification\event\UserNotificationEventList
;
6 use wcf\data\user\notification\UserNotification
;
7 use wcf\data\user\notification\UserNotificationAction
;
8 use wcf\data\user\notification\UserNotificationList
;
9 use wcf\data\user\User
;
10 use wcf\data\user\UserEditor
;
11 use wcf\data\user\UserProfile
;
12 use wcf\system\cache\builder\UserNotificationEventCacheBuilder
;
13 use wcf\system\database\util\PreparedStatementConditionBuilder
;
14 use wcf\system\exception\SystemException
;
15 use wcf\system\mail\Mail
;
16 use wcf\system\user\notification\event\IUserNotificationEvent
;
17 use wcf\system\user\notification\
object\IUserNotificationObject
;
18 use wcf\system\user\storage\UserStorageHandler
;
19 use wcf\system\SingletonFactory
;
21 use wcf\util\StringUtil
;
24 * Handles user notifications.
26 * @author Marcel Werk, Oliver Kliebisch
27 * @copyright 2001-2014 WoltLab GmbH, Oliver Kliebisch
28 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
29 * @package com.woltlab.wcf
30 * @subpackage system.user.notification
31 * @category Community Framework
33 class UserNotificationHandler
extends SingletonFactory
{
35 * list of available object types
38 protected $availableObjectTypes = array();
41 * list of available events
44 protected $availableEvents = array();
47 * number of outstanding notifications
50 protected $notificationCount = null;
53 * list of object types
54 * @var array<\wcf\data\object\type\ObjectType>
56 protected $objectTypes = array();
59 * @see \wcf\system\SingletonFactory::init()
61 protected function init() {
62 // get available object types
63 $this->objectTypes
= ObjectTypeCache
::getInstance()->getObjectTypes('com.woltlab.wcf.notification.objectType');
64 foreach ($this->objectTypes
as $typeName => $object) {
65 $this->availableObjectTypes
[$typeName] = $object->getProcessor();
68 // get available events
69 $this->availableEvents
= UserNotificationEventCacheBuilder
::getInstance()->getData();
73 * Triggers a notification event.
75 * @param string $eventName
76 * @param string $objectType
77 * @param \wcf\system\user\notification\object\IUserNotificationObject $notificationObject
78 * @param array<integer> $recipientIDs
79 * @param array<mixed> $additionalData
81 public function fireEvent($eventName, $objectType, IUserNotificationObject
$notificationObject, array $recipientIDs, array $additionalData = array()) {
82 // check given object type and event name
83 if (!isset($this->availableEvents
[$objectType][$eventName])) {
84 throw new SystemException("Unknown event ".$objectType."-".$eventName." given");
88 $objectTypeObject = $this->availableObjectTypes
[$objectType];
89 $event = $this->availableEvents
[$objectType][$eventName];
91 // get author's profile
93 if ($notificationObject->getAuthorID()) {
94 if ($notificationObject->getAuthorID() == WCF
::getUser()->userID
) {
95 $userProfile = new UserProfile(WCF
::getUser());
98 $userProfile = UserProfile
::getUserProfile($notificationObject->getAuthorID());
101 if ($userProfile === null) {
102 $userProfile = new UserProfile(new User(null, array()));
106 $event->setObject(new UserNotification(null, array()), $notificationObject, $userProfile, $additionalData);
108 // find existing events
110 $conditionBuilder = new PreparedStatementConditionBuilder();
111 $conditionBuilder->add('notification_to_user.notificationID = notification.notificationID');
112 $conditionBuilder->add('notification_to_user.userID IN (?)', array($recipientIDs));
113 $conditionBuilder->add('notification.eventHash = ?', array($event->getEventHash()));
114 $sql = "SELECT notification_to_user.userID
115 FROM wcf".WCF_N
."_user_notification notification,
116 wcf".WCF_N
."_user_notification_to_user notification_to_user
118 $statement = WCF
::getDB()->prepareStatement($sql);
119 $statement->execute($conditionBuilder->getParameters());
120 while ($row = $statement->fetchArray()) $userIDs[] = $row['userID'];
122 // skip recipients with outstanding notifications
123 if (!empty($userIDs)) {
124 $recipientIDs = array_diff($recipientIDs, $userIDs);
125 if (empty($recipientIDs)) return;
129 $recipientList = new UserNotificationEventRecipientList();
130 $recipientList->getConditionBuilder()->add('event_to_user.eventID = ?', array($event->eventID
));
131 $recipientList->getConditionBuilder()->add('event_to_user.userID IN (?)', array($recipientIDs));
132 $recipientList->readObjects();
133 if (count($recipientList)) {
134 // find existing notification
135 $notification = UserNotification
::getNotification($objectTypeObject->packageID
, $event->eventID
, $notificationObject->getObjectID());
136 if ($notification !== null) {
137 // only update recipients
138 $action = new UserNotificationAction(array($notification), 'addRecipients', array(
139 'recipients' => $recipientList->getObjects()
141 $action->executeAction();
144 // create new notification
145 $action = new UserNotificationAction(array(), 'create', array(
147 'packageID' => $objectTypeObject->packageID
,
148 'eventID' => $event->eventID
,
149 'objectID' => $notificationObject->getObjectID(),
150 'authorID' => ($event->getAuthorID() ?
: null),
152 'eventHash' => $event->getEventHash(),
153 'additionalData' => serialize($additionalData)
155 'recipients' => $recipientList->getObjects()
157 $result = $action->executeAction();
158 $notification = $result['returnValues'];
161 // sends notifications
162 foreach ($recipientList->getObjects() as $recipient) {
163 if ($recipient->mailNotificationType
== 'instant') {
164 $this->sendInstantMailNotification($notification, $recipient, $event);
168 // reset notification count
169 UserStorageHandler
::getInstance()->reset($recipientList->getObjectIDs(), 'userNotificationCount');
174 * Returns the number of outstanding notifications for the active user.
178 public function getNotificationCount() {
179 if ($this->notificationCount
=== null) {
180 $this->notificationCount
= 0;
182 if (WCF
::getUser()->userID
) {
184 UserStorageHandler
::getInstance()->loadStorage(array(WCF
::getUser()->userID
));
187 $data = UserStorageHandler
::getInstance()->getStorage(array(WCF
::getUser()->userID
), 'userNotificationCount');
189 // cache does not exist or is outdated
190 if ($data[WCF
::getUser()->userID
] === null) {
191 $conditionBuilder = new PreparedStatementConditionBuilder();
192 $conditionBuilder->add('notification.notificationID = notification_to_user.notificationID');
193 $conditionBuilder->add('notification_to_user.userID = ?', array(WCF
::getUser()->userID
));
195 $sql = "SELECT COUNT(*) AS count
196 FROM wcf".WCF_N
."_user_notification_to_user notification_to_user,
197 wcf".WCF_N
."_user_notification notification
198 ".$conditionBuilder->__toString();
199 $statement = WCF
::getDB()->prepareStatement($sql);
200 $statement->execute($conditionBuilder->getParameters());
201 $row = $statement->fetchArray();
202 $this->notificationCount
= $row['count'];
204 // update storage data
205 UserStorageHandler
::getInstance()->update(WCF
::getUser()->userID
, 'userNotificationCount', serialize($this->notificationCount
));
208 $this->notificationCount
= unserialize($data[WCF
::getUser()->userID
]);
213 return $this->notificationCount
;
217 * Returns a limited list of outstanding notifications.
219 * @param integer $limit
220 * @param integer $offset
221 * @return array<array>
223 public function getNotifications($limit = 5, $offset = 0) {
224 // build enormous query
225 $conditions = new PreparedStatementConditionBuilder();
226 $conditions->add("notification_to_user.userID = ?", array(WCF
::getUser()->userID
));
227 $conditions->add("notification.notificationID = notification_to_user.notificationID");
229 $sql = "SELECT notification_to_user.notificationID, notification_event.eventID,
230 object_type.objectType, notification.objectID,
231 notification.additionalData, notification.authorID,
233 FROM wcf".WCF_N
."_user_notification_to_user notification_to_user,
234 wcf".WCF_N
."_user_notification notification
235 LEFT JOIN wcf".WCF_N
."_user_notification_event notification_event
236 ON (notification_event.eventID = notification.eventID)
237 LEFT JOIN wcf".WCF_N
."_object_type object_type
238 ON (object_type.objectTypeID = notification_event.objectTypeID)
240 ORDER BY notification.time DESC";
241 $statement = WCF
::getDB()->prepareStatement($sql, $limit, $offset);
242 $statement->execute($conditions->getParameters());
244 $authorIDs = $events = $objectTypes = $eventIDs = $notificationIDs = array();
245 while ($row = $statement->fetchArray()) {
248 // cache object types
249 if (!isset($objectTypes[$row['objectType']])) {
250 $objectTypes[$row['objectType']] = array(
251 'objectType' => $this->availableObjectTypes
[$row['objectType']],
252 'objectIDs' => array(),
257 $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
258 $eventIDs[] = $row['eventID'];
259 $notificationIDs[] = $row['notificationID'];
260 $authorIDs[] = $row['authorID'];
263 // return an empty set if no notifications exist
264 if (empty($events)) {
267 'notifications' => array()
272 $authors = UserProfile
::getUserProfiles($authorIDs);
273 $unknownAuthor = new UserProfile(new User(null, array('userID' => null, 'username' => WCF
::getLanguage()->get('wcf.user.guest'))));
275 // load objects associated with each object type
276 foreach ($objectTypes as $objectType => $objectData) {
277 $objectTypes[$objectType]['objects'] = $objectData['objectType']->getObjectsByIDs($objectData['objectIDs']);
280 // load required events
281 $eventList = new UserNotificationEventList();
282 $eventList->getConditionBuilder()->add("user_notification_event.eventID IN (?)", array($eventIDs));
283 $eventList->readObjects();
284 $eventObjects = $eventList->getObjects();
286 // load notification objects
287 $notificationList = new UserNotificationList();
288 $notificationList->getConditionBuilder()->add("user_notification.notificationID IN (?)", array($notificationIDs));
289 $notificationList->readObjects();
290 $notificationObjects = $notificationList->getObjects();
292 // build notification data
293 $notifications = array();
294 foreach ($events as $event) {
295 $className = $eventObjects[$event['eventID']]->className
;
296 $class = new $className($eventObjects[$event['eventID']]);
299 $notificationObjects[$event['notificationID']],
300 $objectTypes[$event['objectType']]['objects'][$event['objectID']],
301 (isset($authors[$event['authorID']]) ?
$authors[$event['authorID']] : $unknownAuthor),
302 unserialize($event['additionalData'])
307 'notificationID' => $event['notificationID'],
308 'time' => $event['time']
311 $notifications[] = $data;
315 'count' => count($notifications),
316 'notifications' => $notifications
321 * Returns event object for given object type and event, returns NULL on failure.
323 * @param string $objectType
324 * @param string $eventName
325 * @return \wcf\system\user\notification\event\IUserNotificationEvent
327 public function getEvent($objectType, $eventName) {
328 if (!isset($this->availableEvents
[$objectType][$eventName])) return null;
330 return $this->availableEvents
[$objectType][$eventName];
334 * Retrieves a notification id.
336 * @param integer $eventID
337 * @param integer $objectID
338 * @param integer $authorID
339 * @param integer $time
342 public function getNotificationID($eventID, $objectID, $authorID = null, $time = null) {
343 if ($authorID === null && $time === null) {
344 throw new SystemException("authorID and time cannot be omitted at once.");
347 $conditions = new PreparedStatementConditionBuilder();
348 $conditions->add("eventID = ?", array($eventID));
349 $conditions->add("objectID = ?", array($objectID));
350 if ($authorID !== null) $conditions->add("authorID = ?", array($authorID));
351 if ($time !== null) $conditions->add("time = ?", array($time));
353 $sql = "SELECT notificationID
354 FROM wcf".WCF_N
."_user_notification
356 $statement = WCF
::getDB()->prepareStatement($sql);
357 $statement->execute($conditions->getParameters());
359 $row = $statement->fetchArray();
361 return ($row === false) ?
null : $row['notificationID'];
365 * Returns a list of available object types.
367 * @return array<\wcf\system\user\notification\object\type\IUserNotificationObjectType>
369 public function getAvailableObjectTypes() {
370 return $this->availableObjectTypes
;
374 * Returns a list of available events.
376 * @return array<\wcf\system\user\notification\event\IUserNotificationEvent>
378 public function getAvailableEvents() {
379 return $this->availableEvents
;
383 * Returns object type id by name.
385 * @param string $objectType
388 public function getObjectTypeID($objectType) {
389 if (isset($this->objectTypes
[$objectType])) {
390 return $this->objectTypes
[$objectType]->objectTypeID
;
397 * Returns object type by name.
399 * @param string $objectType
402 public function getObjectTypeProcessor($objectType) {
403 if (isset($this->availableObjectTypes
[$objectType])) {
404 return $this->availableObjectTypes
[$objectType];
411 * Sends the mail notification.
413 * @param \wcf\data\user\notification\UserNotification $notification
414 * @param \wcf\data\user\User $user
415 * @param \wcf\system\user\notification\event\IUserNotificationEvent $event
417 public function sendInstantMailNotification(UserNotification
$notification, User
$user, IUserNotificationEvent
$event) {
418 // recipient's language
419 $event->setLanguage($user->getLanguage());
422 $message = $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.header', array(
427 $message .= $event->getEmailMessage();
429 // append notification mail footer
430 $token = $user->notificationMailToken
;
432 // generate token if not present
433 $token = mb_substr(StringUtil
::getHash(serialize(array($user->userID
, StringUtil
::getRandomID()))), 0, 20);
434 $editor = new UserEditor($user);
435 $editor->update(array('notificationMailToken' => $token));
437 $message .= "\n\n".$user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.footer', array(
440 'notification' => $notification
444 $mail = new Mail(array($user->username
=> $user->email
), $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.subject', array('title' => $event->getEmailTitle())), $message);
445 $mail->setLanguage($user->getLanguage());
450 * Deletes notifications.
452 * @param string $eventName
453 * @param string $objectType
454 * @param array<integer> $recipientIDs
455 * @param array<integer> $objectIDs
457 public function deleteNotifications($eventName, $objectType, array $recipientIDs, array $objectIDs = array()) {
458 // check given object type and event name
459 if (!isset($this->availableEvents
[$objectType][$eventName])) {
460 throw new SystemException("Unknown event ".$objectType."-".$eventName." given");
464 $objectTypeObject = $this->availableObjectTypes
[$objectType];
465 $event = $this->availableEvents
[$objectType][$eventName];
467 // delete notifications
468 $sql = "DELETE FROM wcf".WCF_N
."_user_notification_to_user
469 WHERE notificationID IN (
470 SELECT notificationID
471 FROM wcf".WCF_N
."_user_notification
474 ".(!empty($objectIDs) ?
"AND objectID IN (?".(count($objectIDs) > 1 ?
str_repeat(',?', count($objectIDs) - 1) : '').")" : '')."
476 ".(!empty($recipientIDs) ?
("AND userID IN (?".(count($recipientIDs) > 1 ?
str_repeat(',?', count($recipientIDs) - 1) : '').")") : '');
477 $parameters = array($objectTypeObject->packageID
, $event->eventID
);
478 if (!empty($objectIDs)) $parameters = array_merge($parameters, $objectIDs);
479 if (!empty($recipientIDs)) $parameters = array_merge($parameters, $recipientIDs);
480 $statement = WCF
::getDB()->prepareStatement($sql);
481 $statement->execute($parameters);
484 if (!empty($recipientIDs)) {
485 UserStorageHandler
::getInstance()->reset($recipientIDs, 'userNotificationCount');
488 UserStorageHandler
::getInstance()->resetAll('userNotificationCount');
493 * Returns the user's notification setting for the given event.
495 * @param string $objectType
496 * @param string $eventName
499 public function getEventSetting($objectType, $eventName) {
501 $event = $this->getEvent($objectType, $eventName);
504 $sql = "SELECT mailNotificationType
505 FROM wcf".WCF_N
."_user_notification_event_to_user
508 $statement = WCF
::getDB()->prepareStatement($sql);
509 $statement->execute(array($event->eventID
, WCF
::getUser()->userID
));
510 $row = $statement->fetchArray();
511 if ($row === false) return false;
512 return $row['mailNotificationType'];