3 * @copyright Copyright (C) 2020, Friendica
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\Factory\Notification;
26 use Friendica\App\BaseURL;
27 use Friendica\BaseFactory;
28 use Friendica\Collection\Api\Notifications as ApiNotifications;
29 use Friendica\Content\Text\BBCode;
30 use Friendica\Core\L10n;
31 use Friendica\Core\PConfig\IPConfig;
32 use Friendica\Core\Protocol;
33 use Friendica\Core\Session\ISession;
34 use Friendica\Database\Database;
35 use Friendica\Model\Item;
36 use Friendica\Module\BaseNotifications;
37 use Friendica\Network\HTTPException\InternalServerErrorException;
38 use Friendica\Object\Api\Friendica\Notification as ApiNotification;
39 use Friendica\Protocol\Activity;
40 use Friendica\Repository;
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 class Notification extends BaseFactory
59 /** @var Repository\Notify */
60 private $notification;
68 public function __construct(LoggerInterface $logger, Database $dba, Repository\Notify $notification, BaseURL $baseUrl, L10n $l10n, App $app, IPConfig $pConfig, ISession $session)
70 parent::__construct($logger);
73 $this->notification = $notification;
74 $this->baseUrl = $baseUrl;
76 $this->nurl = $app->contact['nurl'] ?? '';
80 * Format the item query in an usable array
82 * @param array $item The item from the db query
84 * @return array The item, extended with the notification-specific information
86 * @throws InternalServerErrorException
89 private function formatItem(array $item)
91 $item['seen'] = ($item['unseen'] > 0 ? false : true);
93 // For feed items we use the user's contact, since the avatar is mostly self choosen.
94 if (!empty($item['network']) && $item['network'] == Protocol::FEED) {
95 $item['author-avatar'] = $item['contact-avatar'];
98 $item['label'] = (($item['gravity'] == GRAVITY_PARENT) ? 'post' : 'comment');
99 $item['link'] = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
100 $item['image'] = Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO);
101 $item['url'] = $item['author-link'];
102 $item['text'] = (($item['gravity'] == GRAVITY_PARENT)
103 ? $this->l10n->t("%s created a new post", $item['author-name'])
104 : $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']));
105 $item['when'] = DateTimeFormat::local($item['created'], 'r');
106 $item['ago'] = Temporal::getRelativeDate($item['created']);
114 * @return \Friendica\Object\Notification\Notification
116 * @throws InternalServerErrorException
118 private function createFromItem(array $item)
120 $item = $this->formatItem($item);
122 // Transform the different types of notification in an usable array
123 switch ($item['verb'] ?? '') {
125 return new \Friendica\Object\Notification\Notification([
127 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
128 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
129 'url' => $item['author-link'],
130 'text' => $this->l10n->t("%s liked %s's post", $item['author-name'], $item['parent-author-name']),
131 'when' => $item['when'],
132 'ago' => $item['ago'],
133 'seen' => $item['seen']]);
135 case Activity::DISLIKE:
136 return new \Friendica\Object\Notification\Notification([
137 'label' => 'dislike',
138 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
139 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
140 'url' => $item['author-link'],
141 'text' => $this->l10n->t("%s disliked %s's post", $item['author-name'], $item['parent-author-name']),
142 'when' => $item['when'],
143 'ago' => $item['ago'],
144 'seen' => $item['seen']]);
146 case Activity::ATTEND:
147 return new \Friendica\Object\Notification\Notification([
149 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
150 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
151 'url' => $item['author-link'],
152 'text' => $this->l10n->t("%s is attending %s's event", $item['author-name'], $item['parent-author-name']),
153 'when' => $item['when'],
154 'ago' => $item['ago'],
155 'seen' => $item['seen']]);
157 case Activity::ATTENDNO:
158 return new \Friendica\Object\Notification\Notification([
159 'label' => 'attendno',
160 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
161 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
162 'url' => $item['author-link'],
163 'text' => $this->l10n->t("%s is not attending %s's event", $item['author-name'], $item['parent-author-name']),
164 'when' => $item['when'],
165 'ago' => $item['ago'],
166 'seen' => $item['seen']]);
168 case Activity::ATTENDMAYBE:
169 return new \Friendica\Object\Notification\Notification([
170 'label' => 'attendmaybe',
171 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
172 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
173 'url' => $item['author-link'],
174 'text' => $this->l10n->t("%s may attending %s's event", $item['author-name'], $item['parent-author-name']),
175 'when' => $item['when'],
176 'ago' => $item['ago'],
177 'seen' => $item['seen']]);
179 case Activity::FRIEND:
180 if (!isset($item['object'])) {
181 return new \Friendica\Object\Notification\Notification([
183 'link' => $item['link'],
184 'image' => $item['image'],
185 'url' => $item['url'],
186 'text' => $item['text'],
187 'when' => $item['when'],
188 'ago' => $item['ago'],
189 'seen' => $item['seen']]);
192 $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
193 $obj = XML::parseString($xmlHead . $item['object']);
194 $item['fname'] = $obj->title;
196 return new \Friendica\Object\Notification\Notification([
198 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
199 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
200 'url' => $item['author-link'],
201 'text' => $this->l10n->t("%s is now friends with %s", $item['author-name'], $item['fname']),
202 'when' => $item['when'],
203 'ago' => $item['ago'],
204 'seen' => $item['seen']]);
207 return new \Friendica\Object\Notification\Notification($item);
213 * Get system notifications
215 * @param bool $seen False => only include notifications into the query
216 * which aren't marked as "seen"
217 * @param int $start Start the query at this point
218 * @param int $limit Maximum number of query results
220 * @return \Friendica\Module\Notifications\Notification[]
222 public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
224 $conditions = ['uid' => local_user()];
227 $conditions['seen'] = false;
231 $params['order'] = ['date' => 'DESC'];
232 $params['limit'] = [$start, $limit];
234 $formattedNotifications = [];
236 $notifications = $this->notification->select($conditions, $params);
238 foreach ($notifications as $notification) {
239 $formattedNotifications[] = new \Friendica\Object\Notification\Notification([
240 'label' => 'notification',
241 'link' => $this->baseUrl->get(true) . '/notification/' . $notification->id,
242 'image' => Proxy::proxifyUrl($notification->photo, false, Proxy::SIZE_MICRO),
243 'url' => $notification->url,
244 'text' => strip_tags(BBCode::convert($notification->msg)),
245 'when' => DateTimeFormat::local($notification->date, 'r'),
246 'ago' => Temporal::getRelativeDate($notification->date),
247 'seen' => $notification->seen]);
249 } catch (Exception $e) {
250 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
253 return $formattedNotifications;
257 * Get network notifications
259 * @param bool $seen False => only include notifications into the query
260 * which aren't marked as "seen"
261 * @param int $start Start the query at this point
262 * @param int $limit Maximum number of query results
264 * @return \Friendica\Object\Notification\Notification[]
266 public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
268 $conditions = ['wall' => false, 'uid' => local_user()];
271 $conditions['unseen'] = true;
274 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
275 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
276 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
278 $formattedNotifications = [];
281 $items = Item::selectForUser(local_user(), $fields, $conditions, $params);
283 while ($item = $this->dba->fetch($items)) {
284 $formattedNotifications[] = $this->createFromItem($item);
286 } catch (Exception $e) {
287 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
290 return $formattedNotifications;
294 * Get personal notifications
296 * @param bool $seen False => only include notifications into the query
297 * which aren't marked as "seen"
298 * @param int $start Start the query at this point
299 * @param int $limit Maximum number of query results
301 * @return \Friendica\Object\Notification\Notification[]
303 public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
305 $myUrl = str_replace('http://', '', $this->nurl);
306 $diaspUrl = str_replace('/profile/', '/u/', $myUrl);
308 $condition = ["NOT `wall` AND `uid` = ? AND (`item`.`author-id` = ? OR `item`.`tag` REGEXP ? OR `item`.`tag` REGEXP ?)",
309 local_user(), public_contact(), $myUrl . '\\]', $diaspUrl . '\\]'];
312 $condition[0] .= " AND `unseen`";
315 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
316 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
317 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
319 $formattedNotifications = [];
322 $items = Item::selectForUser(local_user(), $fields, $condition, $params);
324 while ($item = $this->dba->fetch($items)) {
325 $formattedNotifications[] = $this->createFromItem($item);
327 } catch (Exception $e) {
328 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
331 return $formattedNotifications;
335 * Get home notifications
337 * @param bool $seen False => only include notifications into the query
338 * which aren't marked as "seen"
339 * @param int $start Start the query at this point
340 * @param int $limit Maximum number of query results
342 * @return \Friendica\Object\Notification\Notification[]
344 public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
346 $condition = ['wall' => true, 'uid' => local_user()];
349 $condition['unseen'] = true;
352 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
353 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
354 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
356 $formattedNotifications = [];
359 $items = Item::selectForUser(local_user(), $fields, $condition, $params);
361 while ($item = $this->dba->fetch($items)) {
362 $item = $this->formatItem($item);
364 // Overwrite specific fields, not default item format
365 $item['label'] = 'comment';
366 $item['text'] = $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']);
368 $formattedNotifications[] = $this->createFromItem($item);
370 } catch (Exception $e) {
371 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
374 return $formattedNotifications;
378 * @param int $uid The user id of the API call
379 * @param array $params Additional parameters
381 * @return ApiNotifications
385 public function getApiList(int $uid, array $params = ['order' => ['seen' => 'ASC', 'date' => 'DESC'], 'limit' => 50])
387 $notifies = $this->notification->select(['uid' => $uid], $params);
389 /** @var ApiNotification[] $notifications */
392 foreach ($notifies as $notify) {
393 $notifications[] = new ApiNotification($notify);
396 return new ApiNotifications($notifications);