From 49971b1465d6ebb8e8260812ce3f9fbd7a4aef46 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 5 Mar 2022 15:34:09 -0500 Subject: [PATCH] Switch data source from notify to notification in Module\Notifications\Ping - Change unused FormattedNotification classes to FormattedNavNotification classes --- src/Core/System.php | 7 +- src/Module/Notifications/Ping.php | 319 +++++---------- .../Collection/Notifications.php | 7 + .../Factory/FormattedNavNotification.php | 127 ++++++ .../Factory/FormattedNotification.php | 376 ------------------ .../Notifications/Repository/Notification.php | 30 ++ ...ation.php => FormattedNavNotification.php} | 60 ++- 7 files changed, 299 insertions(+), 627 deletions(-) create mode 100644 src/Navigation/Notifications/Factory/FormattedNavNotification.php delete mode 100644 src/Navigation/Notifications/Factory/FormattedNotification.php rename src/Navigation/Notifications/ValueObject/{FormattedNotification.php => FormattedNavNotification.php} (50%) diff --git a/src/Core/System.php b/src/Core/System.php index de0c80b3de..16bc2360ef 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -334,9 +334,10 @@ class System * and adds an application/json HTTP header to the output. * After finishing the process is getting killed. * - * @param mixed $x The input content. - * @param string $content_type Type of the input (Default: 'application/json'). - * @param integer $options JSON options + * @param mixed $x The input content + * @param string $content_type Type of the input (Default: 'application/json') + * @param integer $options JSON options + * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public static function jsonExit($x, $content_type = 'application/json', int $options = 0) { DI::apiResponse()->setType(Response::TYPE_JSON, $content_type); diff --git a/src/Module/Notifications/Ping.php b/src/Module/Notifications/Ping.php index 6faea810e2..50c8232032 100644 --- a/src/Module/Notifications/Ping.php +++ b/src/Module/Notifications/Ping.php @@ -21,53 +21,75 @@ namespace Friendica\Module\Notifications; +use Friendica\App; use Friendica\BaseModule; +use Friendica\Contact\Introduction\Repository\Introduction; use Friendica\Content\ForumManager; -use Friendica\Content\Text\BBCode; use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Hook; -use Friendica\Core\Renderer; +use Friendica\Core\L10n; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\Contact; use Friendica\Model\Group; -use Friendica\Model\Notification; use Friendica\Model\Post; use Friendica\Model\Verb; use Friendica\Module\Register; +use Friendica\Module\Response; use Friendica\Navigation\Notifications\Entity; -use Friendica\Network\HTTPException; +use Friendica\Navigation\Notifications\Factory; +use Friendica\Navigation\Notifications\Repository; +use Friendica\Navigation\Notifications\ValueObject; use Friendica\Protocol\Activity; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Proxy; -use Friendica\Util\Temporal; +use Friendica\Util\Profiler; +use GuzzleHttp\Psr7\Uri; +use Psr\Log\LoggerInterface; class Ping extends BaseModule { + /** @var Repository\Notification */ + private $notificationRepo; + /** @var Introduction */ + private $introductionRepo; + /** @var Factory\FormattedNavNotification */ + private $formattedNavNotification; + + public function __construct(Repository\Notification $notificationRepo, Introduction $introductionRepo, Factory\FormattedNavNotification $formattedNavNotification, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->notificationRepo = $notificationRepo; + $this->introductionRepo = $introductionRepo; + $this->formattedNavNotification = $formattedNavNotification; + } + protected function rawContent(array $request = []) { - $regs = []; - $notifications = []; + $regs = []; + $navNotifications = []; - $intro_count = 0; - $mail_count = 0; - $home_count = 0; - $network_count = 0; - $register_count = 0; + $intro_count = 0; + $mail_count = 0; + $home_count = 0; + $network_count = 0; + $register_count = 0; $sysnotify_count = 0; - $groups_unseen = []; - $forums_unseen = []; + $groups_unseen = []; + $forums_unseen = []; - $all_events = 0; - $all_events_today = 0; - $events = 0; - $events_today = 0; - $birthdays = 0; - $birthdays_today = 0; + $event_count = 0; + $today_event_count = 0; + $birthday_count = 0; + $today_birthday_count = 0; if (local_user()) { - $notifications = $this->getNotificationList(local_user()); + if (DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { + $notifications = $this->notificationRepo->selectForUser(local_user(), [], ['limit' => 50]); + } else { + $notifications = $this->notificationRepo->selectDigestForUser(local_user()); + } $condition = [ "`unseen` AND `uid` = ? AND NOT `origin` AND (`vid` != ? OR `vid` IS NULL)", @@ -76,7 +98,7 @@ class Ping extends BaseModule $items = Post::selectForUser(local_user(), ['wall', 'uid', 'uri-id'], $condition, ['limit' => 1000]); if (DBA::isResult($items)) { $items_unseen = Post::toArray($items, false); - $arr = ['items' => $items_unseen]; + $arr = ['items' => $items_unseen]; Hook::callAll('network_ping', $arr); foreach ($items_unseen as $item) { @@ -110,25 +132,11 @@ class Ping extends BaseModule } } - $intros1 = DBA::toArray(DBA::p( - "SELECT `intro`.`id`, `intro`.`datetime`, - `contact`.`name`, `contact`.`url`, `contact`.`photo` - FROM `intro` INNER JOIN `contact` ON `intro`.`suggest-cid` = `contact`.`id` - WHERE `intro`.`uid` = ? AND NOT `intro`.`blocked` AND NOT `intro`.`ignore` AND `intro`.`suggest-cid` != 0", - local_user() - )); - $intros2 = DBA::toArray(DBA::p( - "SELECT `intro`.`id`, `intro`.`datetime`, - `contact`.`name`, `contact`.`url`, `contact`.`photo` - FROM `intro` INNER JOIN `contact` ON `intro`.`contact-id` = `contact`.`id` - WHERE `intro`.`uid` = ? AND NOT `intro`.`blocked` AND NOT `intro`.`ignore` AND `intro`.`contact-id` != 0 AND (`intro`.`suggest-cid` = 0 OR `intro`.`suggest-cid` IS NULL)", - local_user() - )); + $intros = $this->introductionRepo->selectForUser(local_user()); - $intro_count = count($intros1) + count($intros2); - $intros = $intros1 + $intros2; + $intro_count = $intros->count(); - $myurl = DI::baseUrl() . '/profile/' . DI::app()->getLoggedInUserNickname(); + $myurl = DI::baseUrl() . '/profile/' . DI::app()->getLoggedInUserNickname(); $mail_count = DBA::count('mail', ["`uid` = ? AND NOT `seen` AND `from-url` != ?", local_user(), $myurl]); if (intval(DI::config()->get('config', 'register_policy')) === Register::APPROVE && DI::app()->isSiteAdmin()) { @@ -139,8 +147,8 @@ class Ping extends BaseModule } } - $cachekey = "ping_init:" . local_user(); - $ev = DI::cache()->get($cachekey); + $cachekey = 'ping:events:' . local_user(); + $ev = DI::cache()->get($cachekey); if (is_null($ev)) { $ev = DBA::selectToArray('event', ['type', 'start'], ["`uid` = ? AND `start` < ? AND `finish` > ? AND NOT `ignore`", @@ -158,97 +166,79 @@ class Ping extends BaseModule foreach ($ev as $x) { $bd = false; if ($x['type'] === 'birthday') { - $birthdays ++; + $birthday_count++; $bd = true; } else { - $events ++; + $event_count++; } if (DateTimeFormat::local($x['start'], 'Y-m-d') === $str_now) { - $all_events_today ++; if ($bd) { - $birthdays_today ++; + $today_birthday_count++; } else { - $events_today ++; + $today_event_count++; } } } } } + $sysnotify_count = $notifications->countUnseen(); - foreach ($notifications as $notification) { - if ($notification['seen'] == 0) { - $sysnotify_count ++; - } - } + $navNotifications = array_map(function (Entity\Notification $notification) { + return $this->formattedNavNotification->createFromNotification($notification); + }, $notifications->getArrayCopy()); // merge all notification types in one array - if (DBA::isResult($intros)) { - foreach ($intros as $intro) { - $notifications[] = [ - 'href' => DI::baseUrl() . '/notifications/intros/' . $intro['id'], - 'contact' => [ - 'name' => strip_tags(BBCode::convert($intro['name'])), - 'url' => $intro['url'], - ], - 'message' => DI::l10n()->t('{0}} wants to follow you'), - 'date' => $intro['datetime'], - 'seen' => false, - ]; - } + foreach ($intros as $intro) { + $navNotifications[] = $this->formattedNavNotification->createFromIntro($intro); } if (DBA::isResult($regs)) { if (count($regs) <= 1 || DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { foreach ($regs as $reg) { - $notifications[] = [ - 'href' => DI::baseUrl()->get(true) . '/admin/users/pending', - 'contact' => [ - 'name' => $reg['name'], - 'url' => $reg['url'], + $navNotifications[] = $this->formattedNavNotification->createFromParams( + [ + 'name' => $reg['name'], + 'url' => $reg['url'], ], - 'message' => DI::l10n()->t('{0} requested registration'), - 'date' => $reg['created'], - 'seen' => false, - ]; + DI::l10n()->t('{0} requested registration'), + new \DateTime($reg['created'], new \DateTimeZone('UTC')), + new Uri(DI::baseUrl()->get(true) . '/admin/users/pending') + ); } } else { - $notifications[] = [ - 'href' => DI::baseUrl()->get(true) . '/admin/users/pending', - 'contact' => [ - 'name' => $regs[0]['name'], - 'url' => $regs[0]['url'], + $navNotifications[] = $this->formattedNavNotification->createFromParams( + [ + 'name' => $regs[0]['name'], + 'url' => $regs[0]['url'], ], - 'message' => DI::l10n()->t('{0} and %d others requested registration', count($regs) - 1), - 'date' => $regs[0]['created'], - 'seen' => false, - ]; + DI::l10n()->t('{0} and %d others requested registration', count($regs) - 1), + new \DateTime($regs[0]['created'], new \DateTimeZone('UTC')), + new Uri(DI::baseUrl()->get(true) . '/admin/users/pending') + ); } } // sort notifications by $[]['date'] - $sort_function = function ($a, $b) { - $adate = strtotime($a['date']); - $bdate = strtotime($b['date']); + $sort_function = function (ValueObject\FormattedNavNotification $a, ValueObject\FormattedNavNotification $b) { + $a = $a->toArray(); + $b = $b->toArray(); // Unseen messages are kept at the top - // The value 31536000 means one year. This should be enough :-) - if (!$a['seen']) { - $adate += 31536000; + if ($a['seen'] == $b['seen']) { + if ($a['timestamp'] == $b['timestamp']) { + return 0; + } else { + return $a['timestamp'] < $b['timestamp'] ? 1 : -1; + } + } else { + return $a['seen'] ? 1 : -1; } - if (!$b['seen']) { - $bdate += 31536000; - } - - if ($adate == $bdate) { - return 0; - } - return ($adate < $bdate) ? 1 : -1; }; - usort($notifications, $sort_function); + usort($navNotifications, $sort_function); } - $sysmsgs = []; + $sysmsgs = []; $sysmsgs_info = []; if (!empty($_SESSION['sysmsg'])) { @@ -263,138 +253,35 @@ class Ping extends BaseModule $notification_count = $sysnotify_count + $intro_count + $register_count; - $tpl = Renderer::getMarkupTemplate('notifications/nav/notify.tpl'); - - $data = []; + $data = []; $data['intro'] = $intro_count; $data['mail'] = $mail_count; $data['net'] = ($network_count < 1000) ? $network_count : '999+'; $data['home'] = ($home_count < 1000) ? $home_count : '999+'; $data['register'] = $register_count; - $data['all-events'] = $all_events; - $data['all-events-today'] = $all_events_today; - $data['events'] = $events; - $data['events-today'] = $events_today; - $data['birthdays'] = $birthdays; - $data['birthdays-today'] = $birthdays_today; - $data['groups'] = $groups_unseen; - $data['forums'] = $forums_unseen; - $data['notification'] = ($notification_count < 50) ? $notification_count : '49+'; - $data['notifications'] = array_map(function ($navNotification) use ($tpl) { - $navNotification['contact']['photo'] = Contact::getAvatarUrlForUrl($navNotification['contact']['url'], local_user(), Proxy::SIZE_MICRO); + $data['events'] = $event_count; + $data['events-today'] = $today_event_count; + $data['birthdays'] = $birthday_count; + $data['birthdays-today'] = $today_birthday_count; + $data['groups'] = $groups_unseen; + $data['forums'] = $forums_unseen; + $data['notification'] = ($notification_count < 50) ? $notification_count : '49+'; - $navNotification['timestamp'] = strtotime($navNotification['date']); - $navNotification['localdate'] = DateTimeFormat::local($navNotification['date']); - $navNotification['ago'] = Temporal::getRelativeDate($navNotification['date']); - $navNotification['richtext'] = Entity\Notify::formatMessage($navNotification['contact']['name'], $navNotification['message']); - $navNotification['plaintext'] = strip_tags($navNotification['richtext']); - $navNotification['html'] = Renderer::replaceMacros($tpl, [ - 'notify' => $navNotification, - ]); + $data['notifications'] = $navNotifications; - return $navNotification; - }, $notifications); $data['sysmsgs'] = [ 'notice' => $sysmsgs, - 'info' => $sysmsgs_info + 'info' => $sysmsgs_info ]; - $json_payload = json_encode(["result" => $data]); - if (isset($_GET['callback'])) { // JSONP support header("Content-type: application/javascript"); - echo $_GET['callback'] . '(' . $json_payload . ')'; + echo $_GET['callback'] . '(' . json_encode(['result' => $data]) . ')'; + exit; } else { - header("Content-type: application/json"); - echo $json_payload; + System::jsonExit(['result' => $data]); } - - exit(); - } - - /** - * Retrieves the notifications array for the given user ID - * - * @param int $uid User id - * @return array Associative array of notifications - * @throws HTTPException\InternalServerErrorException - */ - private function getNotificationList(int $uid): array - { - $result = []; - $offset = 0; - $seen = false; - $seensql = 'NOT'; - $order = 'DESC'; - $quit = false; - - do { - $notifies = DBA::toArray(DBA::p( - "SELECT `notify`.*, `post`.`visible`, `post`.`deleted` - FROM `notify` - LEFT JOIN `post` ON `post`.`uri-id` = `notify`.`uri-id` - WHERE `notify`.`uid` = ? AND `notify`.`msg` != '' - AND NOT (`notify`.`type` IN (?, ?)) - AND $seensql `notify`.`seen` ORDER BY `notify`.`date` $order LIMIT ?, 50", - $uid, - Notification\Type::INTRO, - Notification\Type::MAIL, - $offset - )); - - if (!$notifies && !$seen) { - $seen = true; - $seensql = ''; - $order = 'DESC'; - $offset = 0; - } elseif (!$notifies) { - $quit = true; - } else { - $offset += 50; - } - - foreach ($notifies as $notify) { - $notify['visible'] = $notify['visible'] ?? true; - $notify['deleted'] = $notify['deleted'] ?? 0; - - if ($notify['msg_cache']) { - $notify['name'] = $notify['name_cache']; - $notify['message'] = $notify['msg_cache']; - } else { - $notify['name'] = strip_tags(BBCode::convert($notify['name'])); - $notify['message'] = BBCode::toPlaintext($notify['msg']); - - // @todo Replace this with a call of the Notify model class - DBA::update('notify', ['name_cache' => $notify['name'], 'msg_cache' => $notify['message']], ['id' => $notify['id']]); - } - - if ($notify['visible'] - && !$notify['deleted'] - && empty($result['p:' . $notify['parent']]) - ) { - $notification = [ - 'href' => DI::baseUrl() . '/notify/' . $notify['id'], - 'contact' => [ - 'name' => $notify['name'], - 'url' => $notify['url'], - ], - 'message' => $notify['message'], - 'date' => $notify['date'], - 'seen' => $notify['seen'], - ]; - - // Should we condense the notifications or show them all? - if (($notify['verb'] != Activity::POST) || DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { - $result[] = $notification; - } else { - $result['p:' . $notify['parent']] = $notification; - } - } - } - } while ((count($result) < 50) && !$quit); - - return($result); } } diff --git a/src/Navigation/Notifications/Collection/Notifications.php b/src/Navigation/Notifications/Collection/Notifications.php index 3dd3775d04..c1ad8bbbf7 100644 --- a/src/Navigation/Notifications/Collection/Notifications.php +++ b/src/Navigation/Notifications/Collection/Notifications.php @@ -47,4 +47,11 @@ class Notifications extends BaseCollection $Notification->setDismissed(); }); } + + public function countUnseen(): int + { + return array_reduce($this->getArrayCopy(), function (int $carry, Entity\Notification $Notification) { + return $carry + ($Notification->seen ? 0 : 1); + }, 0); + } } diff --git a/src/Navigation/Notifications/Factory/FormattedNavNotification.php b/src/Navigation/Notifications/Factory/FormattedNavNotification.php new file mode 100644 index 0000000000..a9498eaa58 --- /dev/null +++ b/src/Navigation/Notifications/Factory/FormattedNavNotification.php @@ -0,0 +1,127 @@ +. + * + */ + +namespace Friendica\Navigation\Notifications\Factory; + +use Friendica\BaseFactory; +use Friendica\Core\Renderer; +use Friendica\Model\Contact; +use Friendica\Navigation\Notifications\Entity; +use Friendica\Navigation\Notifications\ValueObject; +use Friendica\Util\DateTimeFormat; +use Friendica\Util\Proxy; +use Friendica\Util\Temporal; +use GuzzleHttp\Psr7\Uri; +use Psr\Log\LoggerInterface; + +/** + * Factory for creating notification objects based on items + */ +class FormattedNavNotification extends BaseFactory +{ + private static $contacts = []; + + /** @var Notification */ + private $notification; + /** @var \Friendica\App\BaseURL */ + private $baseUrl; + /** @var \Friendica\Core\L10n */ + private $l10n; + /** @var string */ + private $tpl; + + public function __construct(Notification $notification, \Friendica\App\BaseURL $baseUrl, \Friendica\Core\L10n $l10n, LoggerInterface $logger) + { + parent::__construct($logger); + + $this->notification = $notification; + $this->baseUrl = $baseUrl; + $this->l10n = $l10n; + + $this->tpl = Renderer::getMarkupTemplate('notifications/nav/notify.tpl'); + } + + /** + * @param array $contact A contact array with the following keys: name, url + * @param string $message A notification message with the {0} placeholder for the contact name + * @param \DateTime $date + * @param Uri $href + * @param bool $seen + * @return ValueObject\FormattedNavNotification + * @throws \Friendica\Network\HTTPException\ServiceUnavailableException + */ + public function createFromParams(array $contact, string $message, \DateTime $date, Uri $href, bool $seen = false): ValueObject\FormattedNavNotification + { + $contact['photo'] = Contact::getAvatarUrlForUrl($contact['url'], local_user(), Proxy::SIZE_MICRO); + + $dateMySQL = $date->format(DateTimeFormat::MYSQL); + + $templateNotify = [ + 'contact' => $contact, + 'href' => $href->__toString(), + 'message' => $message, + 'seen' => $seen, + 'localdate' => DateTimeFormat::local($dateMySQL), + 'ago' => Temporal::getRelativeDate($dateMySQL), + 'richtext' => Entity\Notify::formatMessage($contact['name'], $message), + ]; + + return new ValueObject\FormattedNavNotification( + $contact, + $date->getTimestamp(), + strip_tags($templateNotify['richtext']), + Renderer::replaceMacros($this->tpl, ['notify' => $templateNotify]), + $href, + $seen, + ); + } + + public function createFromNotification(Entity\Notification $notification): ValueObject\FormattedNavNotification + { + $message = $this->notification->getMessageFromNotification($notification); + + if (!isset(self::$contacts[$notification->actorId])) { + self::$contacts[$notification->actorId] = Contact::getById($notification->actorId, ['name', 'url']); + } + + return $this->createFromParams( + self::$contacts[$notification->actorId], + $message['notification'], + $notification->created, + new Uri($this->baseUrl->get() . '/notification/' . $notification->id), + $notification->seen, + ); + } + + public function createFromIntro(\Friendica\Contact\Introduction\Entity\Introduction $intro): ValueObject\FormattedNavNotification + { + if (!isset(self::$contacts[$intro->cid])) { + self::$contacts[$intro->cid] = Contact::getById($intro->cid, ['name', 'url']); + } + + return $this->createFromParams( + self::$contacts[$intro->cid], + $this->l10n->t('{0}} wants to follow you'), + new \DateTime($intro->datetime, new \DateTimeZone('UTC')), + new Uri($this->baseUrl->get() . '/notifications/intros/' . $intro->id) + ); + } +} diff --git a/src/Navigation/Notifications/Factory/FormattedNotification.php b/src/Navigation/Notifications/Factory/FormattedNotification.php deleted file mode 100644 index f1df3c173b..0000000000 --- a/src/Navigation/Notifications/Factory/FormattedNotification.php +++ /dev/null @@ -1,376 +0,0 @@ -. - * - */ - -namespace Friendica\Navigation\Notifications\Factory; - -use Exception; -use Friendica\App\BaseURL; -use Friendica\BaseFactory; -use Friendica\Content\Text\BBCode; -use Friendica\Core\L10n; -use Friendica\Core\Protocol; -use Friendica\Database\Database; -use Friendica\Model\Contact; -use Friendica\Model\Post; -use Friendica\Module\BaseNotifications; -use Friendica\Navigation\Notifications\Collection\FormattedNotifications; -use Friendica\Navigation\Notifications\Repository; -use Friendica\Navigation\Notifications\ValueObject; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Protocol\Activity; -use Friendica\Util\DateTimeFormat; -use Friendica\Util\Proxy; -use Friendica\Util\Temporal; -use Friendica\Util\XML; -use Psr\Log\LoggerInterface; - -/** - * Factory for creating notification objects based on items - * Currently, there are the following types of item based notifications: - * - network - * - system - * - home - * - personal - */ -class FormattedNotification extends BaseFactory -{ - /** @var Database */ - private $dba; - /** @var Repository\Notify */ - private $notify; - /** @var BaseURL */ - private $baseUrl; - /** @var L10n */ - private $l10n; - - public function __construct(LoggerInterface $logger, Database $dba, Repository\Notify $notify, BaseURL $baseUrl, L10n $l10n) - { - parent::__construct($logger); - - $this->dba = $dba; - $this->notify = $notify; - $this->baseUrl = $baseUrl; - $this->l10n = $l10n; - } - - /** - * @param array $formattedItem The return of $this->formatItem - * - * @return ValueObject\FormattedNotification - */ - private function createFromFormattedItem(array $formattedItem): ValueObject\FormattedNotification - { - // Transform the different types of notification in a usable array - switch ($formattedItem['verb'] ?? '') { - case Activity::LIKE: - return new ValueObject\FormattedNotification( - 'like', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s liked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - case Activity::DISLIKE: - return new ValueObject\FormattedNotification( - 'dislike', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s disliked %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - case Activity::ATTEND: - return new ValueObject\FormattedNotification( - 'attend', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s is attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - case Activity::ATTENDNO: - return new ValueObject\FormattedNotification( - 'attendno', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s is not attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - case Activity::ATTENDMAYBE: - return new ValueObject\FormattedNotification( - 'attendmaybe', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s may attending %s's event", $formattedItem['author-name'], $formattedItem['parent-author-name']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - case Activity::FRIEND: - if (!isset($formattedItem['object'])) { - return new ValueObject\FormattedNotification( - 'friend', - $formattedItem['link'], - $formattedItem['image'], - $formattedItem['url'], - $formattedItem['text'], - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - } - - $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">"; - $obj = XML::parseString($xmlHead . $formattedItem['object']); - - $formattedItem['fname'] = $obj->title; - - return new ValueObject\FormattedNotification( - 'friend', - $this->baseUrl->get(true) . '/display/' . $formattedItem['parent-guid'], - $formattedItem['author-avatar'], - $formattedItem['author-link'], - $this->l10n->t("%s is now friends with %s", $formattedItem['author-name'], $formattedItem['fname']), - $formattedItem['when'], - $formattedItem['ago'], - $formattedItem['seen'] - ); - - default: - return new ValueObject\FormattedNotification( - $formattedItem['label'] ?? '', - $formattedItem['link'] ?? '', - $formattedItem['image'] ?? '', - $formattedItem['url'] ?? '', - $formattedItem['text'] ?? '', - $formattedItem['when'] ?? '', - $formattedItem['ago'] ?? '', - $formattedItem['seen'] ?? false - ); - } - } - - /** - * Get system notifications - * - * @param bool $seen False => only include notifications into the query - * which aren't marked as "seen" - * @param int $start Start the query at this point - * @param int $limit Maximum number of query results - * - * @return FormattedNotifications - */ - public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications - { - $conditions = []; - if (!$seen) { - $conditions['seen'] = false; - } - - $params = []; - $params['order'] = ['date' => 'DESC']; - $params['limit'] = [$start, $limit]; - - $formattedNotifications = new FormattedNotifications(); - try { - $Notifies = $this->notify->selectForUser(local_user(), $conditions, $params); - - foreach ($Notifies as $Notify) { - $formattedNotifications[] = new ValueObject\FormattedNotification( - 'notification', - $this->baseUrl->get(true) . '/notification/' . $Notify->id, - Contact::getAvatarUrlForUrl($Notify->url, $Notify->uid, Proxy::SIZE_MICRO), - $Notify->url, - strip_tags(BBCode::toPlaintext($Notify->msg)), - DateTimeFormat::local($Notify->date->format(DateTimeFormat::MYSQL), 'r'), - Temporal::getRelativeDate($Notify->date->format(DateTimeFormat::MYSQL)), - $Notify->seen - ); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['conditions' => $conditions, 'exception' => $e]); - } - - return $formattedNotifications; - } - - /** - * Get network notifications - * - * @param bool $seen False => only include notifications into the query - * which aren't marked as "seen" - * @param int $start Start the query at this point - * @param int $limit Maximum number of query results - * - * @return FormattedNotifications - */ - public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications - { - $condition = ['wall' => false, 'uid' => local_user()]; - - if (!$seen) { - $condition['unseen'] = true; - } - - $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar', - 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity']; - $params = ['order' => ['received' => true], 'limit' => [$start, $limit]]; - - $formattedNotifications = new FormattedNotifications(); - - try { - $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params); - while ($userPost = $this->dba->fetch($userPosts)) { - $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost)); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['condition' => $condition, 'exception' => $e]); - } - - return $formattedNotifications; - } - - /** - * Get personal notifications - * - * @param bool $seen False => only include notifications into the query - * which aren't marked as "seen" - * @param int $start Start the query at this point - * @param int $limit Maximum number of query results - * - * @return FormattedNotifications - */ - public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications - { - $condition = ['wall' => false, 'uid' => local_user(), 'author-id' => public_contact()]; - - if (!$seen) { - $condition['unseen'] = true; - } - - $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar', - 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity']; - $params = ['order' => ['received' => true], 'limit' => [$start, $limit]]; - - $formattedNotifications = new FormattedNotifications(); - - try { - $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params); - while ($userPost = $this->dba->fetch($userPosts)) { - $formattedNotifications[] = $this->createFromFormattedItem($this->formatItem($userPost)); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]); - } - - return $formattedNotifications; - } - - /** - * Get home notifications - * - * @param bool $seen False => only include notifications into the query - * which aren't marked as "seen" - * @param int $start Start the query at this point - * @param int $limit Maximum number of query results - * - * @return FormattedNotifications - */ - public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT): FormattedNotifications - { - $condition = ['wall' => true, 'uid' => local_user()]; - - if (!$seen) { - $condition['unseen'] = true; - } - - $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar', - 'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity']; - $params = ['order' => ['received' => true], 'limit' => [$start, $limit]]; - - $formattedNotifications = new FormattedNotifications(); - - try { - $userPosts = Post::selectForUser(local_user(), $fields, $condition, $params); - while ($userPost = $this->dba->fetch($userPosts)) { - $formattedItem = $this->formatItem($userPost); - - // Overwrite specific fields, not default item format - $formattedItem['label'] = 'comment'; - $formattedItem['text'] = $this->l10n->t("%s commented on %s's post", $formattedItem['author-name'], $formattedItem['parent-author-name']); - - $formattedNotifications[] = $this->createFromFormattedItem($formattedItem); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]); - } - - return $formattedNotifications; - } - - /** - * Format the item query in a usable array - * - * @param array $item The item from the db query - * - * @return array The item, extended with the notification-specific information - * - * @throws InternalServerErrorException - * @throws Exception - */ - private function formatItem(array $item): array - { - $item['seen'] = !($item['unseen'] > 0); - - // For feed items we use the user's contact, since the avatar is mostly self choosen. - if (!empty($item['network']) && $item['network'] == Protocol::FEED) { - $item['author-avatar'] = $item['contact-avatar']; - } - - $item['label'] = (($item['gravity'] == GRAVITY_PARENT) ? 'post' : 'comment'); - $item['link'] = $this->baseUrl->get(true) . '/display/' . $item['parent-guid']; - $item['image'] = $item['author-avatar']; - $item['url'] = $item['author-link']; - $item['when'] = DateTimeFormat::local($item['created'], 'r'); - $item['ago'] = Temporal::getRelativeDate($item['created']); - $item['text'] = (($item['gravity'] == GRAVITY_PARENT) - ? $this->l10n->t("%s created a new post", $item['author-name']) - : $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name'])); - - return $item; - } -} diff --git a/src/Navigation/Notifications/Repository/Notification.php b/src/Navigation/Notifications/Repository/Notification.php index 2269f5bc54..57f85054de 100644 --- a/src/Navigation/Notifications/Repository/Notification.php +++ b/src/Navigation/Notifications/Repository/Notification.php @@ -100,6 +100,36 @@ class Notification extends BaseRepository return $this->select($condition, $params); } + /** + * Returns only the most recent notifications for the same conversation or contact + * + * @param int $uid + * @return Collection\Notifications + * @throws Exception + */ + public function selectDigestForUser(int $uid): Collection\Notifications + { + $rows = $this->db->p(" + SELECT notification.* + FROM notification + WHERE id IN ( + SELECT MAX(`id`) + FROM notification + WHERE uid = ? + GROUP BY IFNULL(`parent-uri-id`, `actor-id`) + ) + ORDER BY `seen`, `id` DESC + LIMIT 50 + ", $uid); + + $Entities = new Collection\Notifications(); + foreach ($rows as $fields) { + $Entities[] = $this->factory->createFromTableRow($fields); + } + + return $Entities; + } + public function selectAllForUser(int $uid): Collection\Notifications { return $this->selectForUser($uid); diff --git a/src/Navigation/Notifications/ValueObject/FormattedNotification.php b/src/Navigation/Notifications/ValueObject/FormattedNavNotification.php similarity index 50% rename from src/Navigation/Notifications/ValueObject/FormattedNotification.php rename to src/Navigation/Notifications/ValueObject/FormattedNavNotification.php index 09d41fa876..4a11fa616f 100644 --- a/src/Navigation/Notifications/ValueObject/FormattedNotification.php +++ b/src/Navigation/Notifications/ValueObject/FormattedNavNotification.php @@ -26,40 +26,36 @@ use Friendica\BaseDataTransferObject; /** * A view-only object for printing item notifications to the frontend */ -class FormattedNotification extends BaseDataTransferObject +class FormattedNavNotification extends BaseDataTransferObject { - const SYSTEM = 'system'; - const PERSONAL = 'personal'; - const NETWORK = 'network'; - const INTRO = 'intro'; - const HOME = 'home'; + /** @var array */ + protected $contact; + /** @var string */ + protected $timestamp; + /** @var string */ + protected $plaintext; + /** @var string */ + protected $html; + /** @var string */ + protected $href; + /** @var bool */ + protected $seen; - /** @var string */ - protected $label = ''; - /** @var string */ - protected $link = ''; - /** @var string */ - protected $image = ''; - /** @var string */ - protected $url = ''; - /** @var string */ - protected $text = ''; - /** @var string */ - protected $when = ''; - /** @var string */ - protected $ago = ''; - /** @var boolean */ - protected $seen = false; - - public function __construct(string $label, string $link, string $image, string $url, string $text, string $when, string $ago, bool $seen) + /** + * @param array $contact Contact array with the following keys: name, url, photo + * @param string $timestamp Unix timestamp + * @param string $plaintext Localized notification message with the placeholder replaced by the contact name + * @param string $html Full HTML string of the notification menu element + * @param string $href Absolute URL this notification should send the user to when interacted with + * @param bool $seen Whether the user interacted with this notification once + */ + public function __construct(array $contact, string $timestamp, string $plaintext, string $html, string $href, bool $seen) { - $this->label = $label ?? ''; - $this->link = $link ?? ''; - $this->image = $image ?? ''; - $this->url = $url ?? ''; - $this->text = $text ?? ''; - $this->when = $when ?? ''; - $this->ago = $ago ?? ''; - $this->seen = $seen ?? false; + $this->contact = $contact; + $this->timestamp = $timestamp; + $this->plaintext = $plaintext; + $this->html = $html; + $this->href = $href; + $this->seen = $seen; } }