Introduce new flag variable for email confirmation
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / cronjob / DailyMailNotificationCronjob.class.php
CommitLineData
320f4a6d
MW
1<?php
2namespace wcf\system\cronjob;
3use wcf\data\cronjob\Cronjob;
4use wcf\data\user\notification\event\UserNotificationEventList;
707ac913
AE
5use wcf\data\user\notification\UserNotification;
6use wcf\data\user\User;
320f4a6d
MW
7use wcf\data\user\UserEditor;
8use wcf\data\user\UserList;
9use wcf\data\user\UserProfile;
a8c5936e 10use wcf\system\cache\runtime\UserProfileRuntimeCache;
320f4a6d 11use wcf\system\database\util\PreparedStatementConditionBuilder;
bc1a4c3a
TD
12use wcf\system\email\mime\MimePartFacade;
13use wcf\system\email\mime\RecipientAwareTextMimePart;
14use wcf\system\email\Email;
15use wcf\system\email\UserMailbox;
e4499881 16use wcf\system\user\notification\event\IUserNotificationEvent;
320f4a6d
MW
17use wcf\system\user\notification\UserNotificationHandler;
18use wcf\system\WCF;
320f4a6d
MW
19
20/**
21 * Sends daily mail notifications.
22 *
23 * @author Marcel Werk
7b7b9764 24 * @copyright 2001-2019 WoltLab GmbH
320f4a6d 25 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 26 * @package WoltLabSuite\Core\System\Cronjob
320f4a6d
MW
27 */
28class DailyMailNotificationCronjob extends AbstractCronjob {
29 /**
0fcfe5f6 30 * @inheritDoc
320f4a6d
MW
31 */
32 public function execute(Cronjob $cronjob) {
33 parent::execute($cronjob);
34
35 // get user ids
707ac913
AE
36 $sql = "SELECT DISTINCT userID
37 FROM wcf".WCF_N."_user_notification
5cc33e88
MW
38 WHERE mailNotified = ?
39 AND time < ?
40 AND confirmTime = ?";
707ac913 41 $statement = WCF::getDB()->prepareStatement($sql);
058cbd6a 42 $statement->execute([
5cc33e88 43 0,
e78a8ae2 44 TIME_NOW - 3600 * 23,
5cc33e88 45 0
058cbd6a 46 ]);
cd975610 47 $userIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
320f4a6d
MW
48 if (empty($userIDs)) return;
49
50 // get users
51 $userList = new UserList();
52 $userList->setObjectIDs($userIDs);
53 $userList->readObjects();
54 $users = $userList->getObjects();
55
56 // get notifications
57 $conditions = new PreparedStatementConditionBuilder();
058cbd6a
MS
58 $conditions->add("notification.userID IN (?)", [$userIDs]);
59 $conditions->add("notification.mailNotified = ?", [0]);
60 $conditions->add("notification.confirmTime = ?", [0]);
320f4a6d 61
707ac913
AE
62 $sql = "SELECT notification.*, notification_event.eventID, object_type.objectType
63 FROM wcf".WCF_N."_user_notification notification
320f4a6d
MW
64 LEFT JOIN wcf".WCF_N."_user_notification_event notification_event
65 ON (notification_event.eventID = notification.eventID)
66 LEFT JOIN wcf".WCF_N."_object_type object_type
67 ON (object_type.objectTypeID = notification_event.objectTypeID)
68 ".$conditions."
69 ORDER BY notification.time";
70 $statement = WCF::getDB()->prepareStatement($sql);
71 $statement->execute($conditions->getParameters());
72
73 // mark notifications as done
74 $conditions = new PreparedStatementConditionBuilder();
058cbd6a
MS
75 $conditions->add("userID IN (?)", [$userIDs]);
76 $conditions->add("mailNotified = ?", [0]);
707ac913 77 $sql = "UPDATE wcf".WCF_N."_user_notification
320f4a6d
MW
78 SET mailNotified = 1
79 ".$conditions;
80 $statement2 = WCF::getDB()->prepareStatement($sql);
81 $statement2->execute($conditions->getParameters());
82
83 // collect data
058cbd6a 84 $eventsToUser = $objectTypes = $eventIDs = $notificationObjects = [];
320f4a6d
MW
85 $availableObjectTypes = UserNotificationHandler::getInstance()->getAvailableObjectTypes();
86 while ($row = $statement->fetchArray()) {
058cbd6a 87 if (!isset($eventsToUser[$row['userID']])) $eventsToUser[$row['userID']] = [];
707ac913 88 $eventsToUser[$row['userID']][] = $row['notificationID'];
320f4a6d
MW
89
90 // cache object types
91 if (!isset($objectTypes[$row['objectType']])) {
058cbd6a 92 $objectTypes[$row['objectType']] = [
15cccae7 93 'objectType' => $availableObjectTypes[$row['objectType']],
058cbd6a
MS
94 'objectIDs' => [],
95 'objects' => []
96 ];
320f4a6d 97 }
15cccae7 98
320f4a6d
MW
99 $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
100 $eventIDs[] = $row['eventID'];
15cccae7 101
707ac913
AE
102 $notificationObjects[$row['notificationID']] = new UserNotification(null, $row);
103 }
104
105 // load authors
106 $conditions = new PreparedStatementConditionBuilder();
058cbd6a 107 $conditions->add("notificationID IN (?)", [array_keys($notificationObjects)]);
707ac913
AE
108 $sql = "SELECT notificationID, authorID
109 FROM wcf".WCF_N."_user_notification_author
110 ".$conditions."
111 ORDER BY time ASC";
112 $statement = WCF::getDB()->prepareStatement($sql);
113 $statement->execute($conditions->getParameters());
058cbd6a 114 $authorIDs = $authorToNotification = [];
707ac913 115 while ($row = $statement->fetchArray()) {
afb18fa2
MS
116 if ($row['authorID']) {
117 $authorIDs[] = $row['authorID'];
707ac913
AE
118 }
119
120 if (!isset($authorToNotification[$row['notificationID']])) {
058cbd6a 121 $authorToNotification[$row['notificationID']] = [];
707ac913
AE
122 }
123
707ac913 124 $authorToNotification[$row['notificationID']][] = $row['authorID'];
320f4a6d
MW
125 }
126
127 // load authors
a8c5936e 128 $authors = UserProfileRuntimeCache::getInstance()->getObjects($authorIDs);
058cbd6a 129 $unknownAuthor = new UserProfile(new User(null, ['userID' => null, 'username' => WCF::getLanguage()->get('wcf.user.guest')]));
320f4a6d
MW
130
131 // load objects associated with each object type
132 foreach ($objectTypes as $objectType => $objectData) {
e4499881 133 /** @noinspection PhpUndefinedMethodInspection */
320f4a6d
MW
134 $objectTypes[$objectType]['objects'] = $objectData['objectType']->getObjectsByIDs($objectData['objectIDs']);
135 }
136
137 // load required events
138 $eventList = new UserNotificationEventList();
058cbd6a 139 $eventList->getConditionBuilder()->add("user_notification_event.eventID IN (?)", [$eventIDs]);
320f4a6d
MW
140 $eventList->readObjects();
141 $eventObjects = $eventList->getObjects();
142
bc1a4c3a 143 foreach ($eventsToUser as $userID => $notificationIDs) {
320f4a6d
MW
144 if (!isset($users[$userID])) continue;
145 $user = $users[$userID];
146
2aaeadbe 147 // no notifications for disabled or banned users
783b8dda 148 if (!$user->isEmailConfirmed()) continue;
2aaeadbe
TD
149 if ($user->banned) continue;
150
a78a118b 151 $notifications = array_map(function ($notificationID) use ($notificationObjects, $eventObjects, $user, $objectTypes, $authors, $authorToNotification, $unknownAuthor) {
707ac913 152 $notification = $notificationObjects[$notificationID];
320f4a6d 153
707ac913 154 $className = $eventObjects[$notification->eventID]->className;
e4499881
MS
155
156 /** @var IUserNotificationEvent $class */
707ac913 157 $class = new $className($eventObjects[$notification->eventID]);
320f4a6d 158 $class->setObject(
707ac913
AE
159 $notification,
160 $objectTypes[$notification->objectType]['objects'][$notification->objectID],
161 (isset($authors[$notification->authorID]) ? $authors[$notification->authorID] : $unknownAuthor),
afb18fa2 162 $notification->additionalData
320f4a6d
MW
163 );
164 $class->setLanguage($user->getLanguage());
165
707ac913 166 if (isset($authorToNotification[$notification->notificationID])) {
058cbd6a 167 $eventAuthors = [];
707ac913 168 foreach ($authorToNotification[$notification->notificationID] as $userID) {
afb18fa2
MS
169 if (!$userID) {
170 $eventAuthors[0] = $unknownAuthor;
171 }
172 else if (isset($authors[$userID])) {
707ac913
AE
173 $eventAuthors[$userID] = $authors[$userID];
174 }
175 }
176 if (!empty($eventAuthors)) {
177 $class->setAuthors($eventAuthors);
178 }
179 }
180
bc1a4c3a
TD
181 $message = $class->getEmailMessage('daily');
182 if (is_array($message)) {
b16a5be6 183 if (!isset($message['variables'])) $message['variables'] = [];
cb955f4e 184 $variables = array_merge($message['variables'], [
bc1a4c3a
TD
185 'notificationContent' => $message,
186 'event' => $class,
b16a5be6 187 'notificationType' => 'daily',
cb955f4e
MS
188 'variables' => $message['variables'] // deprecated, but is kept for backwards compatibility
189 ]);
bc1a4c3a
TD
190
191 return $variables;
192 }
193 else {
194 return [
195 'notificationContent' => $message,
196 'event' => $class,
197 'notificationType' => 'daily'
198 ];
199 }
200 }, $notificationIDs);
320f4a6d 201
bc1a4c3a
TD
202 // generate token if not present
203 if (!$user->notificationMailToken) {
8e0aaeaf 204 $token = bin2hex(\random_bytes(10));
320f4a6d 205 $editor = new UserEditor($user);
058cbd6a 206 $editor->update(['notificationMailToken' => $token]);
bc1a4c3a
TD
207
208 // reload user
209 $user = new User($user->userID);
320f4a6d
MW
210 }
211
bc1a4c3a
TD
212 $email = new Email();
213 $email->setSubject($user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.daily.subject', ['count' => count($notifications)]));
214 $email->addRecipient(new UserMailbox($user));
215
216 $html = new RecipientAwareTextMimePart('text/html', 'email_dailyNotification', 'wcf', ['notifications' => $notifications]);
217 $plainText = new RecipientAwareTextMimePart('text/plain', 'email_dailyNotification', 'wcf', ['notifications' => $notifications]);
218 $email->setBody(new MimePartFacade([$html, $plainText]));
320f4a6d 219
bc1a4c3a 220 $email->send();
320f4a6d
MW
221 }
222 }
223}