3 namespace wcf\system\cronjob
;
5 use ParagonIE\ConstantTime\Hex
;
6 use wcf\data\cronjob\Cronjob
;
7 use wcf\data\user\notification\event\UserNotificationEventList
;
8 use wcf\data\user\notification\UserNotification
;
9 use wcf\data\user\User
;
10 use wcf\data\user\UserEditor
;
11 use wcf\data\user\UserList
;
12 use wcf\data\user\UserProfile
;
13 use wcf\form\NotificationUnsubscribeForm
;
14 use wcf\system\cache\runtime\UserProfileRuntimeCache
;
15 use wcf\system\database\util\PreparedStatementConditionBuilder
;
16 use wcf\system\email\Email
;
17 use wcf\system\email\mime\MimePartFacade
;
18 use wcf\system\email\mime\RecipientAwareTextMimePart
;
19 use wcf\system\email\UserMailbox
;
20 use wcf\system\request\LinkHandler
;
21 use wcf\system\user\notification\event\IUserNotificationEvent
;
22 use wcf\system\user\notification\UserNotificationHandler
;
26 * Sends daily mail notifications.
29 * @copyright 2001-2019 WoltLab GmbH
30 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
32 class DailyMailNotificationCronjob
extends AbstractCronjob
37 public function execute(Cronjob
$cronjob)
39 parent
::execute($cronjob);
42 $sql = "SELECT DISTINCT userID
43 FROM wcf" . WCF_N
. "_user_notification
44 WHERE mailNotified = ?
47 $statement = WCF
::getDB()->prepareStatement($sql);
53 $userIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
54 if (empty($userIDs)) {
59 $userList = new UserList();
60 $userList->setObjectIDs($userIDs);
61 $userList->readObjects();
62 $users = $userList->getObjects();
65 $conditions = new PreparedStatementConditionBuilder();
66 $conditions->add("notification.userID IN (?)", [$userIDs]);
67 $conditions->add("notification.mailNotified = ?", [0]);
68 $conditions->add("notification.confirmTime = ?", [0]);
70 $sql = "SELECT notification.*, notification_event.eventID, object_type.objectType
71 FROM wcf" . WCF_N
. "_user_notification notification
72 LEFT JOIN wcf" . WCF_N
. "_user_notification_event notification_event
73 ON notification_event.eventID = notification.eventID
74 LEFT JOIN wcf" . WCF_N
. "_object_type object_type
75 ON object_type.objectTypeID = notification_event.objectTypeID
77 ORDER BY notification.time";
78 $statement = WCF
::getDB()->prepareStatement($sql);
79 $statement->execute($conditions->getParameters());
81 // mark notifications as done
82 $conditions = new PreparedStatementConditionBuilder();
83 $conditions->add("userID IN (?)", [$userIDs]);
84 $conditions->add("mailNotified = ?", [0]);
85 $sql = "UPDATE wcf" . WCF_N
. "_user_notification
88 $statement2 = WCF
::getDB()->prepareStatement($sql);
89 $statement2->execute($conditions->getParameters());
92 $eventsToUser = $objectTypes = $eventIDs = $notificationObjects = [];
93 $availableObjectTypes = UserNotificationHandler
::getInstance()->getAvailableObjectTypes();
94 while ($row = $statement->fetchArray()) {
95 if (!isset($eventsToUser[$row['userID']])) {
96 $eventsToUser[$row['userID']] = [];
98 $eventsToUser[$row['userID']][] = $row['notificationID'];
100 // cache object types
101 if (!isset($objectTypes[$row['objectType']])) {
102 $objectTypes[$row['objectType']] = [
103 'objectType' => $availableObjectTypes[$row['objectType']],
109 $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
110 $eventIDs[] = $row['eventID'];
112 $notificationObjects[$row['notificationID']] = new UserNotification(null, $row);
116 $conditions = new PreparedStatementConditionBuilder();
117 $conditions->add("notificationID IN (?)", [\array_keys
($notificationObjects)]);
118 $sql = "SELECT notificationID, authorID
119 FROM wcf" . WCF_N
. "_user_notification_author
122 $statement = WCF
::getDB()->prepareStatement($sql);
123 $statement->execute($conditions->getParameters());
124 $authorIDs = $authorToNotification = [];
125 while ($row = $statement->fetchArray()) {
126 if ($row['authorID']) {
127 $authorIDs[] = $row['authorID'];
130 if (!isset($authorToNotification[$row['notificationID']])) {
131 $authorToNotification[$row['notificationID']] = [];
134 $authorToNotification[$row['notificationID']][] = $row['authorID'];
138 $authors = UserProfileRuntimeCache
::getInstance()->getObjects($authorIDs);
139 $unknownAuthor = new UserProfile(new User(
141 ['userID' => null, 'username' => WCF
::getLanguage()->get('wcf.user.guest')]
144 // load objects associated with each object type
145 foreach ($objectTypes as $objectType => $objectData) {
146 /** @noinspection PhpUndefinedMethodInspection */
147 $objectTypes[$objectType]['objects'] = $objectData['objectType']->getObjectsByIDs($objectData['objectIDs']);
150 // load required events
151 $eventList = new UserNotificationEventList();
152 $eventList->getConditionBuilder()->add("user_notification_event.eventID IN (?)", [$eventIDs]);
153 $eventList->readObjects();
154 $eventObjects = $eventList->getObjects();
156 foreach ($eventsToUser as $userID => $notificationIDs) {
157 if (!isset($users[$userID])) {
160 $user = $users[$userID];
162 // no notifications for disabled or banned users
163 if (!$user->isEmailConfirmed()) {
170 $notifications = \array_map
(static function ($notificationID) use (
171 $notificationObjects,
176 $authorToNotification,
179 $notification = $notificationObjects[$notificationID];
181 $className = $eventObjects[$notification->eventID
]->className
;
183 /** @var IUserNotificationEvent $class */
184 $class = new $className($eventObjects[$notification->eventID
]);
187 $objectTypes[$notification->objectType
]['objects'][$notification->objectID
],
188 ($authors[$notification->authorID
] ??
$unknownAuthor),
189 $notification->additionalData
191 $class->setLanguage($user->getLanguage());
193 if (isset($authorToNotification[$notification->notificationID
])) {
195 foreach ($authorToNotification[$notification->notificationID
] as $userID) {
197 $eventAuthors[0] = $unknownAuthor;
198 } elseif (isset($authors[$userID])) {
199 $eventAuthors[$userID] = $authors[$userID];
202 if (!empty($eventAuthors)) {
203 $class->setAuthors($eventAuthors);
207 $message = $class->getEmailMessage('daily');
208 if (\
is_array($message)) {
209 if (!isset($message['variables'])) {
210 $message['variables'] = [];
213 return \array_merge
($message['variables'], [
214 'notificationContent' => $message,
216 'notificationType' => 'daily',
217 'variables' => $message['variables'], // deprecated, but is kept for backwards compatibility
221 'notificationContent' => $message,
223 'notificationType' => 'daily',
226 }, $notificationIDs);
228 // generate token if not present
229 if (!$user->notificationMailToken
) {
230 $token = Hex
::encode(\random_bytes
(10));
231 $editor = new UserEditor($user);
232 $editor->update(['notificationMailToken' => $token]);
235 $user = new User($user->userID
);
238 $email = new Email();
239 $email->setSubject($user->getLanguage()->getDynamicVariable(
240 'wcf.user.notification.mail.daily.subject',
241 ['count' => \
count($notifications)]
243 $email->addRecipient(new UserMailbox($user));
244 $email->setListID('daily.notification');
245 $email->setListUnsubscribe(
246 LinkHandler
::getInstance()->getControllerLink(
247 NotificationUnsubscribeForm
::class,
249 'userID' => $user->userID
,
250 'token' => $user->notificationMailToken
,
256 $maximumNotificationCount = 7;
257 $notificationCount = \
count($notifications);
258 if ($notificationCount === $maximumNotificationCount +
1) {
259 $maximumNotificationCount++
;
261 $remainingNotificationCount = $notificationCount - $maximumNotificationCount;
262 $notifications = \array_slice
($notifications, 0, $maximumNotificationCount);
264 $html = new RecipientAwareTextMimePart(
266 'email_dailyNotification',
269 'notifications' => $notifications,
270 'remaining' => $remainingNotificationCount,
271 'maximum' => $maximumNotificationCount,
272 'notificationCount' => $notificationCount,
275 $plainText = new RecipientAwareTextMimePart(
277 'email_dailyNotification',
280 'notifications' => $notifications,
281 'remaining' => $remainingNotificationCount,
282 'maximum' => $maximumNotificationCount,
283 'notificationCount' => $notificationCount,
286 $email->setBody(new MimePartFacade([$html, $plainText]));