diff --git a/include/api.php b/include/api.php index 90a7bc2d90..87d619ac9e 100644 --- a/include/api.php +++ b/include/api.php @@ -24,15 +24,14 @@ */ use Friendica\App; +use Friendica\Collection\Api\Notifications as ApiNotifications; use Friendica\Content\ContactSelector; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\Protocol; -use Friendica\Core\Session; use Friendica\Core\System; -use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; @@ -54,6 +53,7 @@ use Friendica\Network\HTTPException\MethodNotAllowedException; use Friendica\Network\HTTPException\NotFoundException; use Friendica\Network\HTTPException\TooManyRequestsException; use Friendica\Network\HTTPException\UnauthorizedException; +use Friendica\Object\Api\Friendica\Notification as ApiNotification; use Friendica\Object\Image; use Friendica\Protocol\Activity; use Friendica\Protocol\Diaspora; @@ -2193,11 +2193,14 @@ function api_statuses_mentions($type) (SELECT `uri-id` FROM `post-user-notification` WHERE `uid` = ? AND `notification-type` & ? != 0 ORDER BY `uri-id`) AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND `id` > ?"; - $condition = [GRAVITY_PARENT, GRAVITY_COMMENT, api_user(), - Post\UserNotification::NOTIF_EXPLICIT_TAGGED | Post\UserNotification::NOTIF_IMPLICIT_TAGGED | - Post\UserNotification::NOTIF_THREAD_COMMENT | Post\UserNotification::NOTIF_DIRECT_COMMENT | - Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT, - api_user(), $since_id]; + $condition = [ + GRAVITY_PARENT, GRAVITY_COMMENT, + api_user(), + Post\UserNotification::TYPE_EXPLICIT_TAGGED | Post\UserNotification::TYPE_IMPLICIT_TAGGED | + Post\UserNotification::TYPE_THREAD_COMMENT | Post\UserNotification::TYPE_DIRECT_COMMENT | + Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT, + api_user(), $since_id, + ]; if ($max_id > 0) { $query .= " AND `id` <= ?"; @@ -5528,23 +5531,24 @@ api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activit */ function api_friendica_notification($type) { - $a = DI::app(); - if (api_user() === false) { throw new ForbiddenException(); } if (DI::args()->getArgc()!==3) { - throw new BadRequestException("Invalid argument count"); + throw new BadRequestException('Invalid argument count'); } - $notifications = DI::notification()->getApiList(local_user()); + $Notifies = DI::notify()->selectAllForUser(local_user(), 50); - if ($type == "xml") { - $xmlnotes = false; - if (!empty($notifications)) { - foreach ($notifications as $notification) { - $xmlnotes[] = ["@attributes" => $notification->toArray()]; - } + $notifications = new ApiNotifications(); + foreach ($Notifies as $Notify) { + $notifications[] = new ApiNotification($Notify); + } + + if ($type == 'xml') { + $xmlnotes = []; + foreach ($notifications as $notification) { + $xmlnotes[] = ['@attributes' => $notification->toArray()]; } $result = $xmlnotes; @@ -5554,7 +5558,7 @@ function api_friendica_notification($type) $result = false; } - return api_format_data("notes", $type, ['note' => $result]); + return api_format_data('notes', $type, ['note' => $result]); } /** @@ -5579,26 +5583,36 @@ function api_friendica_notification_seen($type) throw new ForbiddenException(); } if (DI::args()->getArgc() !== 4) { - throw new BadRequestException("Invalid argument count"); + throw new BadRequestException('Invalid argument count'); } - $id = (!empty($_REQUEST['id']) ? intval($_REQUEST['id']) : 0); + $id = intval($_REQUEST['id'] ?? 0); try { - $notify = DI::notify()->getByID($id, api_user()); - DI::notify()->setSeen(true, $notify); + $Notify = DI::notify()->selectOneById($id); + if ($Notify->uid !== api_user()) { + throw new NotFoundException(); + } - if ($notify->otype === Notification\ObjectType::ITEM) { - $item = Post::selectFirstForUser(api_user(), [], ['id' => $notify->iid, 'uid' => api_user()]); + if ($Notify->uriId) { + DI::notification()->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]); + } + + $Notify->setSeen(); + DI::notify()->save($Notify); + + if ($Notify->otype === Notification\ObjectType::ITEM) { + $item = Post::selectFirstForUser(api_user(), [], ['id' => $Notify->iid, 'uid' => api_user()]); if (DBA::isResult($item)) { // we found the item, return it to the user $ret = api_format_items([$item], $user_info, false, $type); $data = ['status' => $ret]; - return api_format_data("status", $type, $data); + return api_format_data('status', $type, $data); } // the item can't be found, but we set the notification as seen, so we count this as a success } - return api_format_data('result', $type, ['result' => "success"]); + + return api_format_data('result', $type, ['result' => 'success']); } catch (NotFoundException $e) { throw new BadRequestException('Invalid argument', $e); } catch (Exception $e) { diff --git a/include/enotify.php b/include/enotify.php index 84b46edc02..c30b6de86e 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -19,11 +19,9 @@ * */ -use Friendica\Content\Text\BBCode; use Friendica\Content\Text\Plaintext; use Friendica\Core\Hook; use Friendica\Core\Logger; -use Friendica\Core\Renderer; use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; @@ -32,7 +30,7 @@ use Friendica\Model\Item; use Friendica\Model\Notification; use Friendica\Model\Post; use Friendica\Model\User; -use Friendica\Model\Verb; +use Friendica\Navigation\Notifications; use Friendica\Protocol\Activity; /** @@ -397,42 +395,19 @@ function notification_store_and_send($params, $sitelink, $tsitelink, $hsitelink, $notify_id = 0; if ($show_in_notification_page) { - $fields = [ - 'name' => $params['source_name'] ?? '', - 'name_cache' => substr(strip_tags(BBCode::convertForUriId($uri_id, $params['source_name'])), 0, 255), - 'url' => $params['source_link'] ?? '', - 'photo' => $params['source_photo'] ?? '', - 'link' => $itemlink ?? '', - 'uid' => $params['uid'] ?? 0, - 'type' => $params['type'] ?? '', - 'verb' => $params['verb'] ?? '', - 'otype' => $params['otype'] ?? '', - ]; - if (!empty($item_id)) { - $fields['iid'] = $item_id; - } - if (!empty($uri_id)) { - $fields['uri-id'] = $uri_id; - } - if (!empty($parent_id)) { - $fields['parent'] = $parent_id; - } - if (!empty($parent_uri_id)) { - $fields['parent-uri-id'] = $parent_uri_id; - } - $notification = DI::notify()->insert($fields); - - // Notification insertion can be intercepted by an addon registering the 'enotify_store' hook - if (!$notification) { + $Notify = DI::notifyFactory()->createFromParams($params, $itemlink, $item_id, $uri_id, $parent_id, $parent_uri_id); + try { + $Notify = DI::notify()->save($Notify); + } catch (Notifications\Exception\NotificationCreationInterceptedException $e) { + // Notification insertion can be intercepted by an addon registering the 'enotify_store' hook return false; } - $notification->msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $notification->link]); + $Notify->updateMsgFromPreamble($epreamble); + $Notify = DI::notify()->save($Notify); - DI::notify()->update($notification); - - $itemlink = DI::baseUrl() . '/notification/' . $notification->id; - $notify_id = $notification->id; + $itemlink = DI::baseUrl() . '/notification/' . $Notify->id; + $notify_id = $Notify->id; } // send email notification if notification preferences permit @@ -512,22 +487,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::NOTIF_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']; @@ -537,18 +511,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; } @@ -558,17 +532,17 @@ 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; } // Check to see if there was already a tag notify or comment notify for this post. // 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, 'uid' => $notification['uid']]; - if (DBA::exists('notify', $condition)) { - Logger::info('Duplicate found, quitting', $condition); + 'link' => $params['link'], 'verb' => Activity::POST]; + if (DI::notify()->existsForUser($Notification->uid, $condition)) { + Logger::info('Duplicate found, quitting', $condition + ['uid' => $Notification->uid]); return false; } @@ -583,10 +557,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::NOTIF_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::NOTIF_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 { @@ -594,9 +568,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 = DI::notificationFactory()->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; } @@ -611,7 +585,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 87775126e6..a349a552c3 100644 --- a/mod/display.php +++ b/mod/display.php @@ -222,8 +222,8 @@ 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()]); - DBA::update('notify', ['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']]); } // We are displaying an "alternate" link if that post was public. See issue 2864 diff --git a/mod/ping.php b/mod/ping.php index 69aef1bf3f..2036b8ec41 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -445,7 +445,7 @@ function ping_get_notifications($uid) $notification["message"] = $notification["msg_cache"]; } else { $notification["name"] = strip_tags(BBCode::convert($notification["name"])); - $notification["message"] = Notification::formatMessage($notification["name"], strip_tags(BBCode::convert($notification["msg"]))); + $notification["message"] = \Friendica\Navigation\Notifications\Entity\Notify::formatMessage($notification["name"], BBCode::toPlaintext($notification["msg"])); q( "UPDATE `notify` SET `name_cache` = '%s', `msg_cache` = '%s' WHERE `id` = %d", diff --git a/src/BaseCollection.php b/src/BaseCollection.php index c4d637a367..1aa13ae961 100644 --- a/src/BaseCollection.php +++ b/src/BaseCollection.php @@ -22,12 +22,11 @@ namespace Friendica; /** - * The Collection classes inheriting from this abstract class are meant to represent a list of database record. - * The associated model class has to be provided in the child classes. + * The Collection classes inheriting from this class are meant to represent a list of structured objects of a single type. * * Collections can be used with foreach(), accessed like an array and counted. */ -abstract class BaseCollection extends \ArrayIterator +class BaseCollection extends \ArrayIterator { /** * This property is used with paginated results to hold the total number of items satisfying the paginated request. @@ -115,4 +114,14 @@ abstract class BaseCollection extends \ArrayIterator { return new static(array_filter($this->getArrayCopy(), $callback, $flag)); } + + /** + * Reverse the orders of the elements in the collection + * + * @return $this + */ + public function reverse(): BaseCollection + { + return new static(array_reverse($this->getArrayCopy()), $this->getTotalCount()); + } } diff --git a/src/BaseDepository.php b/src/BaseDepository.php new file mode 100644 index 0000000000..18cca9d30e --- /dev/null +++ b/src/BaseDepository.php @@ -0,0 +1,163 @@ +db = $database; + $this->logger = $logger; + $this->factory = $factory; + } + + /** + * Populates the collection according to the condition. Retrieves a limited subset of entities depending on the + * boundaries and the limit. The total count of rows matching the condition is stored in the collection. + * + * Depends on the corresponding table featuring a numerical auto incremented column called `id`. + * + * max_id and min_id are susceptible to the query order: + * - min_id alone only reliably works with ASC order + * - max_id alone only reliably works with DESC order + * If the wrong order is detected in either case, we reverse the query order and the entity list order after the query + * + * Chainable. + * + * @param array $condition + * @param array $params + * @param int|null $min_id Retrieve models with an id no fewer than this, as close to it as possible + * @param int|null $max_id Retrieve models with an id no greater than this, as close to it as possible + * @param int $limit + * @return BaseCollection + * @throws \Exception + */ + protected function _selectByBoundaries( + array $condition = [], + array $params = [], + int $min_id = null, + int $max_id = null, + int $limit = self::LIMIT + ): BaseCollection { + $totalCount = $this->count($condition); + + $boundCondition = $condition; + + $reverseOrder = false; + + if (isset($min_id)) { + $boundCondition = DBA::mergeConditions($boundCondition, ['`id` > ?', $min_id]); + if (!isset($max_id) && isset($params['order']['id']) && ($params['order']['id'] === true || $params['order']['id'] === 'DESC')) { + $reverseOrder = true; + + $params['order']['id'] = 'ASC'; + } + } + + if (isset($max_id)) { + $boundCondition = DBA::mergeConditions($boundCondition, ['`id` < ?', $max_id]); + if (!isset($min_id) && (!isset($params['order']['id']) || $params['order']['id'] === false || $params['order']['id'] === 'ASC')) { + $reverseOrder = true; + + $params['order']['id'] = 'DESC'; + } + } + + $params['limit'] = $limit; + + $Entities = $this->_select($boundCondition, $params); + if ($reverseOrder) { + $Entities->reverse(); + } + + return new BaseCollection($Entities->getArrayCopy(), $totalCount); + } + + /** + * @param array $condition + * @param array $params + * @return BaseCollection + * @throws Exception + */ + protected function _select(array $condition, array $params = []): BaseCollection + { + $rows = $this->db->selectToArray(static::$table_name, [], $condition, $params); + + $Entities = new BaseCollection(); + foreach ($rows as $fields) { + $Entities[] = $this->factory->createFromTableRow($fields); + } + + return $Entities; + } + + /** + * @param array $condition + * @param array $params + * @return BaseEntity + * @throws NotFoundException + */ + protected function _selectOne(array $condition, array $params = []): BaseEntity + { + $fields = $this->db->selectFirst(static::$table_name, [], $condition, $params); + if (!$this->db->isResult($fields)) { + throw new NotFoundException(); + } + + return $this->factory->createFromTableRow($fields); + } + + /** + * @param array $condition + * @param array $params + * @return int + * @throws Exception + */ + public function count(array $condition, array $params = []): int + { + return $this->db->count(static::$table_name, $condition, $params); + } + + /** + * @param array $condition + * @return bool + * @throws Exception + */ + public function exists(array $condition): bool + { + return $this->db->exists(static::$table_name, $condition); + } +} diff --git a/src/Capabilities/ICanCreateFromTableRow.php b/src/Capabilities/ICanCreateFromTableRow.php new file mode 100644 index 0000000000..bdb6d662da --- /dev/null +++ b/src/Capabilities/ICanCreateFromTableRow.php @@ -0,0 +1,16 @@ +create(Factory\Api\Twitter\User::class); } - /** - * @return Factory\Notification\Notification - */ - public static function notification() + public static function notificationIntro(): Navigation\Notifications\Factory\Introduction { - return self::$dice->create(Factory\Notification\Notification::class); - } - - /** - * @return Factory\Notification\Introduction - */ - public static function notificationIntro() - { - return self::$dice->create(Factory\Notification\Introduction::class); + return self::$dice->create(Navigation\Notifications\Factory\Introduction::class); } // @@ -469,12 +458,29 @@ abstract class DI return self::$dice->create(Repository\ProfileField::class); } - /** - * @return Repository\Notification - */ - public static function notify() + public static function notification(): Navigation\Notifications\Depository\Notification { - return self::$dice->create(Repository\Notification::class); + return self::$dice->create(Navigation\Notifications\Depository\Notification::class); + } + + public static function notificationFactory(): Navigation\Notifications\Factory\Notification + { + return self::$dice->create(Navigation\Notifications\Factory\Notification::class); + } + + public static function notify(): Navigation\Notifications\Depository\Notify + { + return self::$dice->create(Navigation\Notifications\Depository\Notify::class); + } + + public static function notifyFactory(): Navigation\Notifications\Factory\Notify + { + return self::$dice->create(Navigation\Notifications\Factory\Notify::class); + } + + public static function formattedNotificationFactory(): Navigation\Notifications\Factory\FormattedNotification + { + return self::$dice->create(Navigation\Notifications\Factory\FormattedNotification::class); } // diff --git a/src/Database/Database.php b/src/Database/Database.php index f94e03f92a..fe137b39be 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -909,7 +909,7 @@ class Database /** * Fetch a single row * - * @param PDOStatement|mysqli_stmt $stmt statement object + * @param bool|PDOStatement|mysqli_stmt $stmt statement object * * @return array|false current row */ diff --git a/src/Factory/Api/Mastodon/Notification.php b/src/Factory/Api/Mastodon/Notification.php index 82ef67d618..bdc5717f89 100644 --- a/src/Factory/Api/Mastodon/Notification.php +++ b/src/Factory/Api/Mastodon/Notification.php @@ -22,53 +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; - } - /* - follow = Someone followed you - follow_request = Someone requested to follow you - mention = Someone mentioned you in their status - reblog = Someone boosted one of your statuses - favourite = Someone favourited one of your statuses - poll = A poll you have voted in or created has ended - status = Someone you enabled notifications for has posted a status - */ - - $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; } @@ -76,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/Factory/Notification/Notification.php b/src/Factory/Notification/Notification.php deleted file mode 100644 index 9f833130aa..0000000000 --- a/src/Factory/Notification/Notification.php +++ /dev/null @@ -1,392 +0,0 @@ -. - * - */ - -namespace Friendica\Factory\Notification; - -use Exception; -use Friendica\App; -use Friendica\App\BaseURL; -use Friendica\BaseFactory; -use Friendica\Collection\Api\Notifications as ApiNotifications; -use Friendica\Content\Text\BBCode; -use Friendica\Core\L10n; -use Friendica\Core\PConfig\IPConfig; -use Friendica\Core\Protocol; -use Friendica\Core\Session\ISession; -use Friendica\Database\Database; -use Friendica\Model\Contact; -use Friendica\Model\Post; -use Friendica\Module\BaseNotifications; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Object\Api\Friendica\Notification as ApiNotification; -use Friendica\Protocol\Activity; -use Friendica\Repository; -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 Notification extends BaseFactory -{ - /** @var Database */ - private $dba; - /** @var Repository\Notification */ - private $notification; - /** @var BaseURL */ - private $baseUrl; - /** @var L10n */ - private $l10n; - - public function __construct(LoggerInterface $logger, Database $dba, Repository\Notification $notification, BaseURL $baseUrl, L10n $l10n, App $app, IPConfig $pConfig, ISession $session) - { - parent::__construct($logger); - - $this->dba = $dba; - $this->notification = $notification; - $this->baseUrl = $baseUrl; - $this->l10n = $l10n; - } - - /** - * Format the item query in an 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) - { - $item['seen'] = ($item['unseen'] > 0 ? false : true); - - // 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['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'])); - $item['when'] = DateTimeFormat::local($item['created'], 'r'); - $item['ago'] = Temporal::getRelativeDate($item['created']); - - return $item; - } - - /** - * @param array $item - * - * @return \Friendica\Object\Notification\Notification - * - * @throws InternalServerErrorException - */ - private function createFromItem(array $item) - { - $item = $this->formatItem($item); - - // Transform the different types of notification in an usable array - switch ($item['verb'] ?? '') { - case Activity::LIKE: - return new \Friendica\Object\Notification\Notification([ - 'label' => 'like', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s liked %s's post", $item['author-name'], $item['parent-author-name']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - case Activity::DISLIKE: - return new \Friendica\Object\Notification\Notification([ - 'label' => 'dislike', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s disliked %s's post", $item['author-name'], $item['parent-author-name']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - case Activity::ATTEND: - return new \Friendica\Object\Notification\Notification([ - 'label' => 'attend', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s is attending %s's event", $item['author-name'], $item['parent-author-name']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - case Activity::ATTENDNO: - return new \Friendica\Object\Notification\Notification([ - 'label' => 'attendno', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s is not attending %s's event", $item['author-name'], $item['parent-author-name']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - case Activity::ATTENDMAYBE: - return new \Friendica\Object\Notification\Notification([ - 'label' => 'attendmaybe', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s may attending %s's event", $item['author-name'], $item['parent-author-name']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - case Activity::FRIEND: - if (!isset($item['object'])) { - return new \Friendica\Object\Notification\Notification([ - 'label' => 'friend', - 'link' => $item['link'], - 'image' => $item['image'], - 'url' => $item['url'], - 'text' => $item['text'], - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - } - - $xmlHead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">"; - $obj = XML::parseString($xmlHead . $item['object']); - $item['fname'] = $obj->title; - - return new \Friendica\Object\Notification\Notification([ - 'label' => 'friend', - 'link' => $this->baseUrl->get(true) . '/display/' . $item['parent-guid'], - 'image' => $item['author-avatar'], - 'url' => $item['author-link'], - 'text' => $this->l10n->t("%s is now friends with %s", $item['author-name'], $item['fname']), - 'when' => $item['when'], - 'ago' => $item['ago'], - 'seen' => $item['seen']]); - - default: - return new \Friendica\Object\Notification\Notification($item); - break; - } - } - - /** - * 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 \Friendica\Module\Notifications\Notification[] - */ - public function getSystemList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT) - { - $conditions = ['uid' => local_user()]; - - if (!$seen) { - $conditions['seen'] = false; - } - - $params = []; - $params['order'] = ['date' => 'DESC']; - $params['limit'] = [$start, $limit]; - - $formattedNotifications = []; - try { - $notifications = $this->notification->select($conditions, $params); - - foreach ($notifications as $notification) { - $formattedNotifications[] = new \Friendica\Object\Notification\Notification([ - 'label' => 'notification', - 'link' => $this->baseUrl->get(true) . '/notification/' . $notification->id, - 'image' => Contact::getAvatarUrlForUrl($notification->url, $notification->uid, Proxy::SIZE_MICRO), - 'url' => $notification->url, - 'text' => strip_tags(BBCode::convert($notification->msg)), - 'when' => DateTimeFormat::local($notification->date, 'r'), - 'ago' => Temporal::getRelativeDate($notification->date), - 'seen' => $notification->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 \Friendica\Object\Notification\Notification[] - */ - public function getNetworkList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT) - { - $conditions = ['wall' => false, 'uid' => local_user()]; - - if (!$seen) { - $conditions['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 = []; - - try { - $items = Post::selectForUser(local_user(), $fields, $conditions, $params); - - while ($item = $this->dba->fetch($items)) { - $formattedNotifications[] = $this->createFromItem($item); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['conditions' => $conditions, '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 \Friendica\Object\Notification\Notification[] - */ - public function getPersonalList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT) - { - $condition = ["NOT `wall` AND `uid` = ? AND `author-id` = ?", local_user(), public_contact()]; - - if (!$seen) { - $condition[0] .= " AND `unseen`"; - } - - $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 = []; - - try { - $items = Post::selectForUser(local_user(), $fields, $condition, $params); - - while ($item = $this->dba->fetch($items)) { - $formattedNotifications[] = $this->createFromItem($item); - } - } 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 \Friendica\Object\Notification\Notification[] - */ - public function getHomeList(bool $seen = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT) - { - $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 = []; - - try { - $items = Post::selectForUser(local_user(), $fields, $condition, $params); - - while ($item = $this->dba->fetch($items)) { - $item = $this->formatItem($item); - - // Overwrite specific fields, not default item format - $item['label'] = 'comment'; - $item['text'] = $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']); - - $formattedNotifications[] = $this->createFromItem($item); - } - } catch (Exception $e) { - $this->logger->warning('Select failed.', ['conditions' => $condition, 'exception' => $e]); - } - - return $formattedNotifications; - } - - /** - * @param int $uid The user id of the API call - * @param array $params Additional parameters - * - * @return ApiNotifications - * - * @throws Exception - */ - public function getApiList(int $uid, array $params = ['order' => ['seen' => 'ASC', 'date' => 'DESC'], 'limit' => 50]) - { - $notifies = $this->notification->select(['uid' => $uid], $params); - - /** @var ApiNotification[] $notifications */ - $notifications = []; - - foreach ($notifies as $notify) { - $notifications[] = new ApiNotification($notify); - } - - return new ApiNotifications($notifications); - } -} diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 72145c17bd..adf1b1d2c0 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::insertNotication($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::insertNotication($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 1df420845c..0000000000 --- a/src/Model/Notification.php +++ /dev/null @@ -1,353 +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\Protocol\Activity; -use Psr\Log\LoggerInterface; - -/** - * Model for an entry in the notify table - * - * @property string hash - * @property integer type - * @property string name Full name of the contact subject - * @property string url Profile page URL of the contact subject - * @property string photo Profile photo URL of the contact subject - * @property string date YYYY-MM-DD hh:mm:ss local server time - * @property string msg - * @property integer uid Owner User Id - * @property string link Notification URL - * @property integer iid Item Id - * @property integer parent Parent Item Id - * @property boolean seen Whether the notification was read or not. - * @property string verb Verb URL (@see http://activitystrea.ms) - * @property string otype Subject type ('item', 'intro' or 'mail') - * - * @property-read string name_cache Full name of the contact subject - * @property-read string msg_cache Plaintext version of the notification text with a placeholder (`{0}`) for the subject contact's name. - */ -class Notification extends BaseModel -{ - /** @var \Friendica\Repository\Notification */ - private $repo; - - public function __construct(Database $dba, LoggerInterface $logger, \Friendica\Repository\Notification $repo, array $data = []) - { - parent::__construct($dba, $logger, $data); - - $this->repo = $repo; - - $this->setNameCache(); - $this->setMsgCache(); - } - - /** - * Sets the pre-formatted name (caching) - */ - private function setNameCache() - { - try { - $this->name_cache = strip_tags(BBCode::convert($this->source_name)); - } catch (InternalServerErrorException $e) { - } - } - - /** - * Sets the pre-formatted msg (caching) - */ - private function setMsgCache() - { - try { - $this->msg_cache = self::formatMessage($this->name_cache, strip_tags(BBCode::convert($this->msg))); - } catch (InternalServerErrorException $e) { - } - } - - public function __set($name, $value) - { - parent::__set($name, $value); - - if ($name == 'msg') { - $this->setMsgCache(); - } - - if ($name == 'source_name') { - $this->setNameCache(); - } - } - - /** - * Formats a notification message with the notification author - * - * Replace the name with {0} but ensure to make that only once. The {0} is used - * later and prints the name in bold. - * - * @param string $name - * @param string $message - * - * @return string Formatted message - */ - public static function formatMessage($name, $message) - { - if ($name != '') { - $pos = strpos($message, $name); - } else { - $pos = false; - } - - if ($pos !== false) { - $message = substr_replace($message, '{0}', $pos, strlen($name)); - } - - return $message; - } - - /** - * 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::NOTIF_NONE)) { - $contact = Contact::getById($notification['actor-id'], ['pending']); - $type = $contact['pending'] ? 'follow_request' : 'follow'; - } elseif (($notification['vid'] == Verb::getID(Activity::ANNOUNCE)) && - in_array($notification['type'], [Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT])) { - $type = 'reblog'; - } elseif (in_array($notification['vid'], [Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE)]) && - in_array($notification['type'], [Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT])) { - $type = 'favourite'; - } elseif ($notification['type'] == Post\UserNotification::NOTIF_SHARED) { - $type = 'status'; - } elseif (in_array($notification['type'], [Post\UserNotification::NOTIF_EXPLICIT_TAGGED, - Post\UserNotification::NOTIF_IMPLICIT_TAGGED, Post\UserNotification::NOTIF_DIRECT_COMMENT, - Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT])) { - $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::NOTIF_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::NOTIF_THREAD_COMMENT, Post\UserNotification::NOTIF_COMMENT_PARTICIPATION, Post\UserNotification::NOTIF_ACTIVITY_PARTICIPATION, Post\UserNotification::NOTIF_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::NOTIF_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::NOTIF_COMMENT_PARTICIPATION, Post\UserNotification::NOTIF_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::NOTIF_DIRECT_COMMENT: - $msg = $l10n->t('%1$s liked your comment %2$s'); - break; - case Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s liked your post %2$s'); - break; - } - break; - case $dislike: - switch ($notification['type']) { - case Post\UserNotification::NOTIF_DIRECT_COMMENT: - $msg = $l10n->t('%1$s disliked your comment %2$s'); - break; - case Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s disliked your post %2$s'); - break; - } - break; - case $announce: - switch ($notification['type']) { - case Post\UserNotification::NOTIF_DIRECT_COMMENT: - $msg = $l10n->t('%1$s shared your comment %2$s'); - break; - case Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s shared your post %2$s'); - break; - } - break; - case $post: - switch ($notification['type']) { - case Post\UserNotification::NOTIF_EXPLICIT_TAGGED: - $msg = $l10n->t('%1$s tagged you on %2$s'); - break; - - case Post\UserNotification::NOTIF_IMPLICIT_TAGGED: - $msg = $l10n->t('%1$s replied to you on %2$s'); - break; - - case Post\UserNotification::NOTIF_THREAD_COMMENT: - $msg = $l10n->t('%1$s commented in your thread %2$s'); - break; - - case Post\UserNotification::NOTIF_DIRECT_COMMENT: - $msg = $l10n->t('%1$s commented on your comment %2$s'); - break; - - case Post\UserNotification::NOTIF_COMMENT_PARTICIPATION: - case Post\UserNotification::NOTIF_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::NOTIF_DIRECT_THREAD_COMMENT: - $msg = $l10n->t('%1$s commented on your thread %2$s'); - break; - - case Post\UserNotification::NOTIF_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 f4bcb3e402..d14eb7f3be 100644 --- a/src/Model/Post/UserNotification.php +++ b/src/Model/Post/UserNotification.php @@ -21,9 +21,10 @@ namespace Friendica\Model\Post; -use \BadMethodCallException; -use Friendica\Core\Logger; +use BadMethodCallException; +use Exception; use Friendica\Core\Hook; +use Friendica\Core\Logger; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\Database\DBStructure; @@ -31,35 +32,35 @@ use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Post; use Friendica\Model\Subscription; -use Friendica\Util\Strings; 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 { // Notification types - const NOTIF_NONE = 0; - const NOTIF_EXPLICIT_TAGGED = 1; - const NOTIF_IMPLICIT_TAGGED = 2; - const NOTIF_THREAD_COMMENT = 4; - const NOTIF_DIRECT_COMMENT = 8; - const NOTIF_COMMENT_PARTICIPATION = 16; - const NOTIF_ACTIVITY_PARTICIPATION = 32; - const NOTIF_DIRECT_THREAD_COMMENT = 64; - const NOTIF_SHARED = 128; - + const TYPE_NONE = 0; + const TYPE_EXPLICIT_TAGGED = 1; + const TYPE_IMPLICIT_TAGGED = 2; + const TYPE_THREAD_COMMENT = 4; + const TYPE_DIRECT_COMMENT = 8; + const TYPE_COMMENT_PARTICIPATION = 16; + const TYPE_ACTIVITY_PARTICIPATION = 32; + const TYPE_DIRECT_THREAD_COMMENT = 64; + const TYPE_SHARED = 128; /** * Insert a new user notification entry * * @param integer $uri_id * @param integer $uid - * @param array $fields + * @param array $data * @return bool success - * @throws \Exception + * @throws Exception */ - public static function insert(int $uri_id, int $uid, array $data = []) + public static function insert(int $uri_id, int $uid, array $data = []): bool { if (empty($uri_id)) { throw new BadMethodCallException('Empty URI_id'); @@ -67,9 +68,8 @@ class UserNotification $fields = DBStructure::getFieldsForTable('post-user-notification', $data); - // Additionally assign the key fields $fields['uri-id'] = $uri_id; - $fields['uid'] = $uid; + $fields['uid'] = $uid; return DBA::insert('post-user-notification', $fields, Database::INSERT_IGNORE); } @@ -82,9 +82,9 @@ class UserNotification * @param array $data * @param bool $insert_if_missing * @return bool - * @throws \Exception + * @throws Exception */ - public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false) + public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false): bool { if (empty($uri_id)) { throw new BadMethodCallException('Empty URI_id'); @@ -106,32 +106,33 @@ class UserNotification /** * Delete a row from the post-user-notification table * - * @param array $conditions Field condition(s) - * @param array $options - * - cascade: If true we delete records in other tables that depend on the one we're deleting through + * @param array $conditions Field condition(s) + * @param array $options - cascade: If true we delete records in other tables that depend on the one we're deleting through * relations (default: true) * - * @return boolean was the delete successful? - * @throws \Exception + * @return boolean was the deletion successful? + * @throws Exception */ - public static function delete(array $conditions, array $options = []) + public static function delete(array $conditions, array $options = []): bool { return DBA::delete('post-user-notification', $conditions, $options); } /** * Checks an item for notifications and sets the "notification-type" field + * * @ToDo: * - Check for mentions in posts with "uid=0" where the user hadn't interacted before * * @param int $uri_id URI ID * @param int $uid user ID + * @throws Exception */ public static function setNotification(int $uri_id, int $uid) { $fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity', 'vid', 'gravity', - 'private', 'contact-id', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'author-id', 'verb']; - $item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]); + 'private', 'contact-id', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'author-id', 'verb']; + $item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]); if (!DBA::isResult($item)) { return; } @@ -167,6 +168,7 @@ class UserNotification * * @param array $item Item array * @param int $uid User ID + * @throws HTTPException\InternalServerErrorException */ private static function setNotificationForUser(array $item, int $uid) { @@ -174,11 +176,11 @@ class UserNotification return; } - $notification_type = self::NOTIF_NONE; + $notification_type = self::TYPE_NONE; if (self::checkShared($item, $uid)) { - $notification_type = $notification_type | self::NOTIF_SHARED; - self::insertNoticationByItem(self::NOTIF_SHARED, $uid, $item); + $notification_type = $notification_type | self::TYPE_SHARED; + self::insertNotificationByItem(self::TYPE_SHARED, $uid, $item); $notified = true; } else { $notified = false; @@ -188,6 +190,7 @@ class UserNotification // Fetch all contacts for the given profiles $contacts = []; + $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); while ($contact = DBA::fetch($ret)) { $contacts[] = $contact['id']; @@ -200,58 +203,57 @@ class UserNotification } if (self::checkExplicitMention($item, $profiles)) { - $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; + $notification_type = $notification_type | self::TYPE_EXPLICIT_TAGGED; if (!$notified) { - self::insertNoticationByItem( self::NOTIF_EXPLICIT_TAGGED, $uid, $item); + self::insertNotificationByItem(self::TYPE_EXPLICIT_TAGGED, $uid, $item); $notified = true; } } if (self::checkImplicitMention($item, $profiles)) { - $notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED; + $notification_type = $notification_type | self::TYPE_IMPLICIT_TAGGED; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_IMPLICIT_TAGGED, $uid, $item); + self::insertNotificationByItem(self::TYPE_IMPLICIT_TAGGED, $uid, $item); $notified = true; } } if (self::checkDirectComment($item, $contacts)) { - $notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT; + $notification_type = $notification_type | self::TYPE_DIRECT_COMMENT; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_DIRECT_COMMENT, $uid, $item); + self::insertNotificationByItem(self::TYPE_DIRECT_COMMENT, $uid, $item); $notified = true; } } if (self::checkDirectCommentedThread($item, $contacts)) { - $notification_type = $notification_type | self::NOTIF_DIRECT_THREAD_COMMENT; + $notification_type = $notification_type | self::TYPE_DIRECT_THREAD_COMMENT; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_DIRECT_THREAD_COMMENT, $uid, $item); + self::insertNotificationByItem(self::TYPE_DIRECT_THREAD_COMMENT, $uid, $item); $notified = true; } } if (self::checkCommentedThread($item, $contacts)) { - $notification_type = $notification_type | self::NOTIF_THREAD_COMMENT; + $notification_type = $notification_type | self::TYPE_THREAD_COMMENT; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_THREAD_COMMENT, $uid, $item); + self::insertNotificationByItem(self::TYPE_THREAD_COMMENT, $uid, $item); $notified = true; } } if (self::checkCommentedParticipation($item, $contacts)) { - $notification_type = $notification_type | self::NOTIF_COMMENT_PARTICIPATION; + $notification_type = $notification_type | self::TYPE_COMMENT_PARTICIPATION; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_COMMENT_PARTICIPATION, $uid, $item); + self::insertNotificationByItem(self::TYPE_COMMENT_PARTICIPATION, $uid, $item); $notified = true; } } if (self::checkActivityParticipation($item, $contacts)) { - $notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION; + $notification_type = $notification_type | self::TYPE_ACTIVITY_PARTICIPATION; if (!$notified) { - self::insertNoticationByItem(self::NOTIF_ACTIVITY_PARTICIPATION, $uid, $item); - $notified = true; + self::insertNotificationByItem(self::TYPE_ACTIVITY_PARTICIPATION, $uid, $item); } } @@ -270,79 +272,71 @@ class UserNotification /** * Add a notification entry for a given item array * - * @param int $type User notification type - * @param int $uid User ID + * @param int $type User notification type + * @param int $uid User ID * @param array $item Item array - * @return boolean + * @return void + * @throws Exception */ - private static function insertNoticationByItem(int $type, int $uid, array $item) + private static function insertNotificationByItem(int $type, int $uid, array $item): void { if (($item['gravity'] == GRAVITY_ACTIVITY) && - !in_array($type, [self::NOTIF_DIRECT_COMMENT, self::NOTIF_DIRECT_THREAD_COMMENT])) { + !in_array($type, [self::TYPE_DIRECT_COMMENT, self::TYPE_DIRECT_THREAD_COMMENT])) { // Activities are only stored when performed on the user's post or comment 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) { - $ret = DBA::insert('notification', $fields, Database::INSERT_IGNORE); - if ($ret) { - $id = DBA::lastInsertId(); - if (!empty($id)) { - Subscription::pushByNotificationId($id); - } } - return $ret; } /** * 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 insertNotication(int $actor, int $vid, int $uid) + public static function insertNotification(int $actor, string $verb, int $uid): bool { - $fields = [ - 'uid' => $uid, - 'vid' => $vid, - 'type' => self::NOTIF_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; } /** * Fetch all profiles (contact URL) of a given user + * * @param int $uid User ID * * @return array Profile links + * @throws HTTPException\InternalServerErrorException */ - private static function getProfileForUser(int $uid) + private static function getProfileForUser(int $uid): array { $notification_data = ['uid' => $uid, 'profiles' => []]; Hook::callAll('check_item_notification', $notification_data); @@ -365,11 +359,11 @@ class UserNotification // Now the alias $profiles[] = $owner['alias']; - // Notifications from Diaspora are often with an URL in the Diaspora format + // Notifications from Diaspora often have a URL in the Diaspora format $profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; // Validate and add profile links - foreach ($profiles AS $key => $profile) { + foreach ($profiles as $key => $profile) { // Check for invalid profile urls (without scheme, host or path) and remove them if (empty(parse_url($profile, PHP_URL_SCHEME)) || empty(parse_url($profile, PHP_URL_HOST)) || empty(parse_url($profile, PHP_URL_PATH))) { unset($profiles[$key]); @@ -377,11 +371,11 @@ class UserNotification } // Add the normalized form - $profile = Strings::normaliseLink($profile); + $profile = Strings::normaliseLink($profile); $profiles[] = $profile; // Add the SSL form - $profile = str_replace('http://', 'https://', $profile); + $profile = str_replace('http://', 'https://', $profile); $profiles[] = $profile; } @@ -390,11 +384,13 @@ class UserNotification /** * Check for a "shared" notification for every new post of contacts from the given user + * * @param array $item - * @param int $uid User ID + * @param int $uid User ID * @return bool A contact had shared something + * @throws Exception */ - private static function checkShared(array $item, int $uid) + private static function checkShared(array $item, int $uid): bool { // Only check on original posts and reshare ("announce") activities, otherwise return if (($item['gravity'] != GRAVITY_PARENT) && ($item['verb'] != Activity::ANNOUNCE)) { @@ -425,12 +421,14 @@ class UserNotification } /** - * Check for an implicit mention (only tag, no body) of the given user + * Check for an implicit mention (only in tags, not in body) of the given user + * * @param array $item * @param array $profiles Profile links * @return bool The user is mentioned + * @throws Exception */ - private static function checkImplicitMention(array $item, array $profiles) + private static function checkImplicitMention(array $item, array $profiles): bool { $mentions = Tag::getByURIId($item['uri-id'], [Tag::IMPLICIT_MENTION]); foreach ($mentions as $mention) { @@ -446,11 +444,13 @@ class UserNotification /** * Check for an explicit mention (tag and body) of the given user + * * @param array $item * @param array $profiles Profile links * @return bool The user is mentioned + * @throws Exception */ - private static function checkExplicitMention(array $item, array $profiles) + private static function checkExplicitMention(array $item, array $profiles): bool { $mentions = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]); foreach ($mentions as $mention) { @@ -466,11 +466,13 @@ class UserNotification /** * Check if the given user had created this thread + * * @param array $item * @param array $contacts Array of contact IDs * @return bool The user had created this thread + * @throws Exception */ - private static function checkCommentedThread(array $item, array $contacts) + private static function checkCommentedThread(array $item, array $contacts): bool { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; return Post::exists($condition); @@ -478,11 +480,13 @@ class UserNotification /** * Check for a direct comment to a post of the given user + * * @param array $item * @param array $contacts Array of contact IDs * @return bool The item is a direct comment to a user comment + * @throws Exception */ - private static function checkDirectComment(array $item, array $contacts) + private static function checkDirectComment(array $item, array $contacts): bool { $condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Post::exists($condition); @@ -490,11 +494,13 @@ class UserNotification /** * Check for a direct comment to the starting post of the given user + * * @param array $item * @param array $contacts Array of contact IDs * @return bool The user had created this thread + * @throws Exception */ - private static function checkDirectCommentedThread(array $item, array $contacts) + private static function checkDirectCommentedThread(array $item, array $contacts): bool { $condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; return Post::exists($condition); @@ -502,11 +508,13 @@ class UserNotification /** * Check if the user had commented in this thread + * * @param array $item * @param array $contacts Array of contact IDs * @return bool The user had commented in the thread + * @throws Exception */ - private static function checkCommentedParticipation(array $item, array $contacts) + private static function checkCommentedParticipation(array $item, array $contacts): bool { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Post::exists($condition); @@ -514,11 +522,13 @@ class UserNotification /** * Check if the user had interacted in this thread (Like, Dislike, ...) + * * @param array $item * @param array $contacts Array of contact IDs * @return bool The user had interacted in the thread + * @throws Exception */ - private static function checkActivityParticipation(array $item, array $contacts) + private static function checkActivityParticipation(array $item, array $contacts): bool { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY]; return Post::exists($condition); diff --git a/src/Model/Subscription.php b/src/Model/Subscription.php index 59e71307f5..c6c57c7328 100644 --- a/src/Model/Subscription.php +++ b/src/Model/Subscription.php @@ -25,6 +25,8 @@ 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; class Subscription @@ -137,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]); - $desktop_notification = !in_array($type, ['reblog', 'favourite']); - - 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 e02823dea5..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]; @@ -74,72 +78,67 @@ class Notifications extends BaseApi } } - if (in_array('follow_request', $request['exclude_types'])) { + if (in_array(Notification::TYPE_INTRODUCTION, $request['exclude_types'])) { $condition = DBA::mergeConditions($condition, ["(`vid` != ? OR `type` != ? OR NOT EXISTS (SELECT `id` FROM `contact` WHERE `id` = `actor-id` AND `pending`))", - Verb::getID(Activity::FOLLOW), Post\UserNotification::NOTIF_NONE]); + Verb::getID(Activity::FOLLOW), Post\UserNotification::TYPE_NONE]); } - if (in_array('follow', $request['exclude_types'])) { + if (in_array(Notification::TYPE_FOLLOW, $request['exclude_types'])) { $condition = DBA::mergeConditions($condition, ["(`vid` != ? OR `type` != ? OR NOT EXISTS (SELECT `id` FROM `contact` WHERE `id` = `actor-id` AND NOT `pending`))", - Verb::getID(Activity::FOLLOW), Post\UserNotification::NOTIF_NONE]); + Verb::getID(Activity::FOLLOW), Post\UserNotification::TYPE_NONE]); } - if (in_array('favourite', $request['exclude_types'])) { - $condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?, ?) OR NOT `type` IN (?, ?))", + if (in_array(Notification::TYPE_LIKE, $request['exclude_types'])) { + $condition = DBA::mergeConditions($condition, [ + "(NOT `vid` IN (?, ?) OR NOT `type` IN (?, ?))", Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE), - Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]); + Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_THREAD_COMMENT + ]); } - if (in_array('reblog', $request['exclude_types'])) { - $condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?, ?))", + if (in_array(Notification::TYPE_RESHARE, $request['exclude_types'])) { + $condition = DBA::mergeConditions($condition, [ + "(NOT `vid` IN (?) OR NOT `type` IN (?, ?))", Verb::getID(Activity::ANNOUNCE), - Post\UserNotification::NOTIF_DIRECT_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]); + Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_THREAD_COMMENT + ]); } - if (in_array('mention', $request['exclude_types'])) { - $condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?, ?, ?, ?, ?))", - Verb::getID(Activity::POST), Post\UserNotification::NOTIF_EXPLICIT_TAGGED, - Post\UserNotification::NOTIF_IMPLICIT_TAGGED, Post\UserNotification::NOTIF_DIRECT_COMMENT, - Post\UserNotification::NOTIF_DIRECT_THREAD_COMMENT, Post\UserNotification::NOTIF_THREAD_COMMENT]); + if (in_array(Notification::TYPE_MENTION, $request['exclude_types'])) { + $condition = DBA::mergeConditions($condition, [ + "(NOT `vid` IN (?) OR NOT `type` IN (?, ?, ?, ?, ?))", + Verb::getID(Activity::POST), 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]); } - if (in_array('status', $request['exclude_types'])) { + if (in_array(Notification::TYPE_POST, $request['exclude_types'])) { $condition = DBA::mergeConditions($condition, ["(NOT `vid` IN (?) OR NOT `type` IN (?))", - Verb::getID(Activity::POST), Post\UserNotification::NOTIF_SHARED]); + 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/Api/Mastodon/PushSubscription.php b/src/Module/Api/Mastodon/PushSubscription.php index 411d5391f5..3bc77aeb6f 100644 --- a/src/Module/Api/Mastodon/PushSubscription.php +++ b/src/Module/Api/Mastodon/PushSubscription.php @@ -26,6 +26,7 @@ use Friendica\Core\System; use Friendica\DI; use Friendica\Model\Subscription; use Friendica\Module\BaseApi; +use Friendica\Object\Api\Mastodon\Notification; /** * @see https://docs.joinmastodon.org/methods/notifications/push/ @@ -44,18 +45,18 @@ class PushSubscription extends BaseApi ]); $subscription = [ - 'application-id' => $application['id'], - 'uid' => $uid, - 'endpoint' => $request['subscription']['endpoint'] ?? '', - 'pubkey' => $request['subscription']['keys']['p256dh'] ?? '', - 'secret' => $request['subscription']['keys']['auth'] ?? '', - 'follow' => $request['data']['alerts']['follow'] ?? false, - 'favourite' => $request['data']['alerts']['favourite'] ?? false, - 'reblog' => $request['data']['alerts']['reblog'] ?? false, - 'mention' => $request['data']['alerts']['mention'] ?? false, - 'poll' => $request['data']['alerts']['poll'] ?? false, - 'follow_request' => $request['data']['alerts']['follow_request'] ?? false, - 'status' => $request['data']['alerts']['status'] ?? false, + 'application-id' => $application['id'], + 'uid' => $uid, + 'endpoint' => $request['subscription']['endpoint'] ?? '', + 'pubkey' => $request['subscription']['keys']['p256dh'] ?? '', + 'secret' => $request['subscription']['keys']['auth'] ?? '', + Notification::TYPE_FOLLOW => $request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false, + Notification::TYPE_LIKE => $request['data']['alerts'][Notification::TYPE_LIKE] ?? false, + Notification::TYPE_RESHARE => $request['data']['alerts'][Notification::TYPE_RESHARE] ?? false, + Notification::TYPE_MENTION => $request['data']['alerts'][Notification::TYPE_MENTION] ?? false, + Notification::TYPE_POLL => $request['data']['alerts'][Notification::TYPE_POLL] ?? false, + Notification::TYPE_INTRODUCTION => $request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false, + Notification::TYPE_POST => $request['data']['alerts'][Notification::TYPE_POST] ?? false, ]; $ret = Subscription::replace($subscription); @@ -82,13 +83,13 @@ class PushSubscription extends BaseApi } $fields = [ - 'follow' => $request['data']['alerts']['follow'] ?? false, - 'favourite' => $request['data']['alerts']['favourite'] ?? false, - 'reblog' => $request['data']['alerts']['reblog'] ?? false, - 'mention' => $request['data']['alerts']['mention'] ?? false, - 'poll' => $request['data']['alerts']['poll'] ?? false, - 'follow_request' => $request['data']['alerts']['follow_request'] ?? false, - 'status' => $request['data']['alerts']['status'] ?? false, + Notification::TYPE_FOLLOW => $request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false, + Notification::TYPE_LIKE => $request['data']['alerts'][Notification::TYPE_LIKE] ?? false, + Notification::TYPE_RESHARE => $request['data']['alerts'][Notification::TYPE_RESHARE] ?? false, + Notification::TYPE_MENTION => $request['data']['alerts'][Notification::TYPE_MENTION] ?? false, + Notification::TYPE_POLL => $request['data']['alerts'][Notification::TYPE_POLL] ?? false, + Notification::TYPE_INTRODUCTION => $request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false, + Notification::TYPE_POST => $request['data']['alerts'][Notification::TYPE_POST] ?? false, ]; $ret = Subscription::update($application['id'], $uid, $fields); diff --git a/src/Module/BaseNotifications.php b/src/Module/BaseNotifications.php index a472c520ad..e7f9bdabe3 100644 --- a/src/Module/BaseNotifications.php +++ b/src/Module/BaseNotifications.php @@ -27,8 +27,8 @@ use Friendica\Content\Pager; use Friendica\Core\Renderer; use Friendica\Core\System; use Friendica\DI; +use Friendica\Navigation\Notifications\ValueObject\FormattedNotification; use Friendica\Network\HTTPException\ForbiddenException; -use Friendica\Object\Notification\Notification; /** * Base Module for each tab of the notification display @@ -39,29 +39,29 @@ abstract class BaseNotifications extends BaseModule { /** @var array Array of URL parameters */ const URL_TYPES = [ - Notification::NETWORK => 'network', - Notification::SYSTEM => 'system', - Notification::HOME => 'home', - Notification::PERSONAL => 'personal', - Notification::INTRO => 'intros', + FormattedNotification::NETWORK => 'network', + FormattedNotification::SYSTEM => 'system', + FormattedNotification::HOME => 'home', + FormattedNotification::PERSONAL => 'personal', + FormattedNotification::INTRO => 'intros', ]; /** @var array Array of the allowed notifications and their printable name */ const PRINT_TYPES = [ - Notification::NETWORK => 'Network', - Notification::SYSTEM => 'System', - Notification::HOME => 'Home', - Notification::PERSONAL => 'Personal', - Notification::INTRO => 'Introductions', + FormattedNotification::NETWORK => 'Network', + FormattedNotification::SYSTEM => 'System', + FormattedNotification::HOME => 'Home', + FormattedNotification::PERSONAL => 'Personal', + FormattedNotification::INTRO => 'Introductions', ]; /** @var array The array of access keys for notification pages */ const ACCESS_KEYS = [ - Notification::NETWORK => 'w', - Notification::SYSTEM => 'y', - Notification::HOME => 'h', - Notification::PERSONAL => 'r', - Notification::INTRO => 'i', + FormattedNotification::NETWORK => 'w', + FormattedNotification::SYSTEM => 'y', + FormattedNotification::HOME => 'h', + FormattedNotification::PERSONAL => 'r', + FormattedNotification::INTRO => 'i', ]; /** @var int The default count of items per page */ diff --git a/src/Module/Delegation.php b/src/Module/Delegation.php index 579b4d6b59..affe5e7b8f 100644 --- a/src/Module/Delegation.php +++ b/src/Module/Delegation.php @@ -126,9 +126,9 @@ class Delegation extends BaseModule $identities[$key]['selected'] = ($identity['nickname'] === DI::app()->getLoggedInUserNickname()); - $condition = ["`uid` = ? AND `msg` != '' AND NOT (`type` IN (?, ?)) AND NOT `seen`", $identity['uid'], Notification\Type::INTRO, Notification\Type::MAIL]; + $condition = ["`msg` != '' AND NOT (`type` IN (?, ?)) AND NOT `seen`", Notification\Type::INTRO, Notification\Type::MAIL]; $params = ['distinct' => true, 'expression' => 'parent']; - $notifications = DBA::count('notify', $condition, $params); + $notifications = DI::notify()->countForUser($identity['uid'], $condition, $params); $params = ['distinct' => true, 'expression' => 'convid']; $notifications += DBA::count('mail', ['uid' => $identity['uid'], 'seen' => false], $params); diff --git a/src/Module/Notifications/Introductions.php b/src/Module/Notifications/Introductions.php index 8781959312..9bc9f40b0a 100644 --- a/src/Module/Notifications/Introductions.php +++ b/src/Module/Notifications/Introductions.php @@ -26,11 +26,10 @@ use Friendica\Content\Nav; use Friendica\Content\Text\BBCode; use Friendica\Core\Protocol; use Friendica\Core\Renderer; -use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\User; use Friendica\Module\BaseNotifications; -use Friendica\Object\Notification\Introduction; +use Friendica\Navigation\Notifications\ValueObject\Introduction; /** * Prints notifications about introduction @@ -82,34 +81,34 @@ class Introductions extends BaseNotifications // Loop through all introduction notifications.This creates an array with the output html for each // introduction - /** @var Introduction $notification */ - foreach ($notifications['notifications'] as $notification) { + /** @var Introduction $Introduction */ + foreach ($notifications['notifications'] as $Introduction) { // There are two kind of introduction. Contacts suggested by other contacts and normal connection requests. // We have to distinguish between these two because they use different data. - switch ($notification->getLabel()) { + switch ($Introduction->getLabel()) { case 'friend_suggestion': $notificationContent[] = Renderer::replaceMacros($notificationSuggestions, [ - '$type' => $notification->getLabel(), + '$type' => $Introduction->getLabel(), '$str_notification_type' => DI::l10n()->t('Notification type:'), - '$str_type' => $notification->getType(), - '$intro_id' => $notification->getIntroId(), + '$str_type' => $Introduction->getType(), + '$intro_id' => $Introduction->getIntroId(), '$lbl_madeby' => DI::l10n()->t('Suggested by:'), - '$madeby' => $notification->getMadeBy(), - '$madeby_url' => $notification->getMadeByUrl(), - '$madeby_zrl' => $notification->getMadeByZrl(), - '$madeby_addr' => $notification->getMadeByAddr(), - '$contact_id' => $notification->getContactId(), - '$photo' => $notification->getPhoto(), - '$fullname' => $notification->getName(), + '$madeby' => $Introduction->getMadeBy(), + '$madeby_url' => $Introduction->getMadeByUrl(), + '$madeby_zrl' => $Introduction->getMadeByZrl(), + '$madeby_addr' => $Introduction->getMadeByAddr(), + '$contact_id' => $Introduction->getContactId(), + '$photo' => $Introduction->getPhoto(), + '$fullname' => $Introduction->getName(), '$dfrn_url' => $owner['url'], - '$url' => $notification->getUrl(), - '$zrl' => $notification->getZrl(), + '$url' => $Introduction->getUrl(), + '$zrl' => $Introduction->getZrl(), '$lbl_url' => DI::l10n()->t('Profile URL'), - '$addr' => $notification->getAddr(), + '$addr' => $Introduction->getAddr(), '$action' => 'follow', '$approve' => DI::l10n()->t('Approve'), - '$note' => $notification->getNote(), + '$note' => $Introduction->getNote(), '$ignore' => DI::l10n()->t('Ignore'), '$discard' => DI::l10n()->t('Discard'), '$is_mobile' => DI::mode()->isMobile(), @@ -118,15 +117,15 @@ class Introductions extends BaseNotifications // Normal connection requests default: - if ($notification->getNetwork() === Protocol::DFRN) { + if ($Introduction->getNetwork() === Protocol::DFRN) { $lbl_knowyou = DI::l10n()->t('Claims to be known to you: '); - $knowyou = ($notification->getKnowYou() ? DI::l10n()->t('Yes') : DI::l10n()->t('No')); + $knowyou = ($Introduction->getKnowYou() ? DI::l10n()->t('Yes') : DI::l10n()->t('No')); } else { $lbl_knowyou = ''; $knowyou = ''; } - $convertedName = BBCode::convert($notification->getName()); + $convertedName = BBCode::convert($Introduction->getName()); $helptext = DI::l10n()->t('Shall your connection be bidirectional or not?'); $helptext2 = DI::l10n()->t('Accepting %s as a friend allows %s to subscribe to your posts, and you will also receive updates from them in your news feed.', $convertedName, $convertedName); @@ -137,51 +136,51 @@ class Introductions extends BaseNotifications $action = 'follow_confirm'; - $header = $notification->getName(); + $header = $Introduction->getName(); - if ($notification->getAddr() != '') { - $header .= ' <' . $notification->getAddr() . '>'; + if ($Introduction->getAddr() != '') { + $header .= ' <' . $Introduction->getAddr() . '>'; } - $header .= ' (' . ContactSelector::networkToName($notification->getNetwork(), $notification->getUrl()) . ')'; + $header .= ' (' . ContactSelector::networkToName($Introduction->getNetwork(), $Introduction->getUrl()) . ')'; - if ($notification->getNetwork() != Protocol::DIASPORA) { + if ($Introduction->getNetwork() != Protocol::DIASPORA) { $discard = DI::l10n()->t('Discard'); } else { $discard = ''; } $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [ - '$type' => $notification->getLabel(), + '$type' => $Introduction->getLabel(), '$header' => $header, '$str_notification_type' => DI::l10n()->t('Notification type:'), - '$str_type' => $notification->getType(), - '$dfrn_id' => $notification->getDfrnId(), - '$uid' => $notification->getUid(), - '$intro_id' => $notification->getIntroId(), - '$contact_id' => $notification->getContactId(), - '$photo' => $notification->getPhoto(), - '$fullname' => $notification->getName(), - '$location' => $notification->getLocation(), + '$str_type' => $Introduction->getType(), + '$dfrn_id' => $Introduction->getDfrnId(), + '$uid' => $Introduction->getUid(), + '$intro_id' => $Introduction->getIntroId(), + '$contact_id' => $Introduction->getContactId(), + '$photo' => $Introduction->getPhoto(), + '$fullname' => $Introduction->getName(), + '$location' => $Introduction->getLocation(), '$lbl_location' => DI::l10n()->t('Location:'), - '$about' => $notification->getAbout(), + '$about' => $Introduction->getAbout(), '$lbl_about' => DI::l10n()->t('About:'), - '$keywords' => $notification->getKeywords(), + '$keywords' => $Introduction->getKeywords(), '$lbl_keywords' => DI::l10n()->t('Tags:'), - '$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), $notification->isHidden(), ''], + '$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), $Introduction->isHidden(), ''], '$lbl_connection_type' => $helptext, '$friend' => $friend, '$follower' => $follower, - '$url' => $notification->getUrl(), - '$zrl' => $notification->getZrl(), + '$url' => $Introduction->getUrl(), + '$zrl' => $Introduction->getZrl(), '$lbl_url' => DI::l10n()->t('Profile URL'), - '$addr' => $notification->getAddr(), + '$addr' => $Introduction->getAddr(), '$lbl_knowyou' => $lbl_knowyou, '$lbl_network' => DI::l10n()->t('Network:'), - '$network' => ContactSelector::networkToName($notification->getNetwork(), $notification->getUrl()), + '$network' => ContactSelector::networkToName($Introduction->getNetwork(), $Introduction->getUrl()), '$knowyou' => $knowyou, '$approve' => DI::l10n()->t('Approve'), - '$note' => $notification->getNote(), + '$note' => $Introduction->getNote(), '$ignore' => DI::l10n()->t('Ignore'), '$discard' => $discard, '$action' => $action, diff --git a/src/Module/Notifications/Notification.php b/src/Module/Notifications/Notification.php index 14e58c2172..2480e63b0b 100644 --- a/src/Module/Notifications/Notification.php +++ b/src/Module/Notifications/Notification.php @@ -78,7 +78,8 @@ class Notification extends BaseModule if (DI::args()->get(1) === 'mark' && DI::args()->get(2) === 'all') { try { - $success = DI::notify()->setSeen(); + DI::notification()->setAllSeenForUser(local_user()); + $success = DI::notify()->setAllSeenForUser(local_user()); } catch (\Exception $e) { DI::logger()->warning('set all seen failed.', ['exception' => $e]); $success = false; @@ -97,7 +98,7 @@ class Notification extends BaseModule * @throws HTTPException\InternalServerErrorException * @throws \Exception */ - public static function content(array $parameters = []) + public static function content(array $parameters = []): string { if (!local_user()) { notice(DI::l10n()->t('You must be logged in to show this page.')); @@ -107,17 +108,24 @@ class Notification extends BaseModule $request_id = $parameters['id'] ?? false; if ($request_id) { - $notify = DI::notify()->getByID($request_id, local_user()); - - if (DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { - $notify->seen = true; - DI::notify()->update($notify); - } else { - DI::notify()->setSeen(true, $notify); + $Notify = DI::notify()->selectOneById($request_id); + if ($Notify->uid !== local_user()) { + throw new HTTPException\ForbiddenException(); } - if (!empty($notify->link)) { - System::externalRedirect($notify->link); + if (DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) { + $Notify->setSeen(); + DI::notify()->save($Notify); + } else { + if ($Notify->uriId) { + DI::notification()->setAllSeenForUser($Notify->uid, ['target-uri-id' => $Notify->uriId]); + } + + DI::notify()->setAllSeenForRelatedNotify($Notify); + } + + if ($Notify->link) { + System::externalRedirect($Notify->link); } DI::baseUrl()->redirect(); @@ -125,6 +133,6 @@ class Notification extends BaseModule DI::baseUrl()->redirect('notifications/system'); - throw new HTTPException\InternalServerErrorException('Invalid situation.'); + return ''; } } diff --git a/src/Module/Notifications/Notifications.php b/src/Module/Notifications/Notifications.php index 06b5ca80d9..a0e40719c0 100644 --- a/src/Module/Notifications/Notifications.php +++ b/src/Module/Notifications/Notifications.php @@ -25,7 +25,9 @@ use Friendica\Content\Nav; use Friendica\Core\Renderer; use Friendica\DI; use Friendica\Module\BaseNotifications; -use Friendica\Object\Notification\Notification; +use Friendica\Navigation\Notifications\Collection\FormattedNotifications; +use Friendica\Navigation\Notifications\ValueObject\FormattedNotification; +use Friendica\Network\HTTPException\InternalServerErrorException; /** * Prints all notification types except introduction: @@ -42,41 +44,34 @@ class Notifications extends BaseNotifications public static function getNotifications() { $notificationHeader = ''; - /** @var Notification[] $notifications */ $notifications = []; - // Get the network notifications + $factory = DI::formattedNotificationFactory(); + if ((DI::args()->get(1) == 'network')) { $notificationHeader = DI::l10n()->t('Network Notifications'); $notifications = [ - 'ident' => Notification::NETWORK, - 'notifications' => DI::notification()->getNetworkList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), + 'ident' => FormattedNotification::NETWORK, + 'notifications' => $factory->getNetworkList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), ]; - - // Get the system notifications } elseif ((DI::args()->get(1) == 'system')) { $notificationHeader = DI::l10n()->t('System Notifications'); $notifications = [ - 'ident' => Notification::SYSTEM, - 'notifications' => DI::notification()->getSystemList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), + 'ident' => FormattedNotification::SYSTEM, + 'notifications' => $factory->getSystemList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), ]; - - // Get the personal notifications } elseif ((DI::args()->get(1) == 'personal')) { $notificationHeader = DI::l10n()->t('Personal Notifications'); $notifications = [ - 'ident' => Notification::PERSONAL, - 'notifications' => DI::notification()->getPersonalList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), + 'ident' => FormattedNotification::PERSONAL, + 'notifications' => $factory->getPersonalList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), ]; - - // Get the home notifications } elseif ((DI::args()->get(1) == 'home')) { $notificationHeader = DI::l10n()->t('Home Notifications'); $notifications = [ - 'ident' => Notification::HOME, - 'notifications' => DI::notification()->getHomeList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), + 'ident' => FormattedNotification::HOME, + 'notifications' => $factory->getHomeList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE), ]; - // fallback - redirect to main page } else { DI::baseUrl()->redirect('notifications'); } @@ -98,39 +93,32 @@ class Notifications extends BaseNotifications $notifications = $notificationResult['notifications'] ?? []; $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 Notification $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(); - $notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notification->getLabel()]); + $notificationTemplate = Renderer::getMarkupTemplate($notificationTemplates[$notificationArray['label']]); $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [ - '$item_label' => $notification->getLabel(), - '$item_link' => $notification->getLink(), - '$item_image' => $notification->getImage(), - '$item_url' => $notification->getUrl(), - '$item_text' => $notification->getText(), - '$item_when' => $notification->getWhen(), - '$item_ago' => $notification->getAgo(), - '$item_seen' => $notification->isSeen(), + '$notification' => $notificationArray ]); } } else { - $notificationNoContent = DI::l10n()->t('No more %s notifications.', $notifications['ident']); + $notificationNoContent = DI::l10n()->t('No more %s notifications.', $notificationResult['ident']); } $notificationShowLink = [ diff --git a/src/Navigation/Notifications/Collection/FormattedNotifications.php b/src/Navigation/Notifications/Collection/FormattedNotifications.php new file mode 100644 index 0000000000..00179ab32a --- /dev/null +++ b/src/Navigation/Notifications/Collection/FormattedNotifications.php @@ -0,0 +1,36 @@ +. + * + */ + +namespace Friendica\Navigation\Notifications\Collection; + +use Friendica\BaseCollection; +use Friendica\Navigation\Notifications\ValueObject; + +class FormattedNotifications extends BaseCollection +{ + /** + * @return ValueObject\FormattedNotification + */ + public function current(): ValueObject\FormattedNotification + { + return parent::current(); + } +} diff --git a/src/Navigation/Notifications/Collection/Notifications.php b/src/Navigation/Notifications/Collection/Notifications.php new file mode 100644 index 0000000000..f383b4ccb0 --- /dev/null +++ b/src/Navigation/Notifications/Collection/Notifications.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Navigation\Notifications\Collection; + +use Friendica\BaseCollection; +use Friendica\Navigation\Notifications\Entity; + +class Notifications extends BaseCollection +{ + /** + * @return Entity\Notification + */ + public function current(): Entity\Notification + { + return parent::current(); + } + + public function setSeen(): Notifications + { + return $this->map(function (Entity\Notification $Notification) { + $Notification->setSeen(); + }); + } +} diff --git a/src/Navigation/Notifications/Collection/Notifies.php b/src/Navigation/Notifications/Collection/Notifies.php new file mode 100644 index 0000000000..47fac8d0a1 --- /dev/null +++ b/src/Navigation/Notifications/Collection/Notifies.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Navigation\Notifications\Collection; + +use Friendica\BaseCollection; +use Friendica\Navigation\Notifications\Entity; + +class Notifies extends BaseCollection +{ + /** + * @return Entity\Notify + */ + public function current(): Entity\Notify + { + return parent::current(); + } + + public function setSeen(): Notifies + { + return $this->map(function (Entity\Notify $Notify) { + $Notify->setSeen(); + }); + } +} diff --git a/src/Navigation/Notifications/Depository/Notification.php b/src/Navigation/Notifications/Depository/Notification.php new file mode 100644 index 0000000000..a93bce6657 --- /dev/null +++ b/src/Navigation/Notifications/Depository/Notification.php @@ -0,0 +1,141 @@ +getArrayCopy()); + } + + public function countForUser($uid, array $condition, array $params = []): int + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->count($condition, $params); + } + + public function existsForUser($uid, array $condition): bool + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->exists($condition); + } + + /** + * @param int $id + * @return Entity\Notification + * @throws NotFoundException + */ + public function selectOneById(int $id): Entity\Notification + { + return $this->selectOne(['id' => $id]); + } + + public function selectOneForUser(int $uid, array $condition, array $params = []): Entity\Notification + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->selectOne($condition, $params); + } + + public function selectForUser(int $uid, array $condition = [], array $params = []): Collection\Notifications + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->select($condition, $params); + } + + public function selectAllForUser(int $uid): Collection\Notifications + { + return $this->selectForUser($uid); + } + + /** + * @param array $condition + * @param array $params + * @param int|null $min_id Retrieve models with an id no fewer than this, as close to it as possible + * @param int|null $max_id Retrieve models with an id no greater than this, as close to it as possible + * @param int $limit + * @return BaseCollection + * @throws Exception + * @see _selectByBoundaries + */ + public function selectByBoundaries(array $condition = [], array $params = [], int $min_id = null, int $max_id = null, int $limit = self::LIMIT) + { + $BaseCollection = parent::_selectByBoundaries($condition, $params, $min_id, $max_id, $limit); + + return new Collection\Notifications($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount()); + } + + public function setAllSeenForUser(int $uid, array $condition = []): bool + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->db->update(self::$table_name, ['seen' => true], $condition); + } + + /** + * @param Entity\Notification $Notification + * @return Entity\Notification + * @throws Exception + */ + public function save(Entity\Notification $Notification): Entity\Notification + { + $fields = [ + 'uid' => $Notification->uid, + 'vid' => Verb::getID($Notification->verb), + 'type' => $Notification->type, + 'actor-id' => $Notification->actorId, + 'target-uri-id' => $Notification->targetUriId, + 'parent-uri-id' => $Notification->parentUriId, + 'seen' => $Notification->seen, + ]; + + if ($Notification->id) { + $this->db->update(self::$table_name, $fields, ['id' => $Notification->id]); + } else { + $fields['created'] = DateTimeFormat::utcNow(); + $this->db->insert(self::$table_name, $fields); + + $Notification = $this->selectOneById($this->db->lastInsertId()); + } + + return $Notification; + } +} diff --git a/src/Navigation/Notifications/Depository/Notify.php b/src/Navigation/Notifications/Depository/Notify.php new file mode 100644 index 0000000000..4c7a1ef33d --- /dev/null +++ b/src/Navigation/Notifications/Depository/Notify.php @@ -0,0 +1,148 @@ +getArrayCopy()); + } + + public function countForUser($uid, array $condition, array $params = []): int + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->count($condition, $params); + } + + public function existsForUser($uid, array $condition): bool + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->exists($condition); + } + + /** + * @param int $id + * @return Entity\Notify + * @throws HTTPException\NotFoundException + */ + public function selectOneById(int $id): Entity\Notify + { + return $this->selectOne(['id' => $id]); + } + + public function selectForUser(int $uid, array $condition, array $params): Collection\Notifies + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->select($condition, $params); + } + + /** + * Returns notifications for the user, unread first, ordered in descending chronological order. + * + * @param int $uid + * @param int $limit + * @return Collection\Notifies + */ + public function selectAllForUser(int $uid, int $limit): Collection\Notifies + { + return $this->selectForUser($uid, [], ['order' => ['seen' => 'ASC', 'date' => 'DESC'], 'limit' => $limit]); + } + + public function setAllSeenForUser(int $uid, array $condition = []): bool + { + $condition = DBA::mergeConditions($condition, ['uid' => $uid]); + + return $this->db->update(self::$table_name, ['seen' => true], $condition); + } + + /** + * @param Entity\Notify $Notify + * @return Entity\Notify + * @throws HTTPException\NotFoundException + * @throws HTTPException\InternalServerErrorException + * @throws Exception\NotificationCreationInterceptedException + */ + public function save(Entity\Notify $Notify): Entity\Notify + { + $fields = [ + 'type' => $Notify->type, + 'name' => $Notify->name, + 'url' => $Notify->url, + 'photo' => $Notify->photo, + 'msg' => $Notify->msg, + 'uid' => $Notify->uid, + 'link' => $Notify->link, + 'iid' => $Notify->itemId, + 'parent' => $Notify->parent, + 'seen' => $Notify->seen, + 'verb' => $Notify->verb, + 'otype' => $Notify->otype, + 'name_cache' => $Notify->name_cache, + 'msg_cache' => $Notify->msg_cache, + 'uri-id' => $Notify->uriId, + 'parent-uri-id' => $Notify->parentUriId, + ]; + + if ($Notify->id) { + $this->db->update(self::$table_name, $fields, ['id' => $Notify->id]); + } else { + $fields['date'] = DateTimeFormat::utcNow(); + Hook::callAll('enotify_store', $fields); + + $this->db->insert(self::$table_name, $fields); + + $Notify = $this->selectOneById($this->db->lastInsertId()); + } + + return $Notify; + } + + public function setAllSeenForRelatedNotify(Entity\Notify $Notify): bool + { + $condition = [ + '(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?', + $Notify->link, + $Notify->parent, + $Notify->otype, + $Notify->uid + ]; + return $this->db->update(self::$table_name, ['seen' => true], $condition); + } +} diff --git a/src/Navigation/Notifications/Entity/Notification.php b/src/Navigation/Notifications/Entity/Notification.php new file mode 100644 index 0000000000..3f491f98c0 --- /dev/null +++ b/src/Navigation/Notifications/Entity/Notification.php @@ -0,0 +1,74 @@ +uid = $uid; + $this->verb = $verb; + $this->type = $type; + $this->actorId = $actorId; + $this->targetUriId = $targetUriId; + $this->parentUriId = $parentUriId ?: $targetUriId; + $this->created = $created; + $this->seen = $seen; + $this->id = $id; + } + + public function setSeen() + { + $this->seen = true; + } +} diff --git a/src/Navigation/Notifications/Entity/Notify.php b/src/Navigation/Notifications/Entity/Notify.php new file mode 100644 index 0000000000..88cd8ab36e --- /dev/null +++ b/src/Navigation/Notifications/Entity/Notify.php @@ -0,0 +1,128 @@ +type = $type; + $this->name = $name; + $this->url = $url; + $this->photo = $photo; + $this->date = $date; + $this->msg = $msg; + $this->uid = $uid; + $this->link = $link; + $this->itemId = $itemId; + $this->parent = $parent; + $this->seen = $seen; + $this->verb = $verb; + $this->otype = $otype; + $this->name_cache = $name_cache; + $this->msg_cache = $msg_cache; + $this->uriId = $uriId; + $this->parentUriId = $parentUriId; + $this->id = $id; + } + + public function setSeen() + { + $this->seen = true; + } + + public function updateMsgFromPreamble($epreamble) + { + $this->msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $this->link->__toString()]); + $this->msg_cache = self::formatMessage($this->name_cache, strip_tags(BBCode::convert($this->msg))); + } + + /** + * Formats a notification message with the notification author + * + * Replace the name with {0} but ensure to make that only once. The {0} is used + * later and prints the name in bold. + * + * @param string $name + * @param string $message + * + * @return string Formatted message + */ + public static function formatMessage(string $name, string $message): string + { + if ($name != '') { + $pos = strpos($message, $name); + } else { + $pos = false; + } + + if ($pos !== false) { + $message = substr_replace($message, '{0}', $pos, strlen($name)); + } + + return $message; + } +} diff --git a/src/Navigation/Notifications/Exception/NotificationCreationInterceptedException.php b/src/Navigation/Notifications/Exception/NotificationCreationInterceptedException.php new file mode 100644 index 0000000000..5dd7890283 --- /dev/null +++ b/src/Navigation/Notifications/Exception/NotificationCreationInterceptedException.php @@ -0,0 +1,7 @@ +. + * + */ + +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\Depository; +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 Depository\Notify */ + private $notify; + /** @var BaseURL */ + private $baseUrl; + /** @var L10n */ + private $l10n; + + public function __construct(LoggerInterface $logger, Database $dba, Depository\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/Factory/Notification/Introduction.php b/src/Navigation/Notifications/Factory/Introduction.php similarity index 59% rename from src/Factory/Notification/Introduction.php rename to src/Navigation/Notifications/Factory/Introduction.php index 2b5aecdb00..0baf0385c6 100644 --- a/src/Factory/Notification/Introduction.php +++ b/src/Navigation/Notifications/Factory/Introduction.php @@ -19,7 +19,7 @@ * */ -namespace Friendica\Factory\Notification; +namespace Friendica\Navigation\Notifications\Factory; use Exception; use Friendica\App; @@ -33,8 +33,7 @@ use Friendica\Core\Session\ISession; use Friendica\Database\Database; use Friendica\Model\Contact; use Friendica\Module\BaseNotifications; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Object\Notification; +use Friendica\Navigation\Notifications\ValueObject; use Friendica\Util\Proxy; use Psr\Log\LoggerInterface; @@ -80,11 +79,11 @@ class Introduction extends BaseFactory * @param int $limit Maximum number of query results * @param int $id When set, only the introduction with this id is displayed * - * @return Notification\Introduction[] + * @return ValueObject\Introduction[] */ - public function getList(bool $all = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT, int $id = 0) + public function getList(bool $all = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT, int $id = 0): array { - $sql_extra = ""; + $sql_extra = ""; if (empty($id)) { if (!$all) { @@ -93,10 +92,10 @@ class Introduction extends BaseFactory $sql_extra .= " AND NOT `intro`.`blocked` "; } else { - $sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id)); + $sql_extra = sprintf(" AND `intro`.`id` = %d ", $id); } - $formattedNotifications = []; + $formattedIntroductions = []; try { /// @todo Fetch contact details by "Contact::getByUrl" instead of queries to contact and fcontact @@ -114,68 +113,68 @@ class Introduction extends BaseFactory $limit ); - while ($notification = $this->dba->fetch($stmtNotifications)) { - if (empty($notification['url'])) { + while ($intro = $this->dba->fetch($stmtNotifications)) { + if (empty($intro['url'])) { continue; } // There are two kind of introduction. Contacts suggested by other contacts and normal connection requests. // We have to distinguish between these two because they use different data. // Contact suggestions - if ($notification['fid'] ?? '') { - if (empty($notification['furl'])) { + if ($intro['fid'] ?? '') { + if (empty($intro['furl'])) { continue; } $return_addr = bin2hex($this->nick . '@' . - $this->baseUrl->getHostName() . - (($this->baseUrl->getURLPath()) ? '/' . $this->baseUrl->getURLPath() : '')); + $this->baseUrl->getHostname() . + (($this->baseUrl->getUrlPath()) ? '/' . $this->baseUrl->getUrlPath() : '')); - $formattedNotifications[] = new Notification\Introduction([ + $formattedIntroductions[] = new ValueObject\Introduction([ 'label' => 'friend_suggestion', 'str_type' => $this->l10n->t('Friend Suggestion'), - 'intro_id' => $notification['intro_id'], - 'madeby' => $notification['name'], - 'madeby_url' => $notification['url'], - 'madeby_zrl' => Contact::magicLink($notification['url']), - 'madeby_addr' => $notification['addr'], - 'contact_id' => $notification['contact-id'], - 'photo' => Contact::getAvatarUrlForUrl($notification['furl'], 0, Proxy::SIZE_SMALL), - 'name' => $notification['fname'], - 'url' => $notification['furl'], - 'zrl' => Contact::magicLink($notification['furl']), - 'hidden' => $notification['hidden'] == 1, + 'intro_id' => $intro['intro_id'], + 'madeby' => $intro['name'], + 'madeby_url' => $intro['url'], + 'madeby_zrl' => Contact::magicLink($intro['url']), + 'madeby_addr' => $intro['addr'], + 'contact_id' => $intro['contact-id'], + 'photo' => Contact::getAvatarUrlForUrl($intro['furl'], 0, Proxy::SIZE_SMALL), + 'name' => $intro['fname'], + 'url' => $intro['furl'], + 'zrl' => Contact::magicLink($intro['furl']), + 'hidden' => $intro['hidden'] == 1, 'post_newfriend' => (intval($this->pConfig->get(local_user(), 'system', 'post_newfriend')) ? '1' : 0), - 'note' => $notification['note'], - 'request' => $notification['frequest'] . '?addr=' . $return_addr]); + 'note' => $intro['note'], + 'request' => $intro['frequest'] . '?addr=' . $return_addr]); // Normal connection requests } else { // Don't show these data until you are connected. Diaspora is doing the same. - if ($notification['network'] === Protocol::DIASPORA) { - $notification['location'] = ""; - $notification['about'] = ""; + if ($intro['network'] === Protocol::DIASPORA) { + $intro['location'] = ""; + $intro['about'] = ""; } - $formattedNotifications[] = new Notification\Introduction([ - 'label' => (($notification['network'] !== Protocol::OSTATUS) ? 'friend_request' : 'follower'), - 'str_type' => (($notification['network'] !== Protocol::OSTATUS) ? $this->l10n->t('Friend/Connect Request') : $this->l10n->t('New Follower')), - 'dfrn_id' => $notification['issued-id'], + $formattedIntroductions[] = new ValueObject\Introduction([ + 'label' => (($intro['network'] !== Protocol::OSTATUS) ? 'friend_request' : 'follower'), + 'str_type' => (($intro['network'] !== Protocol::OSTATUS) ? $this->l10n->t('Friend/Connect Request') : $this->l10n->t('New Follower')), + 'dfrn_id' => $intro['issued-id'], 'uid' => $this->session->get('uid'), - 'intro_id' => $notification['intro_id'], - 'contact_id' => $notification['contact-id'], - 'photo' => Contact::getPhoto($notification), - 'name' => $notification['name'], - 'location' => BBCode::convert($notification['location'], false), - 'about' => BBCode::convert($notification['about'], false), - 'keywords' => $notification['keywords'], - 'hidden' => $notification['hidden'] == 1, + 'intro_id' => $intro['intro_id'], + 'contact_id' => $intro['contact-id'], + 'photo' => Contact::getPhoto($intro), + 'name' => $intro['name'], + 'location' => BBCode::convert($intro['location'], false), + 'about' => BBCode::convert($intro['about'], false), + 'keywords' => $intro['keywords'], + 'hidden' => $intro['hidden'] == 1, 'post_newfriend' => (intval($this->pConfig->get(local_user(), 'system', 'post_newfriend')) ? '1' : 0), - 'url' => $notification['url'], - 'zrl' => Contact::magicLink($notification['url']), - 'addr' => $notification['addr'], - 'network' => $notification['network'], - 'knowyou' => $notification['knowyou'], - 'note' => $notification['note'], + 'url' => $intro['url'], + 'zrl' => Contact::magicLink($intro['url']), + 'addr' => $intro['addr'], + 'network' => $intro['network'], + 'knowyou' => $intro['knowyou'], + 'note' => $intro['note'], ]); } } @@ -183,6 +182,6 @@ class Introduction extends BaseFactory $this->logger->warning('Select failed.', ['uid' => $_SESSION['uid'], 'exception' => $e]); } - return $formattedNotifications; + return $formattedIntroductions; } } diff --git a/src/Navigation/Notifications/Factory/Notification.php b/src/Navigation/Notifications/Factory/Notification.php new file mode 100644 index 0000000000..e8ff9ea30f --- /dev/null +++ b/src/Navigation/Notifications/Factory/Notification.php @@ -0,0 +1,235 @@ +actorId, ['id', 'name', 'url', 'pending']); + if (empty($causer)) { + $this->logger->info('Causer not found', ['contact' => $Notification->actorId]); + return $message; + } + + if ($Notification->type === Post\UserNotification::TYPE_NONE) { + if ($causer['pending']) { + $msg = $userL10n->t('%1$s wants to follow you'); + } else { + $msg = $userL10n->t('%1$s had started following you'); + } + $title = $causer['name']; + $link = $baseUrl . '/contact/' . $causer['id']; + } else { + if (!$Notification->targetUriId) { + return $message; + } + + 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->parentUriId, 'uid' => [0, $Notification->uid]], ['order' => ['uid' => true]]); + if (empty($item)) { + $this->logger->info('Parent post not found', ['uri-id' => $Notification->parentUriId]); + return $message; + } + } else { + $item = Post::selectFirst([], ['uri-id' => $Notification->targetUriId, 'uid' => [0, $Notification->uid]], ['order' => ['uid' => true]]); + if (empty($item)) { + $this->logger->info('Post not found', ['uri-id' => $Notification->targetUriId]); + return $message; + } + + if ($Notification->verb == Activity::POST) { + $item = Post::selectFirst([], ['uri-id' => $item['thr-parent-id'], 'uid' => [0, $Notification->uid]], ['order' => ['uid' => true]]); + if (empty($item)) { + $this->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($causer)) { + $this->logger->info('Causer not found', ['causer' => $cid]); + return $message; + } + } elseif (in_array($Notification->type, [Post\UserNotification::TYPE_COMMENT_PARTICIPATION, Post\UserNotification::TYPE_ACTIVITY_PARTICIPATION])) { + $author = Contact::getById($item['author-id'], ['id', 'name', 'url']); + if (empty($author)) { + $this->logger->info('Author not found', ['author' => $item['author-id']]); + return $message; + } + } + + $link = $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->verb) { + case Activity::LIKE: + switch ($Notification->type) { + case Post\UserNotification::TYPE_DIRECT_COMMENT: + $msg = $userL10n->t('%1$s liked your comment %2$s'); + break; + case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: + $msg = $userL10n->t('%1$s liked your post %2$s'); + break; + } + break; + case Activity::DISLIKE: + switch ($Notification->type) { + case Post\UserNotification::TYPE_DIRECT_COMMENT: + $msg = $userL10n->t('%1$s disliked your comment %2$s'); + break; + case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: + $msg = $userL10n->t('%1$s disliked your post %2$s'); + break; + } + break; + case Activity::ANNOUNCE: + switch ($Notification->type) { + case Post\UserNotification::TYPE_DIRECT_COMMENT: + $msg = $userL10n->t('%1$s shared your comment %2$s'); + break; + case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: + $msg = $userL10n->t('%1$s shared your post %2$s'); + break; + } + break; + case Activity::POST: + switch ($Notification->type) { + case Post\UserNotification::TYPE_EXPLICIT_TAGGED: + $msg = $userL10n->t('%1$s tagged you on %2$s'); + break; + + case Post\UserNotification::TYPE_IMPLICIT_TAGGED: + $msg = $userL10n->t('%1$s replied to you on %2$s'); + break; + + case Post\UserNotification::TYPE_THREAD_COMMENT: + $msg = $userL10n->t('%1$s commented in your thread %2$s'); + break; + + case Post\UserNotification::TYPE_DIRECT_COMMENT: + $msg = $userL10n->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'] == $author['id']) && ($title != '')) { + $msg = $userL10n->t('%1$s commented in their thread %2$s'); + } elseif ($causer['id'] == $author['id']) { + $msg = $userL10n->t('%1$s commented in their thread'); + } elseif ($title != '') { + $msg = $userL10n->t('%1$s commented in the thread %2$s from %3$s'); + } else { + $msg = $userL10n->t('%1$s commented in the thread from %3$s'); + } + break; + + case Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT: + $msg = $userL10n->t('%1$s commented on your thread %2$s'); + break; + + case Post\UserNotification::TYPE_SHARED: + if (($causer['id'] != $author['id']) && ($title != '')) { + $msg = $userL10n->t('%1$s shared the post %2$s from %3$s'); + } elseif ($causer['id'] != $author['id']) { + $msg = $userL10n->t('%1$s shared a post from %3$s'); + } elseif ($title != '') { + $msg = $userL10n->t('%1$s shared the post %2$s'); + } else { + $msg = $userL10n->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, $author['name']); + // Plain text for the web push api + $message['plain'] = sprintf($msg, $causer['name'], $title, $author['name']); + // Rich text for other purposes + $message['rich'] = sprintf($msg, + '[url=' . $causer['url'] . ']' . $causer['name'] . '[/url]', + '[url=' . $link . ']' . $title . '[/url]', + '[url=' . $author['url'] . ']' . $author['name'] . '[/url]'); + } + + return $message; + } +} diff --git a/src/Navigation/Notifications/Factory/Notify.php b/src/Navigation/Notifications/Factory/Notify.php new file mode 100644 index 0000000000..87ba7d2fe7 --- /dev/null +++ b/src/Navigation/Notifications/Factory/Notify.php @@ -0,0 +1,58 @@ +. + * + */ + +namespace Friendica\Navigation\Notifications\ValueObject; + +use Friendica\BaseDataTransferObject; + +/** + * A view-only object for printing item notifications to the frontend + */ +class FormattedNotification extends BaseDataTransferObject +{ + const SYSTEM = 'system'; + const PERSONAL = 'personal'; + const NETWORK = 'network'; + const INTRO = 'intro'; + const HOME = 'home'; + + /** @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) + { + $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; + } +} diff --git a/src/Object/Notification/Introduction.php b/src/Navigation/Notifications/ValueObject/Introduction.php similarity index 61% rename from src/Object/Notification/Introduction.php rename to src/Navigation/Notifications/ValueObject/Introduction.php index 2a2d90cda2..332f6ccf60 100644 --- a/src/Object/Notification/Introduction.php +++ b/src/Navigation/Notifications/ValueObject/Introduction.php @@ -19,7 +19,7 @@ * */ -namespace Friendica\Object\Notification; +namespace Friendica\Navigation\Notifications\ValueObject; /** * A view-only object for printing introduction notifications to the frontend @@ -27,242 +27,170 @@ namespace Friendica\Object\Notification; class Introduction implements \JsonSerializable { /** @var string */ - private $label = ''; + private $label; /** @var string */ - private $type = ''; - /** @var integer */ - private $intro_id = -1; - /** @var string */ - private $madeBy = ''; - /** @var string */ - private $madeByUrl = ''; - /** @var string */ - private $madeByZrl = ''; - /** @var string */ - private $madeByAddr = ''; - /** @var integer */ - private $contactId = -1; - /** @var string */ - private $photo = ''; - /** @var string */ - private $name = ''; - /** @var string */ - private $url = ''; - /** @var string */ - private $zrl = ''; - /** @var boolean */ - private $hidden = false; - /** @var integer */ - private $postNewFriend = -1; - /** @var boolean */ - private $knowYou = false; - /** @var string */ - private $note = ''; - /** @var string */ - private $request = ''; + private $type; /** @var int */ - private $dfrnId = -1; + private $intro_id; /** @var string */ - private $addr = ''; + private $madeBy; /** @var string */ - private $network = ''; + private $madeByUrl; + /** @var string */ + private $madeByZrl; + /** @var string */ + private $madeByAddr; /** @var int */ - private $uid = -1; + private $contactId; /** @var string */ - private $keywords = ''; + private $photo; /** @var string */ - private $location = ''; + private $name; /** @var string */ - private $about = ''; + private $url; + /** @var string */ + private $zrl; + /** @var boolean */ + private $hidden; + /** @var int */ + private $postNewFriend; + /** @var boolean */ + private $knowYou; + /** @var string */ + private $note; + /** @var string */ + private $request; + /** @var int */ + private $dfrnId; + /** @var string */ + private $addr; + /** @var string */ + private $network; + /** @var int */ + private $uid; + /** @var string */ + private $keywords; + /** @var string */ + private $location; + /** @var string */ + private $about; - /** - * @return string - */ - public function getLabel() + public function getLabel(): string { return $this->label; } - /** - * @return string - */ - public function getType() + public function getType(): string { return $this->type; } - /** - * @return int - */ - public function getIntroId() + public function getIntroId(): int { return $this->intro_id; } - /** - * @return string - */ - public function getMadeBy() + public function getMadeBy(): string { return $this->madeBy; } - /** - * @return string - */ - public function getMadeByUrl() + public function getMadeByUrl(): string { return $this->madeByUrl; } - /** - * @return string - */ - public function getMadeByZrl() + public function getMadeByZrl(): string { return $this->madeByZrl; } - /** - * @return string - */ - public function getMadeByAddr() + public function getMadeByAddr(): string { return $this->madeByAddr; } - /** - * @return int - */ - public function getContactId() + public function getContactId(): int { return $this->contactId; } - /** - * @return string - */ - public function getPhoto() + public function getPhoto(): string { return $this->photo; } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } - /** - * @return string - */ - public function getUrl() + public function getUrl(): string { return $this->url; } - /** - * @return string - */ - public function getZrl() + public function getZrl(): string { return $this->zrl; } - /** - * @return bool - */ - public function isHidden() + public function isHidden(): bool { return $this->hidden; } - /** - * @return int - */ - public function getPostNewFriend() + public function getPostNewFriend(): int { return $this->postNewFriend; } - /** - * @return string - */ - public function getKnowYou() + public function getKnowYou(): string { return $this->knowYou; } - /** - * @return string - */ - public function getNote() + public function getNote(): string { return $this->note; } - /** - * @return string - */ - public function getRequest() + public function getRequest(): string { return $this->request; } - /** - * @return string - */ - public function getDfrnId() + public function getDfrnId(): int { return $this->dfrnId; } - /** - * @return string - */ - public function getAddr() + public function getAddr(): string { return $this->addr; } - /** - * @return string - */ - public function getNetwork() + public function getNetwork(): string { return $this->network; } - /** - * @return int - */ - public function getUid() + public function getUid(): int { return $this->uid; } - /** - * @return string - */ - public function getKeywords() + public function getKeywords(): string { return $this->keywords; } - /** - * @return string - */ - public function getLocation() + public function getLocation(): string { return $this->location; } - /** - * @return string - */ - public function getAbout() + public function getAbout(): string { return $this->about; } @@ -276,7 +204,7 @@ class Introduction implements \JsonSerializable $this->madeByUrl = $data['madeByUrl'] ?? ''; $this->madeByZrl = $data['madeByZrl'] ?? ''; $this->madeByAddr = $data['madeByAddr'] ?? ''; - $this->contactId = $data['contactId'] ?? ''; + $this->contactId = $data['contactId'] ?? -1; $this->photo = $data['photo'] ?? ''; $this->name = $data['name'] ?? ''; $this->url = $data['url'] ?? ''; @@ -306,7 +234,7 @@ class Introduction implements \JsonSerializable /** * @return array */ - public function toArray() + public function toArray(): array { return get_object_vars($this); } diff --git a/src/Object/Api/Friendica/Notification.php b/src/Object/Api/Friendica/Notification.php index e2ff3f6779..1bec7cfed1 100644 --- a/src/Object/Api/Friendica/Notification.php +++ b/src/Object/Api/Friendica/Notification.php @@ -2,7 +2,7 @@ /** * @copyright Copyright (C) 2010-2021, the Friendica project * - * @license GNU AGPL version 3 or any later version + * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -24,7 +24,7 @@ namespace Friendica\Object\Api\Friendica; use Friendica\BaseDataTransferObject; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; -use Friendica\Model\Notification as NotificationModel; +use Friendica\Navigation\Notifications\Entity\Notify; use Friendica\Util\DateTimeFormat; use Friendica\Util\Temporal; @@ -37,8 +37,6 @@ class Notification extends BaseDataTransferObject { /** @var integer */ protected $id; - /** @var string */ - protected $hash; /** @var integer */ protected $type; /** @var string Full name of the contact subject */ @@ -78,21 +76,37 @@ class Notification extends BaseDataTransferObject /** @var string Message (Plaintext) */ protected $msg_plain; - public function __construct(NotificationModel $notify) + public function __construct(Notify $Notify) { - // map each notify attribute to the entity - foreach ($notify->toArray() as $key => $value) { - $this->{$key} = $value; + $this->id = $Notify->id; + $this->type = $Notify->type; + $this->name = $Notify->name; + $this->url = $Notify->url->__toString(); + $this->photo = $Notify->photo->__toString(); + $this->date = DateTimeFormat::local($Notify->date->format(DateTimeFormat::MYSQL)); + $this->msg = $Notify->msg; + $this->uid = $Notify->uid; + $this->link = $Notify->link->__toString(); + $this->iid = $Notify->itemId; + $this->parent = $Notify->parent; + $this->seen = $Notify->seen; + $this->verb = $Notify->verb; + $this->otype = $Notify->otype; + $this->name_cache = $Notify->name_cache; + $this->msg_cache = $Notify->msg_cache; + $this->timestamp = $Notify->date->format('U'); + $this->date_rel = Temporal::getRelativeDate($this->date); + + try { + $this->msg_html = BBCode::convert($this->msg, false); + } catch (\Exception $e) { + $this->msg_html = ''; } - // add additional attributes for the API try { - $this->timestamp = strtotime(DateTimeFormat::local($this->date)); - $this->msg_html = BBCode::convert($this->msg, false); $this->msg_plain = explode("\n", trim(HTML::toPlaintext($this->msg_html, 0)))[0]; } catch (\Exception $e) { + $this->msg_plain = ''; } - - $this->date_rel = Temporal::getRelativeDate($this->date); } } diff --git a/src/Object/Api/Mastodon/Notification.php b/src/Object/Api/Mastodon/Notification.php index 5f39c503e2..d912dc894d 100644 --- a/src/Object/Api/Mastodon/Notification.php +++ b/src/Object/Api/Mastodon/Notification.php @@ -33,9 +33,26 @@ use Friendica\Util\DateTimeFormat; */ class Notification extends BaseDataTransferObject { + /* From the Mastodon documentation: + * - follow = Someone followed you + * - follow_request = Someone requested to follow you + * - mention = Someone mentioned you in their status + * - reblog = Someone boosted one of your statuses + * - favourite = Someone favourited one of your statuses + * - poll = A poll you have voted in or created has ended + * - status = Someone you enabled notifications for has posted a status + */ + public const TYPE_FOLLOW = 'follow'; + public const TYPE_INTRODUCTION = 'follow_request'; + public const TYPE_MENTION = 'mention'; + public const TYPE_RESHARE = 'reblog'; + public const TYPE_LIKE = 'favourite'; + public const TYPE_POLL = 'poll'; + public const TYPE_POST = 'status'; + /** @var string */ protected $id; - /** @var string (Enumerable oneOf) */ + /** @var string One of the TYPE_* constant values */ protected $type; /** @var string (Datetime) */ protected $created_at; @@ -49,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/Object/Api/Mastodon/Subscription.php b/src/Object/Api/Mastodon/Subscription.php index dcd0a6982f..ddc6f7d6eb 100644 --- a/src/Object/Api/Mastodon/Subscription.php +++ b/src/Object/Api/Mastodon/Subscription.php @@ -42,20 +42,19 @@ class Subscription extends BaseDataTransferObject /** * Creates a subscription record from an item record. * - * @param array $subscription - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @param array $subscription + * @param string $vapid */ public function __construct(array $subscription, string $vapid) { $this->id = (string)$subscription['id']; $this->endpoint = $subscription['endpoint']; $this->alerts = [ - 'follow' => $subscription['follow'], - 'favourite' => $subscription['favourite'], - 'reblog' => $subscription['reblog'], - 'mention' => $subscription['mention'], - 'mention' => $subscription['mention'], - 'poll' => $subscription['poll'], + Notification::TYPE_FOLLOW => $subscription[Notification::TYPE_FOLLOW], + Notification::TYPE_LIKE => $subscription[Notification::TYPE_LIKE], + Notification::TYPE_RESHARE => $subscription[Notification::TYPE_RESHARE], + Notification::TYPE_MENTION => $subscription[Notification::TYPE_MENTION], + Notification::TYPE_POLL => $subscription[Notification::TYPE_POLL], ]; $this->server_key = $vapid; diff --git a/src/Object/Notification/Notification.php b/src/Object/Notification/Notification.php deleted file mode 100644 index 164e7cc038..0000000000 --- a/src/Object/Notification/Notification.php +++ /dev/null @@ -1,143 +0,0 @@ -. - * - */ - -namespace Friendica\Object\Notification; - -/** - * A view-only object for printing item notifications to the frontend - */ -class Notification implements \JsonSerializable -{ - const SYSTEM = 'system'; - const PERSONAL = 'personal'; - const NETWORK = 'network'; - const INTRO = 'intro'; - const HOME = 'home'; - - /** @var string */ - private $label = ''; - /** @var string */ - private $link = ''; - /** @var string */ - private $image = ''; - /** @var string */ - private $url = ''; - /** @var string */ - private $text = ''; - /** @var string */ - private $when = ''; - /** @var string */ - private $ago = ''; - /** @var boolean */ - private $seen = false; - - /** - * @return string - */ - public function getLabel() - { - return $this->label; - } - - /** - * @return string - */ - public function getLink() - { - return $this->link; - } - - /** - * @return string - */ - public function getImage() - { - return $this->image; - } - - /** - * @return string - */ - public function getUrl() - { - return $this->url; - } - - /** - * @return string - */ - public function getText() - { - return $this->text; - } - - /** - * @return string - */ - public function getWhen() - { - return $this->when; - } - - /** - * @return string - */ - public function getAgo() - { - return $this->ago; - } - - /** - * @return bool - */ - public function isSeen() - { - return $this->seen; - } - - public function __construct(array $data) - { - $this->label = $data['label'] ?? ''; - $this->link = $data['link'] ?? ''; - $this->image = $data['image'] ?? ''; - $this->url = $data['url'] ?? ''; - $this->text = $data['text'] ?? ''; - $this->when = $data['when'] ?? ''; - $this->ago = $data['ago'] ?? ''; - $this->seen = $data['seen'] ?? false; - } - - /** - * @inheritDoc - */ - public function jsonSerialize() - { - return get_object_vars($this); - } - - /** - * @return array - */ - public function toArray() - { - return get_object_vars($this); - } -} diff --git a/src/Repository/Notification.php b/src/Repository/Notification.php deleted file mode 100644 index 1748759b60..0000000000 --- a/src/Repository/Notification.php +++ /dev/null @@ -1,130 +0,0 @@ -. - * - */ - -namespace Friendica\Repository; - -use Exception; -use Friendica\BaseRepository; -use Friendica\Collection; -use Friendica\Core\Hook; -use Friendica\Model; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Network\HTTPException\NotFoundException; -use Friendica\Util\DateTimeFormat; - -class Notification extends BaseRepository -{ - protected static $table_name = 'notify'; - - protected static $model_class = Model\Notification::class; - - protected static $collection_class = Collection\Notifications::class; - - /** - * {@inheritDoc} - * - * @return Model\Notification - */ - protected function create(array $data) - { - return new Model\Notification($this->dba, $this->logger, $this, $data); - } - - /** - * {@inheritDoc} - * - * @return Collection\Notifications - */ - public function select(array $condition = [], array $params = []) - { - $params['order'] = $params['order'] ?? ['date' => 'DESC']; - - return parent::select($condition, $params); - } - - /** - * Return one notify instance based on ID / UID - * - * @param int $id The ID of the notify instance - * @param int $uid The user ID, bound to this notify instance (= security check) - * - * @return Model\Notification - * @throws NotFoundException - */ - public function getByID(int $id, int $uid) - { - return $this->selectFirst(['id' => $id, 'uid' => $uid]); - } - - /** - * Set seen state of notifications of the local_user() - * - * @param bool $seen optional true or false. default true - * @param Model\Notification $notify optional a notify, which should be set seen (including his parents) - * - * @return bool true on success, false on error - * - * @throws Exception - */ - public function setSeen(bool $seen = true, Model\Notification $notify = null) - { - if (empty($notify)) { - $this->dba->update('notification', ['seen' => $seen], ['uid' => local_user()]); - $conditions = ['uid' => local_user()]; - } else { - if (!empty($notify->{'uri-id'})) { - $this->dba->update('notification', ['seen' => $seen], ['uid' => local_user(), 'target-uri-id' => $notify->{'uri-id'}]); - } - - $conditions = ['(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?', - $notify->link, - $notify->parent, - $notify->otype, - local_user()]; - } - - return $this->dba->update('notify', ['seen' => $seen], $conditions); - } - - /** - * @param array $fields - * - * @return Model\Notification|false - * - * @throws InternalServerErrorException - * @throws Exception - */ - public function insert(array $fields) - { - $fields['date'] = DateTimeFormat::utcNow(); - - Hook::callAll('enotify_store', $fields); - - if (empty($fields)) { - $this->logger->debug('Abort adding notification entry'); - return false; - } - - $this->logger->debug('adding notification entry', ['fields' => $fields]); - - return parent::insert($fields); - } -} diff --git a/src/Worker/PushSubscription.php b/src/Worker/PushSubscription.php index eaf2adb32d..f07b773198 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 = DI::notificationFactory()->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'), diff --git a/tests/datasets/api.fixture.php b/tests/datasets/api.fixture.php index 2c6512f8c6..86b2bd5648 100644 --- a/tests/datasets/api.fixture.php +++ b/tests/datasets/api.fixture.php @@ -732,7 +732,7 @@ return [ 'link' => 'http://localhost/notification/1', 'iid' => 4, 'seen' => 0, - 'verb' => '', + 'verb' => \Friendica\Protocol\Activity::POST, 'otype' => Notification\ObjectType::ITEM, 'name_cache' => 'Reply to', 'msg_cache' => 'A test reply from an item', diff --git a/tests/legacy/ApiTest.php b/tests/legacy/ApiTest.php index 8e0edaeefe..553eef2a0a 100644 --- a/tests/legacy/ApiTest.php +++ b/tests/legacy/ApiTest.php @@ -12,6 +12,7 @@ use Friendica\Core\Protocol; use Friendica\DI; use Friendica\Network\HTTPException; use Friendica\Test\FixtureTest; +use Friendica\Util\DateTimeFormat; use Friendica\Util\Temporal; use Monolog\Handler\TestHandler; @@ -3714,11 +3715,13 @@ class ApiTest extends FixtureTest { DI::args()->setArgv(['api', 'friendica', 'notification']); $result = api_friendica_notification('xml'); - $dateRel = Temporal::getRelativeDate('2020-01-01 12:12:02'); + $date = DateTimeFormat::local('2020-01-01 12:12:02'); + $dateRel = Temporal::getRelativeDate('2020-01-01 07:12:02'); + $assertXml=<< - + XML; self::assertXmlStringEqualsXmlString($assertXml, $result); diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 3e2421d04c..dc445b7320 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2021.12-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-02 16:56-0400\n" +"POT-Creation-Date: 2021-10-02 17:52-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,7 +37,7 @@ msgstr[1] "" msgid "Monthly posting limit of %d post reached. The post was rejected." msgstr "" -#: include/api.php:4428 mod/photos.php:89 mod/photos.php:198 mod/photos.php:621 +#: include/api.php:4431 mod/photos.php:89 mod/photos.php:198 mod/photos.php:621 #: mod/photos.php:1032 mod/photos.php:1049 mod/photos.php:1598 #: src/Model/User.php:1169 src/Model/User.php:1177 src/Model/User.php:1185 #: src/Module/Settings/Profile/Photo/Crop.php:101 @@ -49,195 +49,195 @@ msgstr "" msgid "Profile Photos" msgstr "" -#: include/enotify.php:52 include/enotify.php:559 +#: include/enotify.php:50 include/enotify.php:533 msgid "[Friendica:Notify]" msgstr "" -#: include/enotify.php:116 +#: include/enotify.php:114 #, php-format msgid "%s New mail received at %s" msgstr "" -#: include/enotify.php:118 +#: include/enotify.php:116 #, php-format msgid "%1$s sent you a new private message at %2$s." msgstr "" -#: include/enotify.php:119 +#: include/enotify.php:117 msgid "a private message" msgstr "" -#: include/enotify.php:119 +#: include/enotify.php:117 #, php-format msgid "%1$s sent you %2$s." msgstr "" -#: include/enotify.php:121 +#: include/enotify.php:119 #, php-format msgid "Please visit %s to view and/or reply to your private messages." msgstr "" -#: include/enotify.php:152 +#: include/enotify.php:150 #, php-format msgid "%1$s commented on %2$s's %3$s %4$s" msgstr "" -#: include/enotify.php:157 +#: include/enotify.php:155 #, php-format msgid "%1$s commented on your %2$s %3$s" msgstr "" -#: include/enotify.php:161 +#: include/enotify.php:159 #, php-format msgid "%1$s commented on their %2$s %3$s" msgstr "" -#: include/enotify.php:165 include/enotify.php:594 +#: include/enotify.php:163 include/enotify.php:568 #, php-format msgid "%1$s Comment to conversation #%2$d by %3$s" msgstr "" -#: include/enotify.php:167 +#: include/enotify.php:165 #, php-format msgid "%s commented on an item/conversation you have been following." msgstr "" -#: include/enotify.php:171 include/enotify.php:186 include/enotify.php:205 -#: include/enotify.php:609 +#: include/enotify.php:169 include/enotify.php:184 include/enotify.php:203 +#: include/enotify.php:583 #, php-format msgid "Please visit %s to view and/or reply to the conversation." msgstr "" -#: include/enotify.php:178 +#: include/enotify.php:176 #, php-format msgid "%s %s posted to your profile wall" msgstr "" -#: include/enotify.php:180 +#: include/enotify.php:178 #, php-format msgid "%1$s posted to your profile wall at %2$s" msgstr "" -#: include/enotify.php:181 +#: include/enotify.php:179 #, php-format msgid "%1$s posted to [url=%2$s]your wall[/url]" msgstr "" -#: include/enotify.php:193 +#: include/enotify.php:191 #, php-format msgid "%1$s %2$s poked you" msgstr "" -#: include/enotify.php:195 +#: include/enotify.php:193 #, php-format msgid "%1$s poked you at %2$s" msgstr "" -#: include/enotify.php:196 +#: include/enotify.php:194 #, php-format msgid "%1$s [url=%2$s]poked you[/url]." msgstr "" -#: include/enotify.php:213 +#: include/enotify.php:211 #, php-format msgid "%s Introduction received" msgstr "" -#: include/enotify.php:215 +#: include/enotify.php:213 #, php-format msgid "You've received an introduction from '%1$s' at %2$s" msgstr "" -#: include/enotify.php:216 +#: include/enotify.php:214 #, php-format msgid "You've received [url=%1$s]an introduction[/url] from %2$s." msgstr "" -#: include/enotify.php:221 include/enotify.php:267 +#: include/enotify.php:219 include/enotify.php:265 #, php-format msgid "You may visit their profile at %s" msgstr "" -#: include/enotify.php:223 +#: include/enotify.php:221 #, php-format msgid "Please visit %s to approve or reject the introduction." msgstr "" -#: include/enotify.php:230 +#: include/enotify.php:228 #, php-format msgid "%s A new person is sharing with you" msgstr "" -#: include/enotify.php:232 include/enotify.php:233 +#: include/enotify.php:230 include/enotify.php:231 #, php-format msgid "%1$s is sharing with you at %2$s" msgstr "" -#: include/enotify.php:240 +#: include/enotify.php:238 #, php-format msgid "%s You have a new follower" msgstr "" -#: include/enotify.php:242 include/enotify.php:243 +#: include/enotify.php:240 include/enotify.php:241 #, php-format msgid "You have a new follower at %2$s : %1$s" msgstr "" -#: include/enotify.php:256 +#: include/enotify.php:254 #, php-format msgid "%s Friend suggestion received" msgstr "" -#: include/enotify.php:258 +#: include/enotify.php:256 #, php-format msgid "You've received a friend suggestion from '%1$s' at %2$s" msgstr "" -#: include/enotify.php:259 +#: include/enotify.php:257 #, php-format msgid "You've received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s." msgstr "" -#: include/enotify.php:265 +#: include/enotify.php:263 msgid "Name:" msgstr "" -#: include/enotify.php:266 +#: include/enotify.php:264 msgid "Photo:" msgstr "" -#: include/enotify.php:269 +#: include/enotify.php:267 #, php-format msgid "Please visit %s to approve or reject the suggestion." msgstr "" -#: include/enotify.php:277 include/enotify.php:292 +#: include/enotify.php:275 include/enotify.php:290 #, php-format msgid "%s Connection accepted" msgstr "" -#: include/enotify.php:279 include/enotify.php:294 +#: include/enotify.php:277 include/enotify.php:292 #, php-format msgid "'%1$s' has accepted your connection request at %2$s" msgstr "" -#: include/enotify.php:280 include/enotify.php:295 +#: include/enotify.php:278 include/enotify.php:293 #, php-format msgid "%2$s has accepted your [url=%1$s]connection request[/url]." msgstr "" -#: include/enotify.php:285 +#: include/enotify.php:283 msgid "" "You are now mutual friends and may exchange status updates, photos, and " "email without restriction." msgstr "" -#: include/enotify.php:287 +#: include/enotify.php:285 #, php-format msgid "Please visit %s if you wish to make any changes to this relationship." msgstr "" -#: include/enotify.php:300 +#: include/enotify.php:298 #, php-format msgid "" "'%1$s' has chosen to accept you a fan, which restricts some forms of " @@ -246,37 +246,37 @@ msgid "" "automatically." msgstr "" -#: include/enotify.php:302 +#: include/enotify.php:300 #, php-format msgid "" "'%1$s' may choose to extend this into a two-way or more permissive " "relationship in the future." msgstr "" -#: include/enotify.php:304 +#: include/enotify.php:302 #, php-format msgid "Please visit %s if you wish to make any changes to this relationship." msgstr "" -#: include/enotify.php:314 mod/removeme.php:63 +#: include/enotify.php:312 mod/removeme.php:63 msgid "[Friendica System Notify]" msgstr "" -#: include/enotify.php:314 +#: include/enotify.php:312 msgid "registration request" msgstr "" -#: include/enotify.php:316 +#: include/enotify.php:314 #, php-format msgid "You've received a registration request from '%1$s' at %2$s" msgstr "" -#: include/enotify.php:317 +#: include/enotify.php:315 #, php-format msgid "You've received a [url=%1$s]registration request[/url] from %2$s." msgstr "" -#: include/enotify.php:322 +#: include/enotify.php:320 #, php-format msgid "" "Full Name:\t%s\n" @@ -284,17 +284,17 @@ msgid "" "Login Name:\t%s (%s)" msgstr "" -#: include/enotify.php:328 +#: include/enotify.php:326 #, php-format msgid "Please visit %s to approve or reject the request." msgstr "" -#: include/enotify.php:588 +#: include/enotify.php:562 #, php-format msgid "%s %s tagged you" msgstr "" -#: include/enotify.php:591 +#: include/enotify.php:565 #, php-format msgid "%s %s shared a new post" msgstr "" @@ -638,7 +638,7 @@ msgstr "" #: mod/events.php:556 src/Content/Widget/VCard.php:98 src/Model/Event.php:86 #: src/Model/Event.php:113 src/Model/Event.php:483 src/Model/Event.php:969 #: src/Model/Profile.php:367 src/Module/Contact.php:565 -#: src/Module/Directory.php:150 src/Module/Notifications/Introductions.php:166 +#: src/Module/Directory.php:150 src/Module/Notifications/Introductions.php:165 #: src/Module/Profile/Profile.php:194 msgid "Location:" msgstr "" @@ -732,13 +732,13 @@ msgstr "" #: mod/follow.php:141 mod/unfollow.php:100 #: src/Module/Admin/Blocklist/Contact.php:100 src/Module/Contact.php:561 -#: src/Module/Notifications/Introductions.php:108 -#: src/Module/Notifications/Introductions.php:177 +#: src/Module/Notifications/Introductions.php:107 +#: src/Module/Notifications/Introductions.php:176 msgid "Profile URL" msgstr "" #: mod/follow.php:142 src/Module/Contact.php:573 -#: src/Module/Notifications/Introductions.php:170 +#: src/Module/Notifications/Introductions.php:169 #: src/Module/Profile/Profile.php:207 msgid "Tags:" msgstr "" @@ -957,8 +957,8 @@ msgstr "" msgid "Message collection failure." msgstr "" -#: mod/message.php:120 src/Module/Notifications/Introductions.php:114 -#: src/Module/Notifications/Introductions.php:149 +#: mod/message.php:120 src/Module/Notifications/Introductions.php:113 +#: src/Module/Notifications/Introductions.php:148 #: src/Module/Notifications/Notification.php:56 msgid "Discard" msgstr "" @@ -3039,8 +3039,8 @@ msgstr "" #: src/Content/Item.php:450 src/Module/Contact.php:545 #: src/Module/Contact.php:805 src/Module/Contact.php:1089 -#: src/Module/Notifications/Introductions.php:113 -#: src/Module/Notifications/Introductions.php:185 +#: src/Module/Notifications/Introductions.php:112 +#: src/Module/Notifications/Introductions.php:184 #: src/Module/Notifications/Notification.php:59 msgid "Ignore" msgstr "" @@ -3246,7 +3246,7 @@ msgid "Friend Requests" msgstr "" #: src/Content/Nav.php:278 src/Module/BaseNotifications.php:139 -#: src/Module/Notifications/Introductions.php:54 +#: src/Module/Notifications/Introductions.php:53 msgid "Notifications" msgstr "" @@ -3564,7 +3564,7 @@ msgid "Matrix:" msgstr "" #: src/Content/Widget/VCard.php:101 src/Model/Profile.php:465 -#: src/Module/Notifications/Introductions.php:180 +#: src/Module/Notifications/Introductions.php:179 msgid "Network:" msgstr "" @@ -4305,59 +4305,6 @@ msgstr "" msgid "Internal Server Error" msgstr "" -#: src/Factory/Notification/Introduction.php:135 -msgid "Friend Suggestion" -msgstr "" - -#: src/Factory/Notification/Introduction.php:161 -msgid "Friend/Connect Request" -msgstr "" - -#: src/Factory/Notification/Introduction.php:161 -msgid "New Follower" -msgstr "" - -#: src/Factory/Notification/Notification.php:101 -#, php-format -msgid "%s created a new post" -msgstr "" - -#: src/Factory/Notification/Notification.php:102 -#: src/Factory/Notification/Notification.php:360 -#, php-format -msgid "%s commented on %s's post" -msgstr "" - -#: src/Factory/Notification/Notification.php:128 -#, php-format -msgid "%s liked %s's post" -msgstr "" - -#: src/Factory/Notification/Notification.php:139 -#, php-format -msgid "%s disliked %s's post" -msgstr "" - -#: src/Factory/Notification/Notification.php:150 -#, php-format -msgid "%s is attending %s's event" -msgstr "" - -#: src/Factory/Notification/Notification.php:161 -#, php-format -msgid "%s is not attending %s's event" -msgstr "" - -#: src/Factory/Notification/Notification.php:172 -#, php-format -msgid "%s may attending %s's event" -msgstr "" - -#: src/Factory/Notification/Notification.php:199 -#, php-format -msgid "%s is now friends with %s" -msgstr "" - #: src/LegacyModule.php:49 #, php-format msgid "Legacy module file not found: %s" @@ -4368,8 +4315,8 @@ msgid "UnFollow" msgstr "" #: src/Model/Contact.php:1090 src/Module/Admin/Users/Pending.php:107 -#: src/Module/Notifications/Introductions.php:111 -#: src/Module/Notifications/Introductions.php:183 +#: src/Module/Notifications/Introductions.php:110 +#: src/Module/Notifications/Introductions.php:182 msgid "Approve" msgstr "" @@ -4588,111 +4535,6 @@ msgstr "" msgid "[no subject]" msgstr "" -#: src/Model/Notification.php:187 -#, php-format -msgid "%1$s wants to follow you" -msgstr "" - -#: src/Model/Notification.php:189 -#, php-format -msgid "%1$s had started following you" -msgstr "" - -#: src/Model/Notification.php:259 -#, php-format -msgid "%1$s liked your comment %2$s" -msgstr "" - -#: src/Model/Notification.php:262 -#, php-format -msgid "%1$s liked your post %2$s" -msgstr "" - -#: src/Model/Notification.php:269 -#, php-format -msgid "%1$s disliked your comment %2$s" -msgstr "" - -#: src/Model/Notification.php:272 -#, php-format -msgid "%1$s disliked your post %2$s" -msgstr "" - -#: src/Model/Notification.php:279 -#, php-format -msgid "%1$s shared your comment %2$s" -msgstr "" - -#: src/Model/Notification.php:282 -#, php-format -msgid "%1$s shared your post %2$s" -msgstr "" - -#: src/Model/Notification.php:289 -#, php-format -msgid "%1$s tagged you on %2$s" -msgstr "" - -#: src/Model/Notification.php:293 -#, php-format -msgid "%1$s replied to you on %2$s" -msgstr "" - -#: src/Model/Notification.php:297 -#, php-format -msgid "%1$s commented in your thread %2$s" -msgstr "" - -#: src/Model/Notification.php:301 -#, php-format -msgid "%1$s commented on your comment %2$s" -msgstr "" - -#: src/Model/Notification.php:307 -#, php-format -msgid "%1$s commented in their thread %2$s" -msgstr "" - -#: src/Model/Notification.php:309 -#, php-format -msgid "%1$s commented in their thread" -msgstr "" - -#: src/Model/Notification.php:311 -#, php-format -msgid "%1$s commented in the thread %2$s from %3$s" -msgstr "" - -#: src/Model/Notification.php:313 -#, php-format -msgid "%1$s commented in the thread from %3$s" -msgstr "" - -#: src/Model/Notification.php:318 -#, php-format -msgid "%1$s commented on your thread %2$s" -msgstr "" - -#: src/Model/Notification.php:323 -#, php-format -msgid "%1$s shared the post %2$s from %3$s" -msgstr "" - -#: src/Model/Notification.php:325 -#, php-format -msgid "%1$s shared a post from %3$s" -msgstr "" - -#: src/Model/Notification.php:327 -#, php-format -msgid "%1$s shared the post %2$s" -msgstr "" - -#: src/Model/Notification.php:329 -#, php-format -msgid "%1$s shared a post" -msgstr "" - #: src/Model/Profile.php:355 src/Module/Profile/Profile.php:256 #: src/Module/Profile/Profile.php:258 msgid "Edit profile" @@ -4708,7 +4550,7 @@ msgid "Homepage:" msgstr "" #: src/Model/Profile.php:371 src/Module/Contact.php:571 -#: src/Module/Notifications/Introductions.php:168 +#: src/Module/Notifications/Introductions.php:167 msgid "About:" msgstr "" @@ -5561,7 +5403,7 @@ msgid "Search in logs" msgstr "" #: src/Module/Admin/Logs/View.php:88 -#: src/Module/Notifications/Notifications.php:138 +#: src/Module/Notifications/Notifications.php:126 msgid "Show all" msgstr "" @@ -7481,7 +7323,7 @@ msgstr "" msgid "Awaiting connection acknowledge" msgstr "" -#: src/Module/Contact.php:553 src/Module/Notifications/Introductions.php:171 +#: src/Module/Contact.php:553 src/Module/Notifications/Introductions.php:170 msgid "Hide this contact from others" msgstr "" @@ -7813,7 +7655,7 @@ msgid "" msgstr "" #: src/Module/Contact/Revoke.php:98 -#: src/Module/Notifications/Introductions.php:123 +#: src/Module/Notifications/Introductions.php:122 #: src/Module/OAuth/Acknowledge.php:47 src/Module/Register.php:117 msgid "Yes" msgstr "" @@ -8696,73 +8538,73 @@ msgstr "" msgid "A Decentralized Social Network" msgstr "" -#: src/Module/Notifications/Introductions.php:78 +#: src/Module/Notifications/Introductions.php:77 msgid "Show Ignored Requests" msgstr "" -#: src/Module/Notifications/Introductions.php:78 +#: src/Module/Notifications/Introductions.php:77 msgid "Hide Ignored Requests" msgstr "" -#: src/Module/Notifications/Introductions.php:94 -#: src/Module/Notifications/Introductions.php:157 +#: src/Module/Notifications/Introductions.php:93 +#: src/Module/Notifications/Introductions.php:156 msgid "Notification type:" msgstr "" -#: src/Module/Notifications/Introductions.php:97 +#: src/Module/Notifications/Introductions.php:96 msgid "Suggested by:" msgstr "" -#: src/Module/Notifications/Introductions.php:122 +#: src/Module/Notifications/Introductions.php:121 msgid "Claims to be known to you: " msgstr "" -#: src/Module/Notifications/Introductions.php:123 +#: src/Module/Notifications/Introductions.php:122 #: src/Module/OAuth/Acknowledge.php:48 src/Module/Register.php:118 msgid "No" msgstr "" -#: src/Module/Notifications/Introductions.php:131 +#: src/Module/Notifications/Introductions.php:130 msgid "Shall your connection be bidirectional or not?" msgstr "" -#: src/Module/Notifications/Introductions.php:132 +#: src/Module/Notifications/Introductions.php:131 #, php-format msgid "" "Accepting %s as a friend allows %s to subscribe to your posts, and you will " "also receive updates from them in your news feed." msgstr "" -#: src/Module/Notifications/Introductions.php:133 +#: src/Module/Notifications/Introductions.php:132 #, php-format msgid "" "Accepting %s as a subscriber allows them to subscribe to your posts, but you " "will not receive updates from them in your news feed." msgstr "" -#: src/Module/Notifications/Introductions.php:135 +#: src/Module/Notifications/Introductions.php:134 msgid "Friend" msgstr "" -#: src/Module/Notifications/Introductions.php:136 +#: src/Module/Notifications/Introductions.php:135 msgid "Subscriber" msgstr "" -#: src/Module/Notifications/Introductions.php:195 +#: src/Module/Notifications/Introductions.php:194 msgid "No introductions." msgstr "" -#: src/Module/Notifications/Introductions.php:196 -#: src/Module/Notifications/Notifications.php:133 +#: src/Module/Notifications/Introductions.php:195 +#: src/Module/Notifications/Notifications.php:121 #, php-format msgid "No more %s notifications." msgstr "" -#: src/Module/Notifications/Notification.php:103 +#: src/Module/Notifications/Notification.php:104 msgid "You must be logged in to show this page." msgstr "" -#: src/Module/Notifications/Notifications.php:50 +#: src/Module/Notifications/Notifications.php:52 msgid "Network Notifications" msgstr "" @@ -8770,15 +8612,15 @@ msgstr "" msgid "System Notifications" msgstr "" -#: src/Module/Notifications/Notifications.php:66 +#: src/Module/Notifications/Notifications.php:64 msgid "Personal Notifications" msgstr "" -#: src/Module/Notifications/Notifications.php:74 +#: src/Module/Notifications/Notifications.php:70 msgid "Home Notifications" msgstr "" -#: src/Module/Notifications/Notifications.php:138 +#: src/Module/Notifications/Notifications.php:126 msgid "Show unread" msgstr "" @@ -10239,6 +10081,164 @@ msgid "" "features and resources." msgstr "" +#: src/Navigation/Notifications/Factory/FormattedNotification.php:89 +#, php-format +msgid "%s liked %s's post" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:101 +#, php-format +msgid "%s disliked %s's post" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:113 +#, php-format +msgid "%s is attending %s's event" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:125 +#, php-format +msgid "%s is not attending %s's event" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:137 +#, php-format +msgid "%s may attending %s's event" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:167 +#, php-format +msgid "%s is now friends with %s" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:334 +#: src/Navigation/Notifications/Factory/FormattedNotification.php:372 +#, php-format +msgid "%s commented on %s's post" +msgstr "" + +#: src/Navigation/Notifications/Factory/FormattedNotification.php:371 +#, php-format +msgid "%s created a new post" +msgstr "" + +#: src/Navigation/Notifications/Factory/Introduction.php:134 +msgid "Friend Suggestion" +msgstr "" + +#: src/Navigation/Notifications/Factory/Introduction.php:160 +msgid "Friend/Connect Request" +msgstr "" + +#: src/Navigation/Notifications/Factory/Introduction.php:160 +msgid "New Follower" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:74 +#, php-format +msgid "%1$s wants to follow you" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:76 +#, php-format +msgid "%1$s had started following you" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:141 +#, php-format +msgid "%1$s liked your comment %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:144 +#, php-format +msgid "%1$s liked your post %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:151 +#, php-format +msgid "%1$s disliked your comment %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:154 +#, php-format +msgid "%1$s disliked your post %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:161 +#, php-format +msgid "%1$s shared your comment %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:164 +#, php-format +msgid "%1$s shared your post %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:171 +#, php-format +msgid "%1$s tagged you on %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:175 +#, php-format +msgid "%1$s replied to you on %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:179 +#, php-format +msgid "%1$s commented in your thread %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:183 +#, php-format +msgid "%1$s commented on your comment %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:189 +#, php-format +msgid "%1$s commented in their thread %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:191 +#, php-format +msgid "%1$s commented in their thread" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:193 +#, php-format +msgid "%1$s commented in the thread %2$s from %3$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:195 +#, php-format +msgid "%1$s commented in the thread from %3$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:200 +#, php-format +msgid "%1$s commented on your thread %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:205 +#, php-format +msgid "%1$s shared the post %2$s from %3$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:207 +#, php-format +msgid "%1$s shared a post from %3$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:209 +#, php-format +msgid "%1$s shared the post %2$s" +msgstr "" + +#: src/Navigation/Notifications/Factory/Notification.php:211 +#, php-format +msgid "%1$s shared a post" +msgstr "" + #: src/Object/EMail/ItemCCEMail.php:42 #, php-format msgid "" diff --git a/view/templates/notifications/attend_item.tpl b/view/templates/notifications/attend_item.tpl index a8de3c793d..07c770fb5e 100644 --- a/view/templates/notifications/attend_item.tpl +++ b/view/templates/notifications/attend_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/templates/notifications/comments_item.tpl b/view/templates/notifications/comments_item.tpl index a8de3c793d..07c770fb5e 100644 --- a/view/templates/notifications/comments_item.tpl +++ b/view/templates/notifications/comments_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/templates/notifications/dislikes_item.tpl b/view/templates/notifications/dislikes_item.tpl index a8de3c793d..07c770fb5e 100644 --- a/view/templates/notifications/dislikes_item.tpl +++ b/view/templates/notifications/dislikes_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/templates/notifications/friends_item.tpl b/view/templates/notifications/friends_item.tpl index 9df1554651..8e0158a0e4 100644 --- a/view/templates/notifications/friends_item.tpl +++ b/view/templates/notifications/friends_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/templates/notifications/likes_item.tpl b/view/templates/notifications/likes_item.tpl index a8de3c793d..07c770fb5e 100644 --- a/view/templates/notifications/likes_item.tpl +++ b/view/templates/notifications/likes_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/templates/notifications/network_item.tpl b/view/templates/notifications/network_item.tpl index e2c0e249d1..36b7dd424f 100644 --- a/view/templates/notifications/network_item.tpl +++ b/view/templates/notifications/network_item.tpl @@ -1,4 +1,4 @@ diff --git a/view/templates/notifications/notification.tpl b/view/templates/notifications/notification.tpl index 4ff7bc7a8b..b5c684cd82 100644 --- a/view/templates/notifications/notification.tpl +++ b/view/templates/notifications/notification.tpl @@ -1,4 +1,4 @@ diff --git a/view/templates/notifications/posts_item.tpl b/view/templates/notifications/posts_item.tpl index a8de3c793d..07c770fb5e 100644 --- a/view/templates/notifications/posts_item.tpl +++ b/view/templates/notifications/posts_item.tpl @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/view/theme/frio/templates/notifications/notify.tpl b/view/theme/frio/templates/notifications/notify.tpl deleted file mode 100644 index 58f3b0da9f..0000000000 --- a/view/theme/frio/templates/notifications/notify.tpl +++ /dev/null @@ -1,12 +0,0 @@ - -