Merge pull request #6006 from WoltLab/file-processor-can-adopt
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / cronjob / DailyMailNotificationCronjob.class.php
CommitLineData
320f4a6d 1<?php
a9229942 2
320f4a6d 3namespace wcf\system\cronjob;
a9229942 4
c5fc8e59 5use ParagonIE\ConstantTime\Hex;
320f4a6d
MW
6use wcf\data\cronjob\Cronjob;
7use wcf\data\user\notification\event\UserNotificationEventList;
707ac913
AE
8use wcf\data\user\notification\UserNotification;
9use wcf\data\user\User;
320f4a6d
MW
10use wcf\data\user\UserEditor;
11use wcf\data\user\UserList;
12use wcf\data\user\UserProfile;
9d375181 13use wcf\form\NotificationUnsubscribeForm;
a8c5936e 14use wcf\system\cache\runtime\UserProfileRuntimeCache;
320f4a6d 15use wcf\system\database\util\PreparedStatementConditionBuilder;
a9229942 16use wcf\system\email\Email;
bc1a4c3a
TD
17use wcf\system\email\mime\MimePartFacade;
18use wcf\system\email\mime\RecipientAwareTextMimePart;
bc1a4c3a 19use wcf\system\email\UserMailbox;
9d375181 20use wcf\system\request\LinkHandler;
e4499881 21use wcf\system\user\notification\event\IUserNotificationEvent;
320f4a6d
MW
22use wcf\system\user\notification\UserNotificationHandler;
23use wcf\system\WCF;
320f4a6d
MW
24
25/**
26 * Sends daily mail notifications.
a9229942
TD
27 *
28 * @author Marcel Werk
29 * @copyright 2001-2019 WoltLab GmbH
30 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
320f4a6d 31 */
a9229942
TD
32class DailyMailNotificationCronjob extends AbstractCronjob
33{
34 /**
35 * @inheritDoc
36 */
37 public function execute(Cronjob $cronjob)
38 {
39 parent::execute($cronjob);
40
41 // get user ids
42 $sql = "SELECT DISTINCT userID
43 FROM wcf" . WCF_N . "_user_notification
44 WHERE mailNotified = ?
45 AND time < ?
46 AND confirmTime = ?";
47 $statement = WCF::getDB()->prepareStatement($sql);
48 $statement->execute([
49 0,
50 TIME_NOW - 3600 * 23,
51 0,
52 ]);
53 $userIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
54 if (empty($userIDs)) {
55 return;
56 }
57
58 // get users
59 $userList = new UserList();
60 $userList->setObjectIDs($userIDs);
61 $userList->readObjects();
62 $users = $userList->getObjects();
63
64 // get notifications
65 $conditions = new PreparedStatementConditionBuilder();
66 $conditions->add("notification.userID IN (?)", [$userIDs]);
67 $conditions->add("notification.mailNotified = ?", [0]);
68 $conditions->add("notification.confirmTime = ?", [0]);
69
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
c240c98a 73 ON notification_event.eventID = notification.eventID
a9229942 74 LEFT JOIN wcf" . WCF_N . "_object_type object_type
c240c98a 75 ON object_type.objectTypeID = notification_event.objectTypeID
a9229942
TD
76 " . $conditions . "
77 ORDER BY notification.time";
78 $statement = WCF::getDB()->prepareStatement($sql);
79 $statement->execute($conditions->getParameters());
80
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
86 SET mailNotified = 1
87 " . $conditions;
88 $statement2 = WCF::getDB()->prepareStatement($sql);
89 $statement2->execute($conditions->getParameters());
90
91 // collect data
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']] = [];
97 }
98 $eventsToUser[$row['userID']][] = $row['notificationID'];
99
100 // cache object types
101 if (!isset($objectTypes[$row['objectType']])) {
102 $objectTypes[$row['objectType']] = [
103 'objectType' => $availableObjectTypes[$row['objectType']],
104 'objectIDs' => [],
105 'objects' => [],
106 ];
107 }
108
109 $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
110 $eventIDs[] = $row['eventID'];
111
112 $notificationObjects[$row['notificationID']] = new UserNotification(null, $row);
113 }
114
115 // load authors
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
120 " . $conditions . "
121 ORDER BY time ASC";
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'];
128 }
129
130 if (!isset($authorToNotification[$row['notificationID']])) {
131 $authorToNotification[$row['notificationID']] = [];
132 }
133
134 $authorToNotification[$row['notificationID']][] = $row['authorID'];
135 }
136
137 // load authors
138 $authors = UserProfileRuntimeCache::getInstance()->getObjects($authorIDs);
139 $unknownAuthor = new UserProfile(new User(
140 null,
141 ['userID' => null, 'username' => WCF::getLanguage()->get('wcf.user.guest')]
142 ));
143
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']);
148 }
149
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();
155
156 foreach ($eventsToUser as $userID => $notificationIDs) {
157 if (!isset($users[$userID])) {
158 continue;
159 }
160 $user = $users[$userID];
161
162 // no notifications for disabled or banned users
163 if (!$user->isEmailConfirmed()) {
164 continue;
165 }
166 if ($user->banned) {
167 continue;
168 }
169
170 $notifications = \array_map(static function ($notificationID) use (
171 $notificationObjects,
172 $eventObjects,
173 $user,
174 $objectTypes,
175 $authors,
176 $authorToNotification,
177 $unknownAuthor
178 ) {
179 $notification = $notificationObjects[$notificationID];
180
181 $className = $eventObjects[$notification->eventID]->className;
182
183 /** @var IUserNotificationEvent $class */
184 $class = new $className($eventObjects[$notification->eventID]);
185 $class->setObject(
186 $notification,
187 $objectTypes[$notification->objectType]['objects'][$notification->objectID],
188 ($authors[$notification->authorID] ?? $unknownAuthor),
189 $notification->additionalData
190 );
191 $class->setLanguage($user->getLanguage());
192
193 if (isset($authorToNotification[$notification->notificationID])) {
194 $eventAuthors = [];
195 foreach ($authorToNotification[$notification->notificationID] as $userID) {
196 if (!$userID) {
197 $eventAuthors[0] = $unknownAuthor;
198 } elseif (isset($authors[$userID])) {
199 $eventAuthors[$userID] = $authors[$userID];
200 }
201 }
202 if (!empty($eventAuthors)) {
203 $class->setAuthors($eventAuthors);
204 }
205 }
206
207 $message = $class->getEmailMessage('daily');
208 if (\is_array($message)) {
209 if (!isset($message['variables'])) {
210 $message['variables'] = [];
211 }
212
213 return \array_merge($message['variables'], [
214 'notificationContent' => $message,
215 'event' => $class,
216 'notificationType' => 'daily',
217 'variables' => $message['variables'], // deprecated, but is kept for backwards compatibility
218 ]);
219 } else {
220 return [
221 'notificationContent' => $message,
222 'event' => $class,
223 'notificationType' => 'daily',
224 ];
225 }
226 }, $notificationIDs);
227
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]);
233
234 // reload user
235 $user = new User($user->userID);
236 }
237
238 $email = new Email();
239 $email->setSubject($user->getLanguage()->getDynamicVariable(
240 'wcf.user.notification.mail.daily.subject',
241 ['count' => \count($notifications)]
242 ));
243 $email->addRecipient(new UserMailbox($user));
244 $email->setListID('daily.notification');
245 $email->setListUnsubscribe(
246 LinkHandler::getInstance()->getControllerLink(
247 NotificationUnsubscribeForm::class,
248 [
249 'userID' => $user->userID,
250 'token' => $user->notificationMailToken,
251 ]
252 ),
253 true
254 );
255
bc0e1f3f 256 $maximumNotificationCount = 7;
257 $notificationCount = \count($notifications);
258 if ($notificationCount === $maximumNotificationCount + 1) {
259 $maximumNotificationCount++;
260 }
261 $remainingNotificationCount = $notificationCount - $maximumNotificationCount;
262 $notifications = \array_slice($notifications, 0, $maximumNotificationCount);
263
a9229942
TD
264 $html = new RecipientAwareTextMimePart(
265 'text/html',
266 'email_dailyNotification',
267 'wcf',
bc0e1f3f 268 [
269 'notifications' => $notifications,
270 'remaining' => $remainingNotificationCount,
271 'maximum' => $maximumNotificationCount,
272 'notificationCount' => $notificationCount,
273 ]
a9229942
TD
274 );
275 $plainText = new RecipientAwareTextMimePart(
276 'text/plain',
277 'email_dailyNotification',
278 'wcf',
bc0e1f3f 279 [
280 'notifications' => $notifications,
281 'remaining' => $remainingNotificationCount,
282 'maximum' => $maximumNotificationCount,
283 'notificationCount' => $notificationCount,
284 ]
a9229942
TD
285 );
286 $email->setBody(new MimePartFacade([$html, $plainText]));
287
288 $email->send();
289 }
290 }
320f4a6d 291}