3 * @copyright Copyright (C) 2010-2023, 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\Core\Session\Capability\IHandleUserSessions;
31 use Friendica\Database\Database;
32 use Friendica\Model\Contact;
33 use Friendica\Model\Item;
34 use Friendica\Model\Post;
35 use Friendica\Module\BaseNotifications;
36 use Friendica\Navigation\Notifications\Collection\FormattedNotifies;
37 use Friendica\Navigation\Notifications\Repository;
38 use Friendica\Navigation\Notifications\ValueObject;
39 use Friendica\Network\HTTPException\InternalServerErrorException;
40 use Friendica\Protocol\Activity;
41 use Friendica\Util\DateTimeFormat;
42 use Friendica\Util\Proxy;
43 use Friendica\Util\Temporal;
44 use Friendica\Util\XML;
45 use Psr\Log\LoggerInterface;
48 * Factory for creating notification objects based on items
49 * Currently, there are the following types of item based notifications:
55 * @deprecated since 2022.05 Use \Friendica\Navigation\Notifications\Factory\FormattedNotification instead
57 class FormattedNotify extends BaseFactory
61 /** @var Repository\Notify */
67 /** @var IHandleUserSessions */
70 public function __construct(LoggerInterface $logger, Database $dba, Repository\Notify $notification, BaseURL $baseUrl, L10n $l10n, IHandleUserSessions $userSession)
72 parent::__construct($logger);
75 $this->notify = $notification;
76 $this->baseUrl = $baseUrl;
78 $this->userSession = $userSession;
82 * @param array $formattedItem The return of $this->formatItem
84 * @return ValueObject\FormattedNotify
86 private function createFromFormattedItem(array $formattedItem): ValueObject\FormattedNotify
88 // Transform the different types of notification in a usable array
89 switch ($formattedItem['verb'] ?? '') {
91 return new ValueObject\FormattedNotify(
93 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
94 $formattedItem['author-avatar'],
95 $formattedItem['author-link'],
96 $this->l10n->t("%s liked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']),
97 $formattedItem['when'],
98 $formattedItem['ago'],
99 $formattedItem['seen']
102 case Activity::DISLIKE:
103 return new ValueObject\FormattedNotify(
105 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
106 $formattedItem['author-avatar'],
107 $formattedItem['author-link'],
108 $this->l10n->t("%s disliked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']),
109 $formattedItem['when'],
110 $formattedItem['ago'],
111 $formattedItem['seen']
114 case Activity::ATTEND:
115 return new ValueObject\FormattedNotify(
117 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
118 $formattedItem['author-avatar'],
119 $formattedItem['author-link'],
120 $this->l10n->t("%s is attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
121 $formattedItem['when'],
122 $formattedItem['ago'],
123 $formattedItem['seen']
126 case Activity::ATTENDNO:
127 return new ValueObject\FormattedNotify(
129 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
130 $formattedItem['author-avatar'],
131 $formattedItem['author-link'],
132 $this->l10n->t("%s is not attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
133 $formattedItem['when'],
134 $formattedItem['ago'],
135 $formattedItem['seen']
138 case Activity::ATTENDMAYBE:
139 return new ValueObject\FormattedNotify(
141 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
142 $formattedItem['author-avatar'],
143 $formattedItem['author-link'],
144 $this->l10n->t("%s may attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']),
145 $formattedItem['when'],
146 $formattedItem['ago'],
147 $formattedItem['seen']
150 case Activity::FRIEND:
151 if (!isset($formattedItem['object'])) {
152 return new ValueObject\FormattedNotify(
154 $formattedItem['link'],
155 $formattedItem['image'],
156 $formattedItem['url'],
157 $formattedItem['text'],
158 $formattedItem['when'],
159 $formattedItem['ago'],
160 $formattedItem['seen']
164 $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
165 $obj = XML::parseString($xmlHead . $formattedItem['object']);
167 $formattedItem['fname'] = $obj->title;
169 return new ValueObject\FormattedNotify(
171 $this->baseUrl . '/display/' . $formattedItem['parent-guid'],
172 $formattedItem['author-avatar'],
173 $formattedItem['author-link'],
174 $this->l10n->t("%s is now friends with %s", $formattedItem['author-name'], $formattedItem['fname']),
175 $formattedItem['when'],
176 $formattedItem['ago'],
177 $formattedItem['seen']
181 return new ValueObject\FormattedNotify(
182 $formattedItem['label'] ?? '',
183 $formattedItem['link'] ?? '',
184 $formattedItem['image'] ?? '',
185 $formattedItem['url'] ?? '',
186 $formattedItem['text'] ?? '',
187 $formattedItem['when'] ?? '',
188 $formattedItem['ago'] ?? '',
189 $formattedItem['seen'] ?? false
195 * Get system notifications
197 * @param bool $seen False => only include notifications into the query
198 * which aren't marked as "seen"
199 * @param int $start Start the query at this point
200 * @param int $limit Maximum number of query results
202 * @return FormattedNotifies
204 public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifies
208 $conditions['seen'] = false;
212 $params['order'] = ['date' => 'DESC'];
213 $params['limit'] = [$start, $limit];
215 $formattedNotifications = new FormattedNotifies();
217 $Notifies = $this->notify->selectForUser($this->userSession->getLocalUserId(), $conditions, $params);
219 foreach ($Notifies as $Notify) {
220 $formattedNotifications[] = new ValueObject\FormattedNotify(
222 $this->baseUrl . '/notify/' . $Notify->id,
223 Contact::getAvatarUrlForUrl($Notify->url, $Notify->uid, Proxy::SIZE_MICRO),
225 BBCode::toPlaintext($Notify->msg ?? '', false),
226 DateTimeFormat::local($Notify->date->format(DateTimeFormat::MYSQL), 'r'),
227 Temporal::getRelativeDate($Notify->date->format(DateTimeFormat::MYSQL)),
231 } catch (Exception $e) {
232 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
235 return $formattedNotifications;
239 * Get network notifications
241 * @param bool $seen False => only include notifications into the query
242 * which aren't marked as "seen"
243 * @param int $start Start the query at this point
244 * @param int $limit Maximum number of query results
246 * @return FormattedNotifies
248 public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifies
250 $condition = ['wall' => false, 'uid' => $this->userSession->getLocalUserId()];
253 $condition['unseen'] = true;
256 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
257 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
258 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
260 $formattedNotifications = new FormattedNotifies();
263 $userPosts = Post::selectForUser($this->userSession->getLocalUserId(), $fields, $condition, $params);
264 while ($userPost = $this->dba->fetch($userPosts)) {
265 $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost));
267 } catch (Exception $e) {
268 $this->logger->warning('Select failed.', ['condition' => $condition, 'exception' => $e]);
271 return $formattedNotifications;
275 * Get personal notifications
277 * @param bool $seen False => only include notifications into the query
278 * which aren't marked as "seen"
279 * @param int $start Start the query at this point
280 * @param int $limit Maximum number of query results
282 * @return FormattedNotifies
284 public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifies
286 $condition = ['wall' => false, 'uid' => $this->userSession->getLocalUserId(), 'author-id' => $this->userSession->getPublicContactId()];
289 $condition['unseen'] = true;
292 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
293 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
294 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
296 $formattedNotifications = new FormattedNotifies();
299 $userPosts = Post::selectForUser($this->userSession->getLocalUserId(), $fields, $condition, $params);
300 while ($userPost = $this->dba->fetch($userPosts)) {
301 $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost));
303 } catch (Exception $e) {
304 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
307 return $formattedNotifications;
311 * Get home notifications
313 * @param bool $seen False => only include notifications into the query
314 * which aren't marked as "seen"
315 * @param int $start Start the query at this point
316 * @param int $limit Maximum number of query results
318 * @return FormattedNotifies
320 public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifies
322 $condition = ['wall' => true, 'uid' => $this->userSession->getLocalUserId()];
325 $condition['unseen'] = true;
328 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
329 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
330 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
332 $formattedNotifications = new FormattedNotifies();
335 $userPosts = Post::selectForUser($this->userSession->getLocalUserId(), $fields, $condition, $params);
336 while ($userPost = $this->dba->fetch($userPosts)) {
337 $formattedItem = $this->formatItem($userPost);
339 // Overwrite specific fields, not default item format
340 $formattedItem['label'] = 'comment';
341 $formattedItem['text'] = $this->l10n->t("%s commented on %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']);
343 $formattedNotifications[] = $this->createFromFormattedItem($formattedItem);
345 } catch (Exception $e) {
346 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
349 return $formattedNotifications;
353 * Format the item query in a usable array
355 * @param array $item The item from the db query
357 * @return array The item, extended with the notification-specific information
359 * @throws InternalServerErrorException
362 private function formatItem(array $item): array
364 $item['seen'] = !($item['unseen'] > 0);
366 // For feed items we use the user's contact, since the avatar is mostly self chosen.
367 if (!empty($item['network']) && $item['network'] == Protocol::FEED) {
368 $item['author-avatar'] = $item['contact-avatar'];
371 $item['label'] = (($item['gravity'] == Item::GRAVITY_PARENT) ? 'post' : 'comment');
372 $item['link'] = $this->baseUrl . '/display/' . $item['parent-guid'];
373 $item['image'] = $item['author-avatar'];
374 $item['url'] = $item['author-link'];
375 $item['when'] = DateTimeFormat::local($item['created'], 'r');
376 $item['ago'] = Temporal::getRelativeDate($item['created']);
377 $item['text'] = (($item['gravity'] == Item::GRAVITY_PARENT)
378 ? $this->l10n->t("%s created a new post", $item['author-name'])
379 : $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']));