3 namespace Friendica\Factory\Notification;
7 use Friendica\App\BaseURL;
8 use Friendica\BaseFactory;
9 use Friendica\Content\Text\BBCode;
10 use Friendica\Core\L10n;
11 use Friendica\Core\PConfig\IPConfig;
12 use Friendica\Core\Protocol;
13 use Friendica\Core\Session\ISession;
14 use Friendica\Database\Database;
15 use Friendica\Model\Item;
16 use Friendica\Module\BaseNotifications;
17 use Friendica\Network\HTTPException\InternalServerErrorException;
18 use Friendica\Protocol\Activity;
19 use Friendica\Repository;
20 use Friendica\Util\DateTimeFormat;
21 use Friendica\Util\Proxy;
22 use Friendica\Util\Temporal;
23 use Friendica\Util\XML;
24 use Psr\Log\LoggerInterface;
27 * Factory for creating notification objects based on items
28 * Currently, there are the following types of item based notifications:
34 class Notification extends BaseFactory
38 /** @var Repository\Notify */
39 private $notification;
47 public function __construct(LoggerInterface $logger, Database $dba, Repository\Notify $notification, BaseURL $baseUrl, L10n $l10n, App $app, IPConfig $pConfig, ISession $session)
49 parent::__construct($logger);
52 $this->notification = $notification;
53 $this->baseUrl = $baseUrl;
55 $this->nurl = $app->contact['nurl'] ?? '';
59 * Format the item query in an usable array
61 * @param array $item The item from the db query
63 * @return array The item, extended with the notification-specific information
65 * @throws InternalServerErrorException
68 private function formatItem(array $item)
70 $item['seen'] = ($item['unseen'] > 0 ? false : true);
72 // For feed items we use the user's contact, since the avatar is mostly self choosen.
73 if (!empty($item['network']) && $item['network'] == Protocol::FEED) {
74 $item['author-avatar'] = $item['contact-avatar'];
77 $item['label'] = (($item['id'] == $item['parent']) ? 'post' : 'comment');
78 $item['link'] = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
79 $item['image'] = Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO);
80 $item['url'] = $item['author-link'];
81 $item['text'] = (($item['id'] == $item['parent'])
82 ? $this->l10n->t("%s created a new post", $item['author-name'])
83 : $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']));
84 $item['when'] = DateTimeFormat::local($item['created'], 'r');
85 $item['ago'] = Temporal::getRelativeDate($item['created']);
93 * @return \Friendica\Object\Notification\Notification
95 * @throws InternalServerErrorException
97 private function createFromItem(array $item)
99 $item = $this->formatItem($item);
101 // Transform the different types of notification in an usable array
102 switch ($item['verb'] ?? '') {
104 return new \Friendica\Object\Notification\Notification([
106 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
107 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
108 'url' => $item['author-link'],
109 'text' => $this->l10n->t("%s liked %s's post", $item['author-name'], $item['parent-author-name']),
110 'when' => $item['when'],
111 'ago' => $item['ago'],
112 'seen' => $item['seen']]);
114 case Activity::DISLIKE:
115 return new \Friendica\Object\Notification\Notification([
116 'label' => 'dislike',
117 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
118 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
119 'url' => $item['author-link'],
120 'text' => $this->l10n->t("%s disliked %s's post", $item['author-name'], $item['parent-author-name']),
121 'when' => $item['when'],
122 'ago' => $item['ago'],
123 'seen' => $item['seen']]);
125 case Activity::ATTEND:
126 return new \Friendica\Object\Notification\Notification([
128 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
129 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
130 'url' => $item['author-link'],
131 'text' => $this->l10n->t("%s is attending %s's event", $item['author-name'], $item['parent-author-name']),
132 'when' => $item['when'],
133 'ago' => $item['ago'],
134 'seen' => $item['seen']]);
136 case Activity::ATTENDNO:
137 return new \Friendica\Object\Notification\Notification([
138 'label' => 'attendno',
139 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
140 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
141 'url' => $item['author-link'],
142 'text' => $this->l10n->t("%s is not attending %s's event", $item['author-name'], $item['parent-author-name']),
143 'when' => $item['when'],
144 'ago' => $item['ago'],
145 'seen' => $item['seen']]);
147 case Activity::ATTENDMAYBE:
148 return new \Friendica\Object\Notification\Notification([
149 'label' => 'attendmaybe',
150 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
151 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
152 'url' => $item['author-link'],
153 'text' => $this->l10n->t("%s may attending %s's event", $item['author-name'], $item['parent-author-name']),
154 'when' => $item['when'],
155 'ago' => $item['ago'],
156 'seen' => $item['seen']]);
158 case Activity::FRIEND:
159 if (!isset($item['object'])) {
160 return new \Friendica\Object\Notification\Notification([
162 'link' => $item['link'],
163 'image' => $item['image'],
164 'url' => $item['url'],
165 'text' => $item['text'],
166 'when' => $item['when'],
167 'ago' => $item['ago'],
168 'seen' => $item['seen']]);
171 $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
172 $obj = XML::parseString($xmlHead . $item['object']);
173 $item['fname'] = $obj->title;
175 return new \Friendica\Object\Notification\Notification([
177 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
178 'image' => Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
179 'url' => $item['author-link'],
180 'text' => $this->l10n->t("%s is now friends with %s", $item['author-name'], $item['fname']),
181 'when' => $item['when'],
182 'ago' => $item['ago'],
183 'seen' => $item['seen']]);
186 return new \Friendica\Object\Notification\Notification($item);
192 * Get system notifications
194 * @param bool $seen False => only include notifications into the query
195 * which aren't marked as "seen"
196 * @param int $start Start the query at this point
197 * @param int $limit Maximum number of query results
199 * @return \Friendica\Module\Notifications\Notification[]
201 public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
203 $conditions = ['uid' => local_user()];
206 $conditions['seen'] = false;
210 $params['order'] = ['date' => 'DESC'];
211 $params['limit'] = [$start, $limit];
213 $formattedNotifications = [];
215 $notifications = $this->notification->select($conditions, $params);
217 foreach ($notifications as $notification) {
218 $formattedNotifications[] = new \Friendica\Object\Notification\Notification([
219 'label' => 'notification',
220 'link' => $this->baseUrl->get(true) . '/notification/view/' . $notification->id,
221 'image' => Proxy::proxifyUrl($notification->photo, false, Proxy::SIZE_MICRO),
222 'url' => $notification->url,
223 'text' => strip_tags(BBCode::convert($notification->msg)),
224 'when' => DateTimeFormat::local($notification->date, 'r'),
225 'ago' => Temporal::getRelativeDate($notification->date),
226 'seen' => $notification->seen]);
228 } catch (Exception $e) {
229 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
232 return $formattedNotifications;
236 * Get network notifications
238 * @param bool $seen False => only include notifications into the query
239 * which aren't marked as "seen"
240 * @param int $start Start the query at this point
241 * @param int $limit Maximum number of query results
243 * @return \Friendica\Object\Notification\Notification[]
245 public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
247 $conditions = ['wall' => false, 'uid' => local_user()];
250 $conditions['unseen'] = true;
253 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
254 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
255 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
257 $formattedNotifications = [];
260 $items = Item::selectForUser(local_user(), $fields, $conditions, $params);
262 while ($item = $this->dba->fetch($items)) {
263 $formattedNotifications[] = $this->createFromItem($item);
265 } catch (Exception $e) {
266 $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]);
269 return $formattedNotifications;
273 * Get personal notifications
275 * @param bool $seen False => only include notifications into the query
276 * which aren't marked as "seen"
277 * @param int $start Start the query at this point
278 * @param int $limit Maximum number of query results
280 * @return \Friendica\Object\Notification\Notification[]
282 public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
284 $myUrl = str_replace('http://', '', $this->nurl);
285 $diaspUrl = str_replace('/profile/', '/u/', $myUrl);
287 $condition = ["NOT `wall` AND `uid` = ? AND (`item`.`author-id` = ? OR `item`.`tag` REGEXP ? OR `item`.`tag` REGEXP ?)",
288 local_user(), public_contact(), $myUrl . '\\]', $diaspUrl . '\\]'];
291 $condition[0] .= " AND `unseen`";
294 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
295 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
296 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
298 $formattedNotifications = [];
301 $items = Item::selectForUser(local_user(), $fields, $condition, $params);
303 while ($item = $this->dba->fetch($items)) {
304 $formattedNotifications[] = $this->createFromItem($item);
306 } catch (Exception $e) {
307 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
310 return $formattedNotifications;
314 * Get home notifications
316 * @param bool $seen False => only include notifications into the query
317 * which aren't marked as "seen"
318 * @param int $start Start the query at this point
319 * @param int $limit Maximum number of query results
321 * @return \Friendica\Object\Notification\Notification[]
323 public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT)
325 $condition = ['wall' => true, 'uid' => local_user()];
328 $condition['unseen'] = true;
331 $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
332 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
333 $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
335 $formattedNotifications = [];
338 $items = Item::selectForUser(local_user(), $fields, $condition, $params);
340 while ($item = $this->dba->fetch($items)) {
341 $item = $this->formatItem($item);
343 // Overwrite specific fields, not default item format
344 $item['label'] = 'comment';
345 $item['text'] = $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']);
347 $formattedNotifications[] = $this->createFromItem($item);
349 } catch (Exception $e) {
350 $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]);
353 return $formattedNotifications;