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 * Returns all events for given object type.
336 * @param string $objectType
337 * @return array<\wcf\system\user\notification\event\IUserNotificationEvent>
339 public function getEvents($objectType) {
340 if (!isset($this->availableEvents
[$objectType])) return array();
342 return $this->availableEvents
[$objectType];
346 * Retrieves a notification id.
348 * @param integer $eventID
349 * @param integer $objectID
350 * @param integer $authorID
351 * @param integer $time
354 public function getNotificationID($eventID, $objectID, $authorID = null, $time = null) {
355 if ($authorID === null && $time === null) {
356 throw new SystemException("authorID and time cannot be omitted at once.");
359 $conditions = new PreparedStatementConditionBuilder();
360 $conditions->add("eventID = ?", array($eventID));
361 $conditions->add("objectID = ?", array($objectID));
362 if ($authorID !== null) $conditions->add("authorID = ?", array($authorID));
363 if ($time !== null) $conditions->add("time = ?", array($time));
365 $sql = "SELECT notificationID
366 FROM wcf".WCF_N
."_user_notification
368 $statement = WCF
::getDB()->prepareStatement($sql);
369 $statement->execute($conditions->getParameters());
371 $row = $statement->fetchArray();
373 return ($row === false) ?
null : $row['notificationID'];
377 * Returns a list of available object types.
379 * @return array<\wcf\system\user\notification\object\type\IUserNotificationObjectType>
381 public function getAvailableObjectTypes() {
382 return $this->availableObjectTypes
;
386 * Returns a list of available events.
388 * @return array<\wcf\system\user\notification\event\IUserNotificationEvent>
390 public function getAvailableEvents() {
391 return $this->availableEvents
;
395 * Returns object type id by name.
397 * @param string $objectType
400 public function getObjectTypeID($objectType) {
401 if (isset($this->objectTypes
[$objectType])) {
402 return $this->objectTypes
[$objectType]->objectTypeID
;
409 * Returns object type by name.
411 * @param string $objectType
414 public function getObjectTypeProcessor($objectType) {
415 if (isset($this->availableObjectTypes
[$objectType])) {
416 return $this->availableObjectTypes
[$objectType];
423 * Sends the mail notification.
425 * @param \wcf\data\user\notification\UserNotification $notification
426 * @param \wcf\data\user\User $user
427 * @param \wcf\system\user\notification\event\IUserNotificationEvent $event
429 public function sendInstantMailNotification(UserNotification
$notification, User
$user, IUserNotificationEvent
$event) {
430 // recipient's language
431 $event->setLanguage($user->getLanguage());
434 $message = $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.header', array(
439 $message .= $event->getEmailMessage();
441 // append notification mail footer
442 $token = $user->notificationMailToken
;
444 // generate token if not present
445 $token = mb_substr(StringUtil
::getHash(serialize(array($user->userID
, StringUtil
::getRandomID()))), 0, 20);
446 $editor = new UserEditor($user);
447 $editor->update(array('notificationMailToken' => $token));
449 $message .= "\n\n".$user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.footer', array(
452 'notification' => $notification
456 $mail = new Mail(array($user->username
=> $user->email
), $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.subject', array('title' => $event->getEmailTitle())), $message);
457 $mail->setLanguage($user->getLanguage());
462 * Deletes notifications.
464 * @param string $eventName
465 * @param string $objectType
466 * @param array<integer> $recipientIDs
467 * @param array<integer> $objectIDs
469 public function deleteNotifications($eventName, $objectType, array $recipientIDs, array $objectIDs = array()) {
470 // check given object type and event name
471 if (!isset($this->availableEvents
[$objectType][$eventName])) {
472 throw new SystemException("Unknown event ".$objectType."-".$eventName." given");
476 $objectTypeObject = $this->availableObjectTypes
[$objectType];
477 $event = $this->availableEvents
[$objectType][$eventName];
479 // delete notifications
480 $sql = "DELETE FROM wcf".WCF_N
."_user_notification_to_user
481 WHERE notificationID IN (
482 SELECT notificationID
483 FROM wcf".WCF_N
."_user_notification
486 ".(!empty($objectIDs) ?
"AND objectID IN (?".(count($objectIDs) > 1 ?
str_repeat(',?', count($objectIDs) - 1) : '').")" : '')."
488 ".(!empty($recipientIDs) ?
("AND userID IN (?".(count($recipientIDs) > 1 ?
str_repeat(',?', count($recipientIDs) - 1) : '').")") : '');
489 $parameters = array($objectTypeObject->packageID
, $event->eventID
);
490 if (!empty($objectIDs)) $parameters = array_merge($parameters, $objectIDs);
491 if (!empty($recipientIDs)) $parameters = array_merge($parameters, $recipientIDs);
492 $statement = WCF
::getDB()->prepareStatement($sql);
493 $statement->execute($parameters);
496 if (!empty($recipientIDs)) {
497 UserStorageHandler
::getInstance()->reset($recipientIDs, 'userNotificationCount');
500 UserStorageHandler
::getInstance()->resetAll('userNotificationCount');
505 * Returns the user's notification setting for the given event.
507 * @param string $objectType
508 * @param string $eventName
511 public function getEventSetting($objectType, $eventName) {
513 $event = $this->getEvent($objectType, $eventName);
516 $sql = "SELECT mailNotificationType
517 FROM wcf".WCF_N
."_user_notification_event_to_user
520 $statement = WCF
::getDB()->prepareStatement($sql);
521 $statement->execute(array($event->eventID
, WCF
::getUser()->userID
));
522 $row = $statement->fetchArray();
523 if ($row === false) return false;
524 return $row['mailNotificationType'];