diff --git a/include/api.php b/include/api.php index cd096c187a..11950f52f3 100644 --- a/include/api.php +++ b/include/api.php @@ -5647,7 +5647,7 @@ function api_friendica_notification_seen($type) } if ($Notify->uriId) { - DI::dba()->update('notification', ['seen' => true], ['uid' => $Notify->uid, 'target-uri-id' => $Notify->uriId]); + DI::notification()->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]); } $Notify->setSeen(); diff --git a/include/enotify.php b/include/enotify.php index 623347b8b0..90a17ad25e 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -491,22 +491,21 @@ function notification_store_and_send($params, $sitelink, $tsitelink, $hsitelink, return false; } -function notification_from_array(array $notification) +function notification_from_array(Notifications\Entity\Notification $Notification) { - Logger::info('Start', ['uid' => $notification['uid'], 'id' => $notification['id'], 'type' => $notification['type']]); + Logger::info('Start', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]); - if ($notification['type'] == Post\UserNotification::TYPE_NONE) { - Logger::info('Not an item based notification, quitting', ['uid' => $notification['uid'], 'id' => $notification['id'], 'type' => $notification['type']]); + if ($Notification->type === Post\UserNotification::TYPE_NONE) { + Logger::info('Not an item based notification, quitting', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]); return false; } $params = []; - $params['verb'] = Verb::getByID($notification['vid']); - - $params['uid'] = $notification['uid']; + $params['verb'] = $Notification->verb; + $params['uid'] = $Notification->uid; $params['otype'] = Notification\ObjectType::ITEM; - $user = User::getById($notification['uid']); + $user = User::getById($Notification->uid); $params['notify_flags'] = $user['notify-flags']; $params['language'] = $user['language']; @@ -516,18 +515,18 @@ function notification_from_array(array $notification) // from here on everything is in the recipients language $l10n = DI::l10n()->withLang($user['language']); - $contact = Contact::getById($notification['actor-id'], ['url', 'name', 'photo']); + $contact = Contact::getById($Notification->actorId, ['url', 'name', 'photo']); if (DBA::isResult($contact)) { $params['source_link'] = $contact['url']; $params['source_name'] = $contact['name']; $params['source_photo'] = $contact['photo']; } - $item = Post::selectFirstForUser($notification['uid'], Item::ITEM_FIELDLIST, - ['uid' => [0, $notification['uid']], 'uri-id' => $notification['target-uri-id'], 'deleted' => false], + $item = Post::selectFirstForUser($Notification->uid, Item::ITEM_FIELDLIST, + ['uid' => [0, $Notification->uid], 'uri-id' => $Notification->targetUriId, 'deleted' => false], ['order' => ['uid' => true]]); if (empty($item)) { - Logger::info('Item not found', ['uri-id' => $notification['target-uri-id'], 'type' => $notification['type']]); + Logger::info('Item not found', ['uri-id' => $Notification->targetUriId, 'type' => $Notification->type]); return false; } @@ -537,8 +536,8 @@ function notification_from_array(array $notification) $subjectPrefix = $l10n->t('[Friendica:Notify]'); - if (Post\ThreadUser::getIgnored($notification['parent-uri-id'], $notification['uid'])) { - Logger::info('Thread is ignored', ['parent-uri-id' => $notification['parent-uri-id'], 'type' => $notification['type']]); + if (Post\ThreadUser::getIgnored($Notification->parentUriId, $Notification->uid)) { + Logger::info('Thread is ignored', ['parent-uri-id' => $Notification->parentUriId, 'type' => $Notification->type]); return false; } @@ -546,8 +545,8 @@ function notification_from_array(array $notification) // If so don't create a second notification $condition = ['type' => [Notification\Type::TAG_SELF, Notification\Type::COMMENT, Notification\Type::SHARE], 'link' => $params['link'], 'verb' => Activity::POST]; - if (DI::notify()->existsForUser($notification['uid'], $condition)) { - Logger::info('Duplicate found, quitting', $condition + ['uid' => $notification['uid']]); + if (DI::notify()->existsForUser($Notification->uid, $condition)) { + Logger::info('Duplicate found, quitting', $condition + ['uid' => $Notification->uid]); return false; } @@ -562,10 +561,10 @@ function notification_from_array(array $notification) // So, we cannot have different subjects for notifications of the same thread. // Before this we have the name of the replier on the subject rendering // different subjects for messages on the same thread. - if ($notification['type'] == Post\UserNotification::TYPE_EXPLICIT_TAGGED) { + if ($Notification->type === Post\UserNotification::TYPE_EXPLICIT_TAGGED) { $params['type'] = Notification\Type::TAG_SELF; $subject = $l10n->t('%s %s tagged you', $subjectPrefix, $contact['name']); - } elseif ($notification['type'] == Post\UserNotification::TYPE_SHARED) { + } elseif ($Notification->type === Post\UserNotification::TYPE_SHARED) { $params['type'] = Notification\Type::SHARE; $subject = $l10n->t('%s %s shared a new post', $subjectPrefix, $contact['name']); } else { @@ -573,9 +572,9 @@ function notification_from_array(array $notification) $subject = $l10n->t('%1$s Comment to conversation #%2$d by %3$s', $subjectPrefix, $item['parent'], $contact['name']); } - $msg = Notification::getMessage($notification); + $msg = (new Notifications\Factory\Notification(DI::logger()))->getMessageFromNotification($Notification, DI::baseUrl(), $l10n); if (empty($msg)) { - Logger::info('No notification message, quitting', ['uid' => $notification['uid'], 'id' => $notification['id'], 'type' => $notification['type']]); + Logger::info('No notification message, quitting', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]); return false; } @@ -590,7 +589,7 @@ function notification_from_array(array $notification) $hsitelink = sprintf($sitelink, '' . $sitename . ''); $itemlink = $params['link']; - Logger::info('Perform notification', ['uid' => $notification['uid'], 'id' => $notification['id'], 'type' => $notification['type']]); + Logger::info('Perform notification', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]); return notification_store_and_send($params, $sitelink, $tsitelink, $hsitelink, $title, $subject, $preamble, $epreamble, $item['body'], $itemlink, true); } diff --git a/mod/display.php b/mod/display.php index 26f6eae368..a349a552c3 100644 --- a/mod/display.php +++ b/mod/display.php @@ -222,7 +222,7 @@ function display_content(App $a, $update = false, $update_uid = 0) } if (!DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { - DBA::update('notification', ['seen' => true], ['parent-uri-id' => $item['parent-uri-id'], 'uid' => local_user()]); + DI::notification()->setAllSeenForUser(local_user(), ['parent-uri-id' => $item['parent-uri-id']]); DI::notify()->setAllSeenForUser(local_user(), ['parent-uri-id' => $item['parent-uri-id']]); } diff --git a/src/DI.php b/src/DI.php index ed73ab767c..4168997d4b 100644 --- a/src/DI.php +++ b/src/DI.php @@ -458,6 +458,11 @@ abstract class DI return self::$dice->create(Repository\ProfileField::class); } + public static function notification(): Navigation\Notifications\Depository\Notification + { + return self::$dice->create(Navigation\Notifications\Depository\Notification::class); + } + public static function notify(): Navigation\Notifications\Depository\Notify { return self::$dice->create(Navigation\Notifications\Depository\Notify::class); diff --git a/src/Factory/Api/Mastodon/Notification.php b/src/Factory/Api/Mastodon/Notification.php index 03d102b503..bdc5717f89 100644 --- a/src/Factory/Api/Mastodon/Notification.php +++ b/src/Factory/Api/Mastodon/Notification.php @@ -22,44 +22,41 @@ namespace Friendica\Factory\Api\Mastodon; use Friendica\BaseFactory; -use Friendica\Database\Database; -use Friendica\Model\Notification as ModelNotification; +use Friendica\Model\Contact; +use Friendica\Navigation\Notifications; +use Friendica\Navigation\Notifications\Exception\UnexpectedNotificationTypeException; +use Friendica\Object\Api\Mastodon\Notification as MstdnNotification; +use Friendica\Protocol\Activity; use Psr\Log\LoggerInterface; +use Friendica\Navigation\Notifications\Entity; +use Friendica\Model\Post; class Notification extends BaseFactory { - /** @var Database */ - private $dba; /** @var Account */ private $mstdnAccountFactory; /** @var Status */ private $mstdnStatusFactory; - public function __construct(LoggerInterface $logger, Database $dba, Account $mstdnAccountFactory, Status $mstdnStatusFactoryFactory) + public function __construct(LoggerInterface $logger, Account $mstdnAccountFactory, Status $mstdnStatusFactoryFactory) { parent::__construct($logger); - $this->dba = $dba; $this->mstdnAccountFactory = $mstdnAccountFactory; $this->mstdnStatusFactory = $mstdnStatusFactoryFactory; } - public function createFromNotificationId(int $id) + public function createFromNotification(Notifications\Entity\Notification $Notification): MstdnNotification { - $notification = $this->dba->selectFirst('notification', [], ['id' => $id]); - if (!$this->dba->isResult($notification)) { - return null; - } - - $type = ModelNotification::getType($notification); + $type = self::getType($Notification); if (empty($type)) { - return null; + throw new UnexpectedNotificationTypeException(); } - $account = $this->mstdnAccountFactory->createFromContactId($notification['actor-id'], $notification['uid']); + $account = $this->mstdnAccountFactory->createFromContactId($Notification->actorId, $Notification->uid); - if (!empty($notification['target-uri-id'])) { + if ($Notification->targetUriId) { try { - $status = $this->mstdnStatusFactory->createFromUriId($notification['target-uri-id'], $notification['uid']); + $status = $this->mstdnStatusFactory->createFromUriId($Notification->targetUriId, $Notification->uid); } catch (\Throwable $th) { $status = null; } @@ -67,6 +64,41 @@ class Notification extends BaseFactory $status = null; } - return new \Friendica\Object\Api\Mastodon\Notification($id, $type, $notification['created'], $account, $status); + return new MstdnNotification($Notification->id, $type, $Notification->created, $account, $status); + } + + /** + * Computes the Mastodon notification type from the given local notification + * + * @param Entity\Notification $Notification + * @return string + * @throws \Exception + */ + public static function getType(Entity\Notification $Notification): string + { + if (($Notification->verb == Activity::FOLLOW) && ($Notification->type === Post\UserNotification::TYPE_NONE)) { + $contact = Contact::getById($Notification->actorId, ['pending']); + $type = $contact['pending'] ? MstdnNotification::TYPE_INTRODUCTION : MstdnNotification::TYPE_FOLLOW; + } elseif (($Notification->verb == Activity::ANNOUNCE) && + in_array($Notification->type, [Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT])) { + $type = MstdnNotification::TYPE_RESHARE; + } elseif (in_array($Notification->verb, [Activity::LIKE, Activity::DISLIKE]) && + in_array($Notification->type, [Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT])) { + $type = MstdnNotification::TYPE_LIKE; + } elseif ($Notification->type === Post\UserNotification::TYPE_SHARED) { + $type = MstdnNotification::TYPE_POST; + } elseif (in_array($Notification->type, [ + Post\UserNotification::TYPE_EXPLICIT_TAGGED, + Post\UserNotification::TYPE_IMPLICIT_TAGGED, + Post\UserNotification::TYPE_DIRECT_COMMENT, + Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT, + Post\UserNotification::TYPE_THREAD_COMMENT + ])) { + $type = MstdnNotification::TYPE_MENTION; + } else { + return ''; + } + + return $type; } } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 18e041ab98..ddf541b70e 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -2705,7 +2705,7 @@ class Contact // Ensure to always have the correct network type, independent from the connection request method self::updateFromProbe($contact['id']); - Post\UserNotification::insertNotification($contact['id'], Verb::getID(Activity::FOLLOW), $importer['uid']); + Post\UserNotification::insertNotification($contact['id'], Activity::FOLLOW, $importer['uid']); return true; } else { @@ -2736,7 +2736,7 @@ class Contact self::updateAvatar($contact_id, $photo, true); - Post\UserNotification::insertNotification($contact_id, Verb::getID(Activity::FOLLOW), $importer['uid']); + Post\UserNotification::insertNotification($contact_id, Activity::FOLLOW, $importer['uid']); $contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]); diff --git a/src/Model/Notification.php b/src/Model/Notification.php deleted file mode 100644 index 284b3e7a83..0000000000 --- a/src/Model/Notification.php +++ /dev/null @@ -1,266 +0,0 @@ -. - * - */ - -namespace Friendica\Model; - -use Friendica\BaseModel; -use Friendica\Content\Text\BBCode; -use Friendica\Content\Text\Plaintext; -use Friendica\Core\Logger; -use Friendica\Database\Database; -use Friendica\DI; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Object\Api\Mastodon\Notification as MstdnNotification; -use Friendica\Protocol\Activity; -use Psr\Log\LoggerInterface; - -/** - * Model for an entry in the notify table - */ -class Notification extends BaseModel -{ - /** - * Fetch the notification type for the given notification - * - * @param array $notification - * @return string - */ - public static function getType(array $notification): string - { - if (($notification['vid'] == Verb::getID(Activity::FOLLOW)) && ($notification['type'] == Post\UserNotification::TYPE_NONE)) { - $contact = Contact::getById($notification['actor-id'], ['pending']); - $type = $contact['pending'] ? MstdnNotification::TYPE_INTRODUCTION : MstdnNotification::TYPE_FOLLOW; - } elseif (($notification['vid'] == Verb::getID(Activity::ANNOUNCE)) && - in_array($notification['type'], [Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT])) { - $type = MstdnNotification::TYPE_RESHARE; - } elseif (in_array($notification['vid'], [Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE)]) && - in_array($notification['type'], [Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT])) { - $type = MstdnNotification::TYPE_LIKE; - } elseif ($notification['type'] == Post\UserNotification::TYPE_SHARED) { - $type = MstdnNotification::TYPE_POST; - } elseif (in_array($notification['type'], [ - Post\UserNotification::TYPE_EXPLICIT_TAGGED, - Post\UserNotification::TYPE_IMPLICIT_TAGGED, - Post\UserNotification::TYPE_DIRECT_COMMENT, - Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT, - Post\UserNotification::TYPE_THREAD_COMMENT - ])) { - $type = MstdnNotification::TYPE_MENTION; - } else { - return ''; - } - - return $type; - } - - /** - * Create a notification message for the given notification - * - * @param array $notification - * @return array with the elements "causer", "notification", "plain" and "rich" - */ - public static function getMessage(array $notification) - { - $message = []; - - $user = User::getById($notification['uid']); - if (empty($user)) { - Logger::info('User not found', ['application' => $notification['uid']]); - return $message; - } - - $l10n = DI::l10n()->withLang($user['language']); - - $causer = $contact = Contact::getById($notification['actor-id'], ['id', 'name', 'url', 'pending']); - if (empty($contact)) { - Logger::info('Contact not found', ['contact' => $notification['actor-id']]); - return $message; - } - - if ($notification['type'] == Post\UserNotification::TYPE_NONE) { - if ($contact['pending']) { - $msg = $l10n->t('%1$s wants to follow you'); - } else { - $msg = $l10n->t('%1$s had started following you'); - } - $title = $contact['name']; - $link = DI::baseUrl() . '/contact/' . $contact['id']; - } else { - if (empty($notification['target-uri-id'])) { - return $message; - } - - $like = Verb::getID(Activity::LIKE); - $dislike = Verb::getID(Activity::DISLIKE); - $announce = Verb::getID(Activity::ANNOUNCE); - $post = Verb::getID(Activity::POST); - - if (in_array($notification['type'], [Post\UserNotification::TYPE_THREAD_COMMENT, Post\UserNotification::TYPE_COMMENT_PARTICIPATION, Post\UserNotification::TYPE_ACTIVITY_PARTICIPATION, Post\UserNotification::TYPE_EXPLICIT_TAGGED])) { - $item = Post::selectFirst([], ['uri-id' => $notification['parent-uri-id'], 'uid' => [0, $notification['uid']]], ['order' => ['uid' => true]]); - if (empty($item)) { - Logger::info('Parent post not found', ['uri-id' => $notification['parent-uri-id']]); - return $message; - } - } else { - $item = Post::selectFirst([], ['uri-id' => $notification['target-uri-id'], 'uid' => [0, $notification['uid']]], ['order' => ['uid' => true]]); - if (empty($item)) { - Logger::info('Post not found', ['uri-id' => $notification['target-uri-id']]); - return $message; - } - - if ($notification['vid'] == $post) { - $item = Post::selectFirst([], ['uri-id' => $item['thr-parent-id'], 'uid' => [0, $notification['uid']]], ['order' => ['uid' => true]]); - if (empty($item)) { - Logger::info('Thread parent post not found', ['uri-id' => $item['thr-parent-id']]); - return $message; - } - } - } - - if ($item['owner-id'] != $item['author-id']) { - $cid = $item['owner-id']; - } - if (!empty($item['causer-id']) && ($item['causer-id'] != $item['author-id'])) { - $cid = $item['causer-id']; - } - - if (($notification['type'] == Post\UserNotification::TYPE_SHARED) && !empty($cid)) { - $causer = Contact::getById($cid, ['id', 'name', 'url']); - if (empty($contact)) { - Logger::info('Causer not found', ['causer' => $cid]); - return $message; - } - } elseif (in_array($notification['type'], [Post\UserNotification::TYPE_COMMENT_PARTICIPATION, Post\UserNotification::TYPE_ACTIVITY_PARTICIPATION])) { - $contact = Contact::getById($item['author-id'], ['id', 'name', 'url']); - if (empty($contact)) { - Logger::info('Author not found', ['author' => $item['author-id']]); - return $message; - } - } - - $link = DI::baseUrl() . '/display/' . urlencode($item['guid']); - - $content = Plaintext::getPost($item, 70); - if (!empty($content['text'])) { - $title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"'; - } else { - $title = ''; - } - - switch ($notification['vid']) { - case $like: - switch ($notification['type']) { - case Post\UserNotification::TYPE_DIRECT_COMMENT: - $msg = $l10n->t('%1$s liked your comment %2$s'); - break; - case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s liked your post %2$s'); - break; - } - break; - case $dislike: - switch ($notification['type']) { - case Post\UserNotification::TYPE_DIRECT_COMMENT: - $msg = $l10n->t('%1$s disliked your comment %2$s'); - break; - case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s disliked your post %2$s'); - break; - } - break; - case $announce: - switch ($notification['type']) { - case Post\UserNotification::TYPE_DIRECT_COMMENT: - $msg = $l10n->t('%1$s shared your comment %2$s'); - break; - case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s shared your post %2$s'); - break; - } - break; - case $post: - switch ($notification['type']) { - case Post\UserNotification::TYPE_EXPLICIT_TAGGED: - $msg = $l10n->t('%1$s tagged you on %2$s'); - break; - - case Post\UserNotification::TYPE_IMPLICIT_TAGGED: - $msg = $l10n->t('%1$s replied to you on %2$s'); - break; - - case Post\UserNotification::TYPE_THREAD_COMMENT: - $msg = $l10n->t('%1$s commented in your thread %2$s'); - break; - - case Post\UserNotification::TYPE_DIRECT_COMMENT: - $msg = $l10n->t('%1$s commented on your comment %2$s'); - break; - - case Post\UserNotification::TYPE_COMMENT_PARTICIPATION: - case Post\UserNotification::TYPE_ACTIVITY_PARTICIPATION: - if (($causer['id'] == $contact['id']) && ($title != '')) { - $msg = $l10n->t('%1$s commented in their thread %2$s'); - } elseif ($causer['id'] == $contact['id']) { - $msg = $l10n->t('%1$s commented in their thread'); - } elseif ($title != '') { - $msg = $l10n->t('%1$s commented in the thread %2$s from %3$s'); - } else { - $msg = $l10n->t('%1$s commented in the thread from %3$s'); - } - break; - - case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s commented on your thread %2$s'); - break; - - case Post\UserNotification::TYPE_SHARED: - if (($causer['id'] != $contact['id']) && ($title != '')) { - $msg = $l10n->t('%1$s shared the post %2$s from %3$s'); - } elseif ($causer['id'] != $contact['id']) { - $msg = $l10n->t('%1$s shared a post from %3$s'); - } elseif ($title != '') { - $msg = $l10n->t('%1$s shared the post %2$s'); - } else { - $msg = $l10n->t('%1$s shared a post'); - } - break; - } - break; - } - } - - if (!empty($msg)) { - // Name of the notification's causer - $message['causer'] = $causer['name']; - // Format for the "ping" mechanism - $message['notification'] = sprintf($msg, '{0}', $title, $contact['name']); - // Plain text for the web push api - $message['plain'] = sprintf($msg, $causer['name'], $title, $contact['name']); - // Rich text for other purposes - $message['rich'] = sprintf($msg, - '[url=' . $causer['url'] . ']' . $causer['name'] . '[/url]', - '[url=' . $link . ']' . $title . '[/url]', - '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'); - } - - return $message; - } -} diff --git a/src/Model/Post/UserNotification.php b/src/Model/Post/UserNotification.php index 065cba44ca..d14eb7f3be 100644 --- a/src/Model/Post/UserNotification.php +++ b/src/Model/Post/UserNotification.php @@ -33,9 +33,9 @@ use Friendica\Model\Contact; use Friendica\Model\Post; use Friendica\Model\Subscription; use Friendica\Model\Tag; +use Friendica\Navigation\Notifications; use Friendica\Network\HTTPException; use Friendica\Protocol\Activity; -use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; class UserNotification @@ -286,56 +286,46 @@ class UserNotification return; } - $fields = [ - 'uid' => $uid, - 'vid' => $item['vid'], - 'type' => $type, - 'actor-id' => $item['author-id'], - 'parent-uri-id' => $item['parent-uri-id'], - 'created' => DateTimeFormat::utcNow(), - ]; + $notification = (new Notifications\Factory\Notification(DI::logger()))->createForUser( + $uid, + $item['vid'], + $type, + $item['author-id'], + $item['gravity'] == GRAVITY_ACTIVITY ? $item['thr-parent-id'] : $item['uri-id'], + $item['parent-uri-id'] + ); - if ($item['gravity'] == GRAVITY_ACTIVITY) { - $fields['target-uri-id'] = $item['thr-parent-id']; - } else { - $fields['target-uri-id'] = $item['uri-id']; - } + try { + $notification = DI::notification()->save($notification); + Subscription::pushByNotification($notification); + } catch (Exception $e) { - if (DBA::insert('notification', $fields, Database::INSERT_IGNORE)) { - $id = DBA::lastInsertId(); - if (!empty($id)) { - Subscription::pushByNotificationId($id); - } } } /** * Add a notification entry * - * @param int $actor Contact ID of the actor - * @param int $vid Verb ID - * @param int $uid User ID + * @param int $actor Contact ID of the actor + * @param string $verb One of the Activity verb constant values + * @param int $uid User ID * @return boolean * @throws Exception */ - public static function insertNotification(int $actor, int $vid, int $uid): bool + public static function insertNotification(int $actor, string $verb, int $uid): bool { - $fields = [ - 'uid' => $uid, - 'vid' => $vid, - 'type' => self::TYPE_NONE, - 'actor-id' => $actor, - 'created' => DateTimeFormat::utcNow(), - ]; - - $ret = DBA::insert('notification', $fields, Database::INSERT_IGNORE); - if ($ret) { - $id = DBA::lastInsertId(); - if (!empty($id)) { - Subscription::pushByNotificationId($id); - } + $notification = (new Notifications\Factory\Notification(DI::logger()))->createForRelationship( + $uid, + $actor, + $verb + ); + try { + $notification = DI::notification()->save($notification); + Subscription::pushByNotification($notification); + return true; + } catch (Exception $e) { + return false; } - return $ret; } /** diff --git a/src/Model/Subscription.php b/src/Model/Subscription.php index f9498af986..c6c57c7328 100644 --- a/src/Model/Subscription.php +++ b/src/Model/Subscription.php @@ -25,6 +25,7 @@ use Friendica\Core\Logger; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; +use Friendica\Navigation\Notifications\Entity; use Friendica\Object\Api\Mastodon\Notification; use Minishlink\WebPush\VAPID; @@ -138,34 +139,32 @@ class Subscription * @param int $nid * @return void */ - public static function pushByNotificationId(int $nid) + public static function pushByNotification(Entity\Notification $Notification) { - $notification = DBA::selectFirst('notification', [], ['id' => $nid]); + $type = \Friendica\Factory\Api\Mastodon\Notification::getType($Notification); - $type = Notification::getType($notification); $desktop_notification = !in_array($type, [Notification::TYPE_RESHARE, Notification::TYPE_LIKE]); - - if (DI::pConfig()->get($notification['uid'], 'system', 'notify_like') && ($type == 'favourite')) { + if (DI::pConfig()->get($Notification->uid, 'system', 'notify_like') && ($type == Notification::TYPE_LIKE)) { $desktop_notification = true; } - if (DI::pConfig()->get($notification['uid'], 'system', 'notify_announce') && ($type == 'reblog')) { + if (DI::pConfig()->get($Notification->uid, 'system', 'notify_announce') && ($type == Notification::TYPE_RESHARE)) { $desktop_notification = true; } if ($desktop_notification) { - notification_from_array($notification); + notification_from_array($Notification); } if (empty($type)) { return; } - $subscriptions = DBA::select('subscription', [], ['uid' => $notification['uid'], $type => true]); + $subscriptions = DBA::select('subscription', [], ['uid' => $Notification->uid, $type => true]); while ($subscription = DBA::fetch($subscriptions)) { Logger::info('Push notification', ['id' => $subscription['id'], 'uid' => $subscription['uid'], 'type' => $type]); - Worker::add(PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $nid); + Worker::add(PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $Notification->id); } DBA::close($subscriptions); } diff --git a/src/Module/Api/Mastodon/Notifications.php b/src/Module/Api/Mastodon/Notifications.php index a04a9ae69a..28166e6ed7 100644 --- a/src/Module/Api/Mastodon/Notifications.php +++ b/src/Module/Api/Mastodon/Notifications.php @@ -28,6 +28,8 @@ use Friendica\Model\Contact; use Friendica\Model\Post; use Friendica\Model\Verb; use Friendica\Module\BaseApi; +use Friendica\Navigation\Notifications\Entity; +use Friendica\Object\Api\Mastodon\Notification; use Friendica\Protocol\Activity; /** @@ -46,10 +48,12 @@ class Notifications extends BaseApi if (!empty($parameters['id'])) { $id = $parameters['id']; - if (!DBA::exists('notification', ['id' => $id, 'uid' => $uid])) { + try { + $notification = DI::notification()->selectOneForUser($uid, ['id' => $id]); + System::jsonExit(DI::mstdnNotification()->createFromNotification($notification)); + } catch (\Exception $e) { DI::mstdnError()->RecordNotFound(); } - System::jsonExit(DI::mstdnNotification()->createFromNotificationId($id)); } $request = self::getRequest([ @@ -63,7 +67,7 @@ class Notifications extends BaseApi 'count' => 0, // Unknown parameter ]); - $params = ['order' => ['id' => true], 'limit' => $request['limit']]; + $params = ['order' => ['id' => true]]; $condition = ['uid' => $uid, 'seen' => false]; @@ -115,36 +119,26 @@ class Notifications extends BaseApi Verb::getID(Activity::POST), Post\UserNotification::TYPE_SHARED]); } - if (!empty($request['max_id'])) { - $condition = DBA::mergeConditions($condition, ["`id` < ?", $request['max_id']]); - } + $mstdnNotifications = []; - if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`id` > ?", $request['since_id']]); - } + $Notifications = DI::notification()->selectByBoundaries( + $condition, + $params, + $request['min_id'] ?? null, + $request['min_id'] ?? $request['since_id'] ?? null, + $request['limit'] + ); - if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`id` > ?", $request['min_id']]); - - $params['order'] = ['id']; - } - - $notifications = []; - - $notify = DBA::select('notification', ['id'], $condition, $params); - while ($notification = DBA::fetch($notify)) { - self::setBoundaries($notification['id']); - $entry = DI::mstdnNotification()->createFromNotificationId($notification['id']); - if (!empty($entry)) { - $notifications[] = $entry; + foreach($Notifications as $Notification) { + try { + $mstdnNotifications[] = DI::mstdnNotification()->createFromNotification($Notification); + self::setBoundaries($Notification->id); + } catch (\Exception $e) { + // Skip this notification } } - if (!empty($request['min_id'])) { - array_reverse($notifications); - } - self::setLinkHeader(); - System::jsonExit($notifications); + System::jsonExit($mstdnNotifications); } } diff --git a/src/Module/Api/Mastodon/Notifications/Clear.php b/src/Module/Api/Mastodon/Notifications/Clear.php index 5471dc24e6..9dca0bf655 100644 --- a/src/Module/Api/Mastodon/Notifications/Clear.php +++ b/src/Module/Api/Mastodon/Notifications/Clear.php @@ -22,7 +22,7 @@ namespace Friendica\Module\Api\Mastodon\Notifications; use Friendica\Core\System; -use Friendica\Database\DBA; +use Friendica\DI; use Friendica\Module\BaseApi; /** @@ -35,7 +35,7 @@ class Clear extends BaseApi self::checkAllowedScope(self::SCOPE_WRITE); $uid = self::getCurrentUserID(); - DBA::update('notification', ['seen' => true], ['uid' => $uid]); + DI::notification()->setAllSeenForUser($uid); System::jsonExit([]); } diff --git a/src/Module/Api/Mastodon/Notifications/Dismiss.php b/src/Module/Api/Mastodon/Notifications/Dismiss.php index 8900a2d43f..b615c5e890 100644 --- a/src/Module/Api/Mastodon/Notifications/Dismiss.php +++ b/src/Module/Api/Mastodon/Notifications/Dismiss.php @@ -25,6 +25,7 @@ use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\BaseApi; +use Friendica\Network\HTTPException\ForbiddenException; /** * @see https://docs.joinmastodon.org/methods/notifications/ @@ -40,7 +41,9 @@ class Dismiss extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - DBA::update('notification', ['seen' => true], ['uid' => $uid, 'id' => $parameters['id']]); + $Notification = DI::notification()->selectOneForUser($uid, $parameters['id']); + $Notification->setSeen(); + DI::notification()->save($Notification); System::jsonExit([]); } diff --git a/src/Module/Notifications/Notification.php b/src/Module/Notifications/Notification.php index 6f032772c7..2480e63b0b 100644 --- a/src/Module/Notifications/Notification.php +++ b/src/Module/Notifications/Notification.php @@ -78,7 +78,7 @@ class Notification extends BaseModule if (DI::args()->get(1) === 'mark' && DI::args()->get(2) === 'all') { try { - DI::dba()->update('notification', ['seen' => true], ['uid' => local_user()]); + DI::notification()->setAllSeenForUser(local_user()); $success = DI::notify()->setAllSeenForUser(local_user()); } catch (\Exception $e) { DI::logger()->warning('set all seen failed.', ['exception' => $e]); @@ -118,7 +118,7 @@ class Notification extends BaseModule DI::notify()->save($Notify); } else { if ($Notify->uriId) { - DI::dba()->update('notification', ['seen' => true], ['uid' => $Notify->uid, 'target-uri-id' => $Notify->uriId]); + DI::notification()->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]); } DI::notify()->setAllSeenForRelatedNotify($Notify); diff --git a/src/Module/Notifications/Notifications.php b/src/Module/Notifications/Notifications.php index 79f1596f95..af8b14512b 100644 --- a/src/Module/Notifications/Notifications.php +++ b/src/Module/Notifications/Notifications.php @@ -95,25 +95,24 @@ class Notifications extends BaseNotifications $notificationHeader = $notificationResult['header'] ?? ''; if (!empty($notifications['notifications'])) { + $notificationTemplates = [ + 'like' => 'notifications/likes_item.tpl', + 'dislike' => 'notifications/dislikes_item.tpl', + 'attend' => 'notifications/attend_item.tpl', + 'attendno' => 'notifications/attend_item.tpl', + 'attendmaybe' => 'notifications/attend_item.tpl', + 'friend' => 'notifications/friends_item.tpl', + 'comment' => 'notifications/comments_item.tpl', + 'post' => 'notifications/posts_item.tpl', + 'notification' => 'notifications/notification.tpl', + ]; // Loop trough ever notification This creates an array with the output html for each // notification and apply the correct template according to the notificationtype (label). - /** @var FormattedNotification $notification */ - foreach ($notifications['notifications'] as $notification) { - $notification_templates = [ - 'like' => 'notifications/likes_item.tpl', - 'dislike' => 'notifications/dislikes_item.tpl', - 'attend' => 'notifications/attend_item.tpl', - 'attendno' => 'notifications/attend_item.tpl', - 'attendmaybe' => 'notifications/attend_item.tpl', - 'friend' => 'notifications/friends_item.tpl', - 'comment' => 'notifications/comments_item.tpl', - 'post' => 'notifications/posts_item.tpl', - 'notification' => 'notifications/notification.tpl', - ]; + /** @var FormattedNotification $Notification */ + foreach ($notifications['notifications'] as $Notification) { + $notificationArray = $Notification->toArray(); - $notificationArray = $notification->toArray(); - - $notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notificationArray['label']]); + $notificationTemplate = Renderer::getMarkupTemplate($notificationTemplates[$notificationArray['label']]); $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [ '$notification' => $notificationArray diff --git a/src/Object/Api/Mastodon/Notification.php b/src/Object/Api/Mastodon/Notification.php index d660baa083..d912dc894d 100644 --- a/src/Object/Api/Mastodon/Notification.php +++ b/src/Object/Api/Mastodon/Notification.php @@ -66,11 +66,11 @@ class Notification extends BaseDataTransferObject * * @throws HttpException\InternalServerErrorException|Exception */ - public function __construct(int $id, string $type, string $created_at, Account $account = null, Status $status = null) + public function __construct(int $id, string $type, \DateTime $created_at, Account $account = null, Status $status = null) { $this->id = (string)$id; $this->type = $type; - $this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::JSON); + $this->created_at = $created_at->format(DateTimeFormat::JSON); $this->account = $account->toArray(); if (!empty($status)) { diff --git a/src/Worker/PushSubscription.php b/src/Worker/PushSubscription.php index eaf2adb32d..ae34abc877 100644 --- a/src/Worker/PushSubscription.php +++ b/src/Worker/PushSubscription.php @@ -27,10 +27,11 @@ use Friendica\Core\Logger; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; -use Friendica\Model\Notification; use Friendica\Model\Post; use Friendica\Model\Subscription as ModelSubscription; use Friendica\Model\User; +use Friendica\Navigation\Notifications; +use Friendica\Network\HTTPException\NotFoundException; use Minishlink\WebPush\WebPush; use Minishlink\WebPush\Subscription; @@ -46,8 +47,9 @@ class PushSubscription return; } - $notification = DBA::selectFirst('notification', [], ['id' => $nid]); - if (empty($notification)) { + try { + $Notification = DI::notification()->selectOneById($nid); + } catch (NotFoundException $e) { Logger::info('Notification not found', ['notification' => $nid]); return; } @@ -58,7 +60,7 @@ class PushSubscription return; } - $user = User::getById($notification['uid']); + $user = User::getById($Notification->uid); if (empty($user)) { Logger::info('User not found', ['application' => $subscription['uid']]); return; @@ -66,23 +68,21 @@ class PushSubscription $l10n = DI::l10n()->withLang($user['language']); - $type = Notification::getType($notification); - - if (!empty($notification['actor-id'])) { - $actor = Contact::getById($notification['actor-id']); + if ($Notification->actorId) { + $actor = Contact::getById($Notification->actorId); } $body = ''; - if (!empty($notification['target-uri-id'])) { - $post = Post::selectFirst([], ['uri-id' => $notification['target-uri-id'], 'uid' => [0, $notification['uid']]]); + if ($Notification->targetUriId) { + $post = Post::selectFirst([], ['uri-id' => $Notification->targetUriId, 'uid' => [0, $Notification->uid]]); if (!empty($post['body'])) { $body = BBCode::toPlaintext($post['body'], false); - $body = Plaintext::shorten($body, 160, $notification['uid']); + $body = Plaintext::shorten($body, 160, $Notification->uid); } } - $message = Notification::getMessage($notification); + $message = (new Notifications\Factory\Notification(DI::logger()))->getMessageFromNotification($Notification, DI::baseUrl(), $l10n); $title = $message['plain'] ?: ''; $push = Subscription::create([ @@ -98,7 +98,7 @@ class PushSubscription 'access_token' => $application_token['access_token'], 'preferred_locale' => $user['language'], 'notification_id' => $nid, - 'notification_type' => $type, + 'notification_type' => \Friendica\Factory\Api\Mastodon\Notification::getType($Notification), 'icon' => $actor['thumb'] ?? '', 'title' => $title ?: $l10n->t('Notification from Friendica'), 'body' => $body ?: $l10n->t('Empty Post'),