3 * @copyright Copyright (C) 2010-2021, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Navigation\Notifications\Factory;
25 use Friendica\App\BaseURL;
26 use Friendica\BaseFactory;
27 use Friendica\Content\Text\BBCode;
28 use Friendica\Core\L10n;
29 use Friendica\Core\Protocol;
30 use Friendica\Database\Database;
31 use Friendica\Model\Contact;
32 use Friendica\Model\Post;
33 use Friendica\Module\BaseNotifications;
34 use Friendica\Navigation\Notifications\Collection\FormattedNotifications;
35 use Friendica\Navigation\Notifications\Depository;
36 use Friendica\Navigation\Notifications\ValueObject;
37 use Friendica\Network\HTTPException\InternalServerErrorException;
38 use Friendica\Protocol\Activity;
39 use Friendica\Util\DateTimeFormat;
40 use Friendica\Util\Proxy;
41 use Friendica\Util\Temporal;
42 use Friendica\Util\XML;
43 use Psr\Log\LoggerInterface;
46 * Factory for creating notification objects based on items
47 * Currently, there are the following types of item based notifications:
53 class FormattedNotification extends BaseFactory
57 /** @var Depository\Notify */
64 public function __construct(LoggerInterface $logger, Database $dba, Depository\Notify $notify, BaseURL $baseUrl, L10n $l10n)
66 parent::__construct($logger);
69 $this->notify = $notify;
70 $this->baseUrl = $baseUrl;
75 * @param array $formattedItem The return of $this->formatItem
77 * @return ValueObject\FormattedNotification
79 private function createFromFormattedItem(array $formattedItem): ValueObject\FormattedNotification
81 // Transform the different types of notification in a usable array
82 switch ($formattedItem['verb'] ?? '') {
84 return new ValueObject\FormattedNotification(
86 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
87 $formattedItem['author-avatar'],
88 $formattedItem['author-link'],
89 $this->l10n->t("%s liked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']),
90 $formattedItem['when'],
91 $formattedItem['ago'],
92 $formattedItem['seen']
95 case Activity::DISLIKE:
96 return new ValueObject\FormattedNotification(
98 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
99 $formattedItem['author-avatar'],
100 $formattedItem['author-link'],
101 $this->l10n->t("%s disliked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']),
102 $formattedItem['when'],
103 $formattedItem['ago'],
104 $formattedItem['seen']
107 case Activity::ATTEND:
108 return new ValueObject\FormattedNotification(
110 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
111 $formattedItem['author-avatar'],
112 $formattedItem['author-link'],
113 $this->l10n->t("%s is attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
114 $formattedItem['when'],
115 $formattedItem['ago'],
116 $formattedItem['seen']
119 case Activity::ATTENDNO:
120 return new ValueObject\FormattedNotification(
122 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
123 $formattedItem['author-avatar'],
124 $formattedItem['author-link'],
125 $this->l10n->t("%s is not attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
126 $formattedItem['when'],
127 $formattedItem['ago'],
128 $formattedItem['seen']
131 case Activity::ATTENDMAYBE:
132 return new ValueObject\FormattedNotification(
134 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
135 $formattedItem['author-avatar'],
136 $formattedItem['author-link'],
137 $this->l10n->t("%s may attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
138 $formattedItem['when'],
139 $formattedItem['ago'],
140 $formattedItem['seen']
143 case Activity::FRIEND:
144 if (!isset($formattedItem['object'])) {
145 return new ValueObject\FormattedNotification(
147 $formattedItem['link'],
148 $formattedItem['image'],
149 $formattedItem['url'],
150 $formattedItem['text'],
151 $formattedItem['when'],
152 $formattedItem['ago'],
153 $formattedItem['seen']
157 $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
158 $obj = XML::parseString($xmlHead . $formattedItem['object']);
160 $formattedItem['fname'] = $obj->title;
162 return new ValueObject\FormattedNotification(
164 $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'],
165 $formattedItem['author-avatar'],
166 $formattedItem['author-link'],
167 $this->l10n->t("%s is now friends with %s", $formattedItem['author-name'], $formattedItem['fname']),
168 $formattedItem['when'],
169 $formattedItem['ago'],
170 $formattedItem['seen']
174 return new ValueObject\FormattedNotification(
175 $formattedItem['label'] ?? '',
176 $formattedItem['link'] ?? '',
177 $formattedItem['image'] ?? '',
178 $formattedItem['url'] ?? '',
179 $formattedItem['text'] ?? '',
180 $formattedItem['when'] ?? '',
181 $formattedItem['ago'] ?? '',
182 $formattedItem['seen'] ?? false
188 * Get system notifications
190 * @param bool $seen False => only include notifications into the query
191 * which aren't marked as "seen"
192 * @param int $start Start the query at this point
193 * @param int $limit Maximum number of query results
195 * @return FormattedNotifications
197 public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications
201 $conditions['seen'] = false;
205 $params['order'] = ['date' => 'DESC'];
206 $params['limit'] = [$start, $limit];
208 $formattedNotifications = new FormattedNotifications();
210 $Notifies = $this->notify->selectForUser(local_user(), $conditions, $params);
212 foreach ($Notifies as $Notify) {
213 $formattedNotifications[] = new ValueObject\FormattedNotification(
215 $this->baseUrl->get(true) . '/notification/' . $Notify->id,
216 Contact::getAvatarUrlForUrl($Notify->url, $Notify->uid, Proxy::SIZE_MICRO),
218 strip_tags(BBCode::toPlaintext($Notify->msg)),
219 DateTimeFormat::local($Notify->date->format(DateTimeFormat::MYSQL), 'r'),
220 Temporal::getRelativeDate($Notify->date->format(DateTimeFormat::MYSQL)),
224 } catch (Exception $e) {
225 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
228 return $formattedNotifications;
232 * Get network notifications
234 * @param bool $seen False => only include notifications into the query
235 * which aren't marked as "seen"
236 * @param int $start Start the query at this point
237 * @param int $limit Maximum number of query results
239 * @return FormattedNotifications
241 public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications
243 $condition = ['wall' => false, 'uid' => local_user()];
246 $condition['unseen'] = true;
249 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
250 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
251 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
253 $formattedNotifications = new FormattedNotifications();
256 $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params);
257 while ($userPost = $this->dba->fetch($userPosts)) {
258 $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost));
260 } catch (Exception $e) {
261 $this->logger->warning('Select failed.', ['condition' => $condition, 'exception' => $e]);
264 return $formattedNotifications;
268 * Get personal notifications
270 * @param bool $seen False => only include notifications into the query
271 * which aren't marked as "seen"
272 * @param int $start Start the query at this point
273 * @param int $limit Maximum number of query results
275 * @return FormattedNotifications
277 public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications
279 $condition = ['wall' => false, 'uid' => local_user(), 'author-id' => public_contact()];
282 $condition['unseen'] = true;
285 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
286 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
287 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
289 $formattedNotifications = new FormattedNotifications();
292 $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params);
293 while ($userPost = $this->dba->fetch($userPosts)) {
294 $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost));
296 } catch (Exception $e) {
297 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
300 return $formattedNotifications;
304 * Get home notifications
306 * @param bool $seen False => only include notifications into the query
307 * which aren't marked as "seen"
308 * @param int $start Start the query at this point
309 * @param int $limit Maximum number of query results
311 * @return FormattedNotifications
313 public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications
315 $condition = ['wall' => true, 'uid' => local_user()];
318 $condition['unseen'] = true;
321 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
322 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
323 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
325 $formattedNotifications = new FormattedNotifications();
328 $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params);
329 while ($userPost = $this->dba->fetch($userPosts)) {
330 $formattedItem = $this->formatItem($userPost);
332 // Overwrite specific fields, not default item format
333 $formattedItem['label'] = 'comment';
334 $formattedItem['text'] = $this->l10n->t("%s commented on %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']);
336 $formattedNotifications[] = $this->createFromFormattedItem($formattedItem);
338 } catch (Exception $e) {
339 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
342 return $formattedNotifications;
346 * Format the item query in a usable array
348 * @param array $item The item from the db query
350 * @return array The item, extended with the notification-specific information
352 * @throws InternalServerErrorException
355 private function formatItem(array $item): array
357 $item['seen'] = !($item['unseen'] > 0);
359 // For feed items we use the user's contact, since the avatar is mostly self choosen.
360 if (!empty($item['network']) && $item['network'] == Protocol::FEED) {
361 $item['author-avatar'] = $item['contact-avatar'];
364 $item['label'] = (($item['gravity'] == GRAVITY_PARENT) ? 'post' : 'comment');
365 $item['link'] = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
366 $item['image'] = $item['author-avatar'];
367 $item['url'] = $item['author-link'];
368 $item['when'] = DateTimeFormat::local($item['created'], 'r');
369 $item['ago'] = Temporal::getRelativeDate($item['created']);
370 $item['text'] = (($item['gravity'] == GRAVITY_PARENT)
371 ? $this->l10n->t("%s created a new post", $item['author-name'])
372 : $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']));