]> git.mxchange.org Git - friendica.git/commitdiff
Add new paradigm classes for notify
authorHypolite Petovan <hypolite@mrpetovan.com>
Sun, 29 Aug 2021 01:55:04 +0000 (21:55 -0400)
committerHypolite Petovan <hypolite@mrpetovan.com>
Sat, 2 Oct 2021 22:15:34 +0000 (18:15 -0400)
- Create BaseDepository class
- Create Entity, Collection, Factory and Depository classes
- Create FormattedNotification Entity, Collection and Factory to remove business logic from Notify repository
- Create new NotificationCreationIntercepted exception to allow addons to cancel notification creation
- Remove unused frio notifications/notify.tpl template

src/BaseCollection.php
src/BaseDepository.php [new file with mode: 0644]
src/Capabilities/ICanCreateFromTableRow.php [new file with mode: 0644]
src/Navigation/Notifications/Collection/FormattedNotifications.php [new file with mode: 0644]
src/Navigation/Notifications/Collection/Notifies.php [new file with mode: 0644]
src/Navigation/Notifications/Depository/Notify.php [new file with mode: 0644]
src/Navigation/Notifications/Entity/Notify.php [new file with mode: 0644]
src/Navigation/Notifications/Exception/NotificationCreationInterceptedException.php [new file with mode: 0644]
src/Navigation/Notifications/Factory/FormattedNotification.php [new file with mode: 0644]
src/Navigation/Notifications/Factory/Notify.php [new file with mode: 0644]
src/Navigation/Notifications/ValueObject/FormattedNotification.php [new file with mode: 0644]

index c4d637a3672642cb604fabdd161ce8de69387fe0..1aa13ae9611403df74bd3078f4b83bcd2989bfa2 100644 (file)
 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 (file)
index 0000000..00d3bcf
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+
+namespace Friendica;
+
+use Exception;
+use Friendica\Capabilities\ICanCreateFromTableRow;
+use Friendica\Database\Database;
+use Friendica\Network\HTTPException\NotFoundException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Depositories are meant to store and retrieve Entities from the database.
+ *
+ * The reason why there are methods prefixed with an underscore is because PHP doesn't support generic polymorphism
+ * which means we can't direcly overload base methods and make parameters more strict (from a parent class to a child
+ * class for example)
+ *
+ * Similarly, we can't make an overloaded method return type more strict until we only support PHP version 7.4 but this
+ * is less pressing.
+ */
+abstract class BaseDepository
+{
+       const LIMIT = 30;
+
+       /**
+        * @var string This should be set to the main database table name the depository is using
+        */
+       protected static $table_name;
+
+       /** @var Database */
+       protected $db;
+
+       /** @var LoggerInterface */
+       protected $logger;
+
+       /** @var ICanCreateFromTableRow */
+       protected $factory;
+
+       public function __construct(Database $database, LoggerInterface $logger, ICanCreateFromTableRow $factory)
+       {
+               $this->db      = $database;
+               $this->logger  = $logger;
+               $this->factory = $factory;
+       }
+
+
+       /**
+        * @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 (file)
index 0000000..bdb6d66
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+namespace Friendica\Capabilities;
+
+use Friendica\BaseEntity;
+
+interface ICanCreateFromTableRow
+{
+       /**
+        * Returns the correcponding Entity given a table row record
+        *
+        * @param array $row
+        * @return BaseEntity
+        */
+       public function createFromTableRow(array $row);
+}
diff --git a/src/Navigation/Notifications/Collection/FormattedNotifications.php b/src/Navigation/Notifications/Collection/FormattedNotifications.php
new file mode 100644 (file)
index 0000000..00179ab
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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/Notifies.php b/src/Navigation/Notifications/Collection/Notifies.php
new file mode 100644 (file)
index 0000000..47fac8d
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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/Notify.php b/src/Navigation/Notifications/Depository/Notify.php
new file mode 100644 (file)
index 0000000..c35f50a
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+namespace Friendica\Navigation\Notifications\Depository;
+
+use Friendica\BaseDepository;
+use Friendica\Core\Hook;
+use Friendica\Database\Database;
+use Friendica\Database\DBA;
+use Friendica\Navigation\Notifications\Collection;
+use Friendica\Navigation\Notifications\Entity;
+use Friendica\Navigation\Notifications\Exception;
+use Friendica\Navigation\Notifications\Factory;
+use Friendica\Network\HTTPException;
+use Friendica\Util\DateTimeFormat;
+use Psr\Log\LoggerInterface;
+
+class Notify extends BaseDepository
+{
+       /** @var Factory\Notify  */
+       protected $factory;
+
+       protected static $table_name = 'notify';
+
+       public function __construct(Database $database, LoggerInterface $logger, Factory\Notify $factory = null)
+       {
+               parent::__construct($database, $logger, $factory ?? new Factory\Notify($logger));
+       }
+
+       /**
+        * @param array $condition
+        * @param array $params
+        * @return Entity\Notify
+        * @throws HTTPException\NotFoundException
+        */
+       private function selectOne(array $condition, array $params = []): Entity\Notify
+       {
+               return parent::_selectOne($condition, $params);
+       }
+
+       private function select(array $condition, array $params = []): Collection\Notifies
+       {
+               return new Collection\Notifies(parent::_select($condition, $params)->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);
+       }
+
+       public function selectAllForUser(int $uid, array $params = []): Collection\Notifies
+       {
+               return $this->selectForUser($uid, [], $params);
+       }
+
+       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/Notify.php b/src/Navigation/Notifications/Entity/Notify.php
new file mode 100644 (file)
index 0000000..88cd8ab
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+namespace Friendica\Navigation\Notifications\Entity;
+
+use DateTime;
+use Friendica\BaseEntity;
+use Friendica\Content\Text\BBCode;
+use Friendica\Core\Renderer;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * @property-read $type
+ * @property-read $name
+ * @property-read $url
+ * @property-read $photo
+ * @property-read $date
+ * @property-read $msg
+ * @property-read $uid
+ * @property-read $link
+ * @property-read $itemId
+ * @property-read $parent
+ * @property-read $seen
+ * @property-read $verb
+ * @property-read $otype
+ * @property-read $name_cache
+ * @property-read $msg_cache
+ * @property-read $uriId
+ * @property-read $parentUriId
+ * @property-read $id
+ */
+class Notify extends BaseEntity
+{
+       /** @var int */
+       protected $type;
+       /** @var string */
+       protected $name;
+       /** @var UriInterface */
+       protected $url;
+       /** @var UriInterface */
+       protected $photo;
+       /** @var DateTime */
+       protected $date;
+       /** @var string */
+       protected $msg;
+       /** @var int */
+       protected $uid;
+       /** @var UriInterface */
+       protected $link;
+       /** @var int */
+       protected $itemId;
+       /** @var int */
+       protected $parent;
+       /** @var bool */
+       protected $seen;
+       /** @var string */
+       protected $verb;
+       /** @var string */
+       protected $otype;
+       /** @var string */
+       protected $name_cache;
+       /** @var string */
+       protected $msg_cache;
+       /** @var int */
+       protected $uriId;
+       /** @var int */
+       protected $parentUriId;
+       /** @var int */
+       protected $id;
+
+       public function __construct(int $type, string $name, UriInterface $url, UriInterface $photo, DateTime $date, int $uid, UriInterface $link, bool $seen, string $verb, string $otype, string $name_cache, string $msg = null, string $msg_cache = null, int $itemId = null, int $uriId = null, int $parent = null, int $parentUriId = null, int $id = null)
+       {
+               $this->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 (file)
index 0000000..5dd7890
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+namespace Friendica\Navigation\Notifications\Exception;
+
+class NotificationCreationInterceptedException extends \Exception
+{
+}
diff --git a/src/Navigation/Notifications/Factory/FormattedNotification.php b/src/Navigation/Notifications/Factory/FormattedNotification.php
new file mode 100644 (file)
index 0000000..4e8c1b8
--- /dev/null
@@ -0,0 +1,376 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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/Navigation/Notifications/Factory/Notify.php b/src/Navigation/Notifications/Factory/Notify.php
new file mode 100644 (file)
index 0000000..87ba7d2
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+namespace Friendica\Navigation\Notifications\Factory;
+
+use Friendica\BaseFactory;
+use Friendica\Capabilities\ICanCreateFromTableRow;
+use Friendica\Content\Text\BBCode;
+use GuzzleHttp\Psr7\Uri;
+
+class Notify extends BaseFactory implements ICanCreateFromTableRow
+{
+       public function createFromTableRow(array $row): \Friendica\Navigation\Notifications\Entity\Notify
+       {
+               return new \Friendica\Navigation\Notifications\Entity\Notify(
+                       $row['type'],
+                       $row['name'],
+                       new Uri($row['url']),
+                       new Uri($row['photo']),
+                       new \DateTime($row['date'], new \DateTimeZone('UTC')),
+                       $row['uid'],
+                       new Uri($row['link']),
+                       $row['seen'],
+                       $row['verb'],
+                       $row['otype'],
+                       $row['name_cache'],
+                       $row['msg'],
+                       $row['msg_cache'],
+                       $row['iid'],
+                       $row['uri-id'],
+                       $row['parent'],
+                       $row['parent-uri-id'],
+                       $row['id']
+               );
+       }
+
+       public function createFromParams($params, $itemlink = null, $item_id = null, $uri_id = null, $parent_id = null, $parent_uri_id = null): \Friendica\Navigation\Notifications\Entity\Notify
+       {
+               return new \Friendica\Navigation\Notifications\Entity\Notify(
+                       $params['type'] ?? '',
+                       $params['source_name'] ?? '',
+                       new Uri($params['source_link'] ?? ''),
+                       new Uri($params['source_photo'] ?? ''),
+                       new \DateTime(),
+                       $params['uid'] ?? 0,
+                       new Uri($itemlink ?? ''),
+                       false,
+                       $params['verb'] ?? '',
+                       $params['otype'] ?? '',
+                       substr(strip_tags(BBCode::convertForUriId($uri_id, $params['source_name'])), 0, 255),
+                       null,
+                       null,
+                       $item_id,
+                       $uri_id,
+                       $parent_id,
+                       $parent_uri_id
+               );
+       }
+}
diff --git a/src/Navigation/Notifications/ValueObject/FormattedNotification.php b/src/Navigation/Notifications/ValueObject/FormattedNotification.php
new file mode 100644 (file)
index 0000000..37d9ae8
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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;
+       }
+}