"item" is replaced by "post-view" / postupdate check added
[friendica.git/.git] / src / Factory / Notification / Notification.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 namespace Friendica\Factory\Notification;
23
24 use Exception;
25 use Friendica\App;
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\Post;
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;
46
47 /**
48  * Factory for creating notification objects based on items
49  * Currently, there are the following types of item based notifications:
50  * - network
51  * - system
52  * - home
53  * - personal
54  */
55 class Notification extends BaseFactory
56 {
57         /** @var Database */
58         private $dba;
59         /** @var Repository\Notify */
60         private $notification;
61         /** @var BaseURL */
62         private $baseUrl;
63         /** @var L10n */
64         private $l10n;
65         /** @var string */
66         private $nurl;
67
68         public function __construct(LoggerInterface $logger, Database $dba, Repository\Notify $notification, BaseURL $baseUrl, L10n $l10n, App $app, IPConfig $pConfig, ISession $session)
69         {
70                 parent::__construct($logger);
71
72                 $this->dba          = $dba;
73                 $this->notification = $notification;
74                 $this->baseUrl      = $baseUrl;
75                 $this->l10n         = $l10n;
76                 $this->nurl         = $app->contact['nurl'] ?? '';
77         }
78
79         /**
80          * Format the item query in an usable array
81          *
82          * @param array $item The item from the db query
83          *
84          * @return array The item, extended with the notification-specific information
85          *
86          * @throws InternalServerErrorException
87          * @throws Exception
88          */
89         private function formatItem(array $item)
90         {
91                 $item['seen'] = ($item['unseen'] > 0 ? false : true);
92
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'];
96                 }
97
98                 $item['label'] = (($item['gravity'] == GRAVITY_PARENT) ? 'post' : 'comment');
99                 $item['link']  = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
100                 $item['image'] = $item['author-avatar'];
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']);
107
108                 return $item;
109         }
110
111         /**
112          * @param array $item
113          *
114          * @return \Friendica\Object\Notification\Notification
115          *
116          * @throws InternalServerErrorException
117          */
118         private function createFromItem(array $item)
119         {
120                 $item = $this->formatItem($item);
121
122                 // Transform the different types of notification in an usable array
123                 switch ($item['verb'] ?? '') {
124                         case Activity::LIKE:
125                                 return new \Friendica\Object\Notification\Notification([
126                                         'label' => 'like',
127                                         'link'  => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
128                                         'image' => $item['author-avatar'],
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']]);
134
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' => $item['author-avatar'],
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']]);
145
146                         case Activity::ATTEND:
147                                 return new \Friendica\Object\Notification\Notification([
148                                         'label' => 'attend',
149                                         'link'  => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
150                                         'image' => $item['author-avatar'],
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']]);
156
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' => $item['author-avatar'],
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']]);
167
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' => $item['author-avatar'],
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']]);
178
179                         case Activity::FRIEND:
180                                 if (!isset($item['object'])) {
181                                         return new \Friendica\Object\Notification\Notification([
182                                                 'label' => 'friend',
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']]);
190                                 }
191
192                                 $xmlHead       = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
193                                 $obj           = XML::parseString($xmlHead . $item['object']);
194                                 $item['fname'] = $obj->title;
195
196                                 return new \Friendica\Object\Notification\Notification([
197                                         'label' => 'friend',
198                                         'link'  => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
199                                         'image' => $item['author-avatar'],
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']]);
205
206                         default:
207                                 return new \Friendica\Object\Notification\Notification($item);
208                                 break;
209                 }
210         }
211
212         /**
213          * Get system notifications
214          *
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
219          *
220          * @return \Friendica\Module\Notifications\Notification[]
221          */
222         public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
223         {
224                 $conditions = ['uid' => local_user()];
225
226                 if (!$seen) {
227                         $conditions['seen'] = false;
228                 }
229
230                 $params          = [];
231                 $params['order'] = ['date' => 'DESC'];
232                 $params['limit'] = [$start, $limit];
233
234                 $formattedNotifications = [];
235                 try {
236                         $notifications = $this->notification->select($conditions, $params);
237
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]);
248                         }
249                 } catch (Exception $e) {
250                         $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
251                 }
252
253                 return $formattedNotifications;
254         }
255
256         /**
257          * Get network notifications
258          *
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
263          *
264          * @return \Friendica\Object\Notification\Notification[]
265          */
266         public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
267         {
268                 $conditions = ['wall' => false, 'uid' => local_user()];
269
270                 if (!$seen) {
271                         $conditions['unseen'] = true;
272                 }
273
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]];
277
278                 $formattedNotifications = [];
279
280                 try {
281                         $items = Post::selectForUser(local_user(), $fields, $conditions, $params);
282
283                         while ($item = $this->dba->fetch($items)) {
284                                 $formattedNotifications[] = $this->createFromItem($item);
285                         }
286                 } catch (Exception $e) {
287                         $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
288                 }
289
290                 return $formattedNotifications;
291         }
292
293         /**
294          * Get personal notifications
295          *
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
300          *
301          * @return \Friendica\Object\Notification\Notification[]
302          */
303         public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
304         {
305                 $condition = ["NOT `wall` AND `uid` = ? AND `author-id` = ?", local_user(), public_contact()];
306
307                 if (!$seen) {
308                         $condition[0] .= " AND `unseen`";
309                 }
310
311                 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
312                         'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
313                 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
314
315                 $formattedNotifications = [];
316
317                 try {
318                         $items = Post::selectForUser(local_user(), $fields, $condition, $params);
319
320                         while ($item = $this->dba->fetch($items)) {
321                                 $formattedNotifications[] = $this->createFromItem($item);
322                         }
323                 } catch (Exception $e) {
324                         $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
325                 }
326
327                 return $formattedNotifications;
328         }
329
330         /**
331          * Get home notifications
332          *
333          * @param bool $seen          False => only include notifications into the query
334          *                            which aren't marked as "seen"
335          * @param int  $start         Start the query at this point
336          * @param int  $limit         Maximum number of query results
337          *
338          * @return \Friendica\Object\Notification\Notification[]
339          */
340         public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
341         {
342                 $condition = ['wall' => true, 'uid' => local_user()];
343
344                 if (!$seen) {
345                         $condition['unseen'] = true;
346                 }
347
348                 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
349                         'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
350                 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
351
352                 $formattedNotifications = [];
353
354                 try {
355                         $items = Post::selectForUser(local_user(), $fields, $condition, $params);
356
357                         while ($item = $this->dba->fetch($items)) {
358                                 $item = $this->formatItem($item);
359
360                                 // Overwrite specific fields, not default item format
361                                 $item['label'] = 'comment';
362                                 $item['text']  = $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']);
363
364                                 $formattedNotifications[] = $this->createFromItem($item);
365                         }
366                 } catch (Exception $e) {
367                         $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
368                 }
369
370                 return $formattedNotifications;
371         }
372
373         /**
374          * @param int   $uid    The user id of the API call
375          * @param array $params Additional parameters
376          *
377          * @return ApiNotifications
378          *
379          * @throws Exception
380          */
381         public function getApiList(int $uid, array $params = ['order' => ['seen' => 'ASC', 'date' => 'DESC'], 'limit' => 50])
382         {
383                 $notifies = $this->notification->select(['uid' => $uid], $params);
384
385                 /** @var ApiNotification[] $notifications */
386                 $notifications = [];
387
388                 foreach ($notifies as $notify) {
389                         $notifications[] = new ApiNotification($notify);
390                 }
391
392                 return new ApiNotifications($notifications);
393         }
394 }