]> git.mxchange.org Git - friendica.git/commitdiff
ReWork Notification Model/Module/Object/Repository/Factory
authornupplaPhil <admin@philipp.info>
Sat, 25 Jan 2020 01:01:49 +0000 (02:01 +0100)
committernupplaPhil <admin@philipp.info>
Sat, 25 Jan 2020 18:58:22 +0000 (19:58 +0100)
- Introduce Repository for interaction with "notify" table
- Introduce Factory for read-only notification objects (they're just loosely based on notification the table!)
- Introduce Objects for type-safe usage at the presentation layer
- Reworked Model, which is now fully based on the notify table, including generated fields (cache, ..)

17 files changed:
include/api.php
include/enotify.php
mod/ping.php
src/BaseModel.php
src/BaseRepository.php
src/Collection/Notifications.php [new file with mode: 0644]
src/DI.php
src/Factory/Notification/IntroductionFactory.php [new file with mode: 0644]
src/Factory/Notification/NotificationFactory.php [new file with mode: 0644]
src/Model/Notification.php
src/Module/BaseNotifications.php
src/Module/Notifications/Introductions.php
src/Module/Notifications/Notification.php
src/Module/Notifications/Notifications.php
src/Object/Notification/Introduction.php [new file with mode: 0644]
src/Object/Notification/Notification.php [new file with mode: 0644]
src/Repository/Notification.php [new file with mode: 0644]

index 3836caa19d7d72229496d0095b3d21e71ab187ab..cb0599d9a6b0b1294de3d62044bf4703e58b3b62 100644 (file)
@@ -5905,19 +5905,20 @@ function api_friendica_notification($type)
        if ($a->argc!==3) {
                throw new BadRequestException("Invalid argument count");
        }
-       $notes = DI::notification()->getAll([], ['seen' => 'ASC', 'date' => 'DESC'], 50);
+
+       $notifications = DI::notification()->select([], ['order' => ['seen' => 'ASC', 'date' => 'DESC'], 'limit' => 50]);
 
        if ($type == "xml") {
                $xmlnotes = [];
-               if (!empty($notes)) {
-                       foreach ($notes as $note) {
-                               $xmlnotes[] = ["@attributes" => $note];
+               if (!empty($notifications)) {
+                       foreach ($notifications as $notification) {
+                               $xmlnotes[] = ["@attributes" => $notification->toArray()];
                        }
                }
 
-               $notes = $xmlnotes;
+               $notifications = $xmlnotes;
        }
-       return api_format_data("notes", $type, ['note' => $notes]);
+       return api_format_data("notes", $type, ['note' => $notifications->getArrayCopy()]);
 }
 
 /**
@@ -5935,37 +5936,37 @@ function api_friendica_notification($type)
  */
 function api_friendica_notification_seen($type)
 {
-       $a = DI::app();
+       $a         = DI::app();
        $user_info = api_get_user($a);
 
        if (api_user() === false || $user_info === false) {
                throw new ForbiddenException();
        }
-       if ($a->argc!==4) {
+       if ($a->argc !== 4) {
                throw new BadRequestException("Invalid argument count");
        }
 
        $id = (!empty($_REQUEST['id']) ? intval($_REQUEST['id']) : 0);
 
-       $nm = DI::notification();
-       $note = $nm->getByID($id);
-       if (is_null($note)) {
-               throw new BadRequestException("Invalid argument");
-       }
-
-       $nm->setSeen($note);
-       if ($note['otype']=='item') {
-               // would be really better with an ItemsManager and $im->getByID() :-P
-               $item = Item::selectFirstForUser(api_user(), [], ['id' => $note['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);
+       try {
+               $notification = DI::notification()->getByID($id);
+               $notification->setSeen();
+
+               if ($notification->otype == 'item') {
+                       // would be really better with an ItemsManager and $im->getByID() :-P
+                       $item = Item::selectFirstForUser(api_user(), [], ['id' => $notification->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);
+                       }
+                       // the item can't be found, but we set the notification as seen, so we count this as a success
                }
-               // the item can't be found, but we set the note as seen, so we count this as a success
+               return api_format_data('result', $type, ['result' => "success"]);
+       } catch (NotFoundException $e) {
+               throw new BadRequestException('Invalid argument');
        }
-       return api_format_data('result', $type, ['result' => "success"]);
 }
 
 /// @TODO move to top of file or somewhere better
index 403688af6d2831cbac97c8039580f2f118d0ae49..32f32afec8fc081a49e947d94778930220be7b09 100644 (file)
@@ -482,47 +482,25 @@ function notification($params)
        $notify_id = 0;
 
        if ($show_in_notification_page) {
-               Logger::log("adding notification entry", Logger::DEBUG);
-
-               /// @TODO One statement is enough
-               $datarray = [];
-               $datarray['name']  = $params['source_name'];
-               $datarray['name_cache'] = strip_tags(BBCode::convert($params['source_name']));
-               $datarray['url']   = $params['source_link'];
-               $datarray['photo'] = $params['source_photo'];
-               $datarray['date']  = DateTimeFormat::utcNow();
-               $datarray['uid']   = $params['uid'];
-               $datarray['link']  = $itemlink;
-               $datarray['iid']   = $item_id;
-               $datarray['parent'] = $parent_id;
-               $datarray['type']  = $params['type'];
-               $datarray['verb']  = $params['verb'];
-               $datarray['otype'] = $params['otype'];
-               $datarray['abort'] = false;
-
-               Hook::callAll('enotify_store', $datarray);
-
-               if ($datarray['abort']) {
-                       return false;
-               }
-
-               // create notification entry in DB
-               $fields = ['name' => $datarray['name'], 'url' => $datarray['url'],
-                       'photo' => $datarray['photo'], 'date' => $datarray['date'], 'uid' => $datarray['uid'],
-                       'link' => $datarray['link'], 'iid' => $datarray['iid'], 'parent' => $datarray['parent'],
-                       'type' => $datarray['type'], 'verb' => $datarray['verb'], 'otype' => $datarray['otype'],
-                       'name_cache' => $datarray["name_cache"]];
-               DBA::insert('notify', $fields);
+               $notification = DI::notification()->insert([
+                       'name'   => $params['source_name'],
+                       'url'    => $params['source_link'],
+                       'photo'  => $params['source_photo'],
+                       'uid'    => $params['uid'],
+                       'iid'    => $item_id,
+                       'parent' => $parent_id,
+                       'type'   => $params['type'],
+                       'verb'   => $params['verb'],
+                       'otype'  => $params['otype'],
+               ]);
 
-               $notify_id = DBA::lastInsertId();
+               $notification->link = DI::baseUrl() . '/notification/view/' . $notification->id;
+               $notification->msg  = Renderer::replaceMacros($epreamble, ['$itemlink' => $notification->link]);
 
-               $itemlink = DI::baseUrl().'/notification/view/'.$notify_id;
-               $msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $itemlink]);
-               $msg_cache = format_notification_message($datarray['name_cache'], strip_tags(BBCode::convert($msg)));
+               DI::notification()->update($notification);
 
-               $fields = ['msg' => $msg, 'msg_cache' => $msg_cache];
-               $condition = ['id' => $notify_id, 'uid' => $params['uid']];
-               DBA::update('notify', $fields, $condition);
+               $itemlink  = $notification->link;
+               $notify_id = $notification->id;
        }
 
        // send email notification if notification preferences permit
@@ -732,27 +710,3 @@ function check_item_notification($itemid, $uid, $notification_type) {
 
        notification($params);
 }
-
-/**
- * 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
- */
-function format_notification_message($name, $message) {
-       if ($name != '') {
-               $pos = strpos($message, $name);
-       } else {
-               $pos = false;
-       }
-
-       if ($pos !== false) {
-               $message = substr_replace($message, '{0}', $pos, strlen($name));
-       }
-
-       return $message;
-}
index 0c758bd4f8ab1640f3b1dd8dab112bc906abbf1d..a786d379f2493f911317a8432514b4f3a50a1e60 100644 (file)
@@ -433,7 +433,7 @@ function ping_get_notifications($uid)
                                $notification["message"] = $notification["msg_cache"];
                        } else {
                                $notification["name"] = strip_tags(BBCode::convert($notification["name"]));
-                               $notification["message"] = format_notification_message($notification["name"], strip_tags(BBCode::convert($notification["msg"])));
+                               $notification["message"] = Friendica\Model\Notification::formatMessage($notification["name"], strip_tags(BBCode::convert($notification["msg"])));
 
                                q(
                                        "UPDATE `notify` SET `name_cache` = '%s', `msg_cache` = '%s' WHERE `id` = %d",
index b2dc7eedaf50ad58b88da114134f2d4671b79852..4e4259170872ea23977c24ef7a425f52b4675575 100644 (file)
@@ -53,6 +53,11 @@ abstract class BaseModel
                return $this->originalData;
        }
 
+       public function resetOriginalData()
+       {
+               $this->originalData = $this->data;
+       }
+
        /**
         * Performance-improved model creation in a loop
         *
index c0bcab18f92fbafc37a8e96b23561eecb1899c66..cce1c50c17bc77f1cdc905a30297b8686f0c7b28 100644 (file)
@@ -122,7 +122,12 @@ abstract class BaseRepository extends BaseFactory
         */
        public function update(BaseModel $model)
        {
-               return $this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], $model->getOriginalData());
+               if ($this->dba->update(static::$table_name, $model->toArray(), ['id' => $model->id], $model->getOriginalData())) {
+                       $model->resetOriginalData();
+                       return true;
+               }
+
+               return false;
        }
 
        /**
diff --git a/src/Collection/Notifications.php b/src/Collection/Notifications.php
new file mode 100644 (file)
index 0000000..1c04bdf
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+namespace Friendica\Collection;
+
+use Friendica\BaseCollection;
+use Friendica\Model;
+
+class Notifications extends BaseCollection
+{
+       /**
+        * @return Model\Notification
+        */
+       public function current()
+       {
+               return parent::current();
+       }
+}
index 530d79580cc64a6d3afc7e35d25e408a7b8f819e..8f80bf68dde12bc765462f790b7eb5d66a912558 100644 (file)
@@ -244,6 +244,22 @@ abstract class DI
                return self::$dice->create(Factory\Mastodon\Relationship::class);
        }
 
+       /**
+        * @return \Friendica\Factory\Notification\NotificationFactory
+        */
+       public static function factNotification()
+       {
+               return self::$dice->create(Factory\Notification\NotificationFactory::class);
+       }
+
+       /**
+        * @return \Friendica\Factory\Notification\IntroductionFactory
+        */
+       public static function factNotIntro()
+       {
+               return self::$dice->create(Factory\Notification\IntroductionFactory::class);
+       }
+
        //
        // "Model" namespace instances
        //
@@ -257,11 +273,11 @@ abstract class DI
        }
 
        /**
-        * @return Model\Notification
+        * @return Repository\Notification
         */
        public static function notification()
        {
-               return self::$dice->create(Model\Notification::class);
+               return self::$dice->create(Repository\Notification::class);
        }
 
        /**
diff --git a/src/Factory/Notification/IntroductionFactory.php b/src/Factory/Notification/IntroductionFactory.php
new file mode 100644 (file)
index 0000000..73f2883
--- /dev/null
@@ -0,0 +1,205 @@
+<?php
+
+namespace Friendica\Factory\Notification;
+
+use Exception;
+use Friendica\App;
+use Friendica\App\BaseURL;
+use Friendica\BaseFactory;
+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\Module\BaseNotifications;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Object\Notification\Introduction;
+use Friendica\Util\Proxy;
+use Psr\Log\LoggerInterface;
+
+class IntroductionFactory extends BaseFactory
+{
+       /** @var Database */
+       private $dba;
+       /** @var BaseURL */
+       private $baseUrl;
+       /** @var L10n */
+       private $l10n;
+       /** @var IPConfig */
+       private $pConfig;
+       /** @var ISession */
+       private $session;
+       /** @var string */
+       private $nick;
+
+       public function __construct(LoggerInterface $logger, Database $dba, BaseURL $baseUrl, L10n $l10n, App $app, IPConfig $pConfig, ISession $session)
+       {
+               parent::__construct($logger);
+
+               $this->dba          = $dba;
+               $this->baseUrl      = $baseUrl;
+               $this->l10n         = $l10n;
+               $this->pConfig      = $pConfig;
+               $this->session      = $session;
+               $this->nick         = $app->user['nickname'] ?? '';
+       }
+
+       /**
+        * Get introductions
+        *
+        * @param bool $all     If false only include introductions into the query
+        *                      which aren't marked as ignored
+        * @param int  $start   Start the query at this point
+        * @param int  $limit   Maximum number of query results
+        * @param int  $id      When set, only the introduction with this id is displayed
+        *
+        * @return Introduction[]
+        */
+       public function getIntroList(bool $all = false, int $start = 0, int $limit = BaseNotifications::DEFAULT_PAGE_LIMIT, int $id = 0)
+       {
+               $sql_extra     = "";
+
+               if (empty($id)) {
+                       if (!$all) {
+                               $sql_extra = " AND NOT `ignore` ";
+                       }
+
+                       $sql_extra .= " AND NOT `intro`.`blocked` ";
+               } else {
+                       $sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id));
+               }
+
+               $formattedNotifications = [];
+
+               try {
+                       /// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact
+                       $stmtNotifications = $this->dba->p(
+                               "SELECT `intro`.`id` AS `intro_id`, `intro`.*, `contact`.*,
+                               `fcontact`.`name` AS `fname`, `fcontact`.`url` AS `furl`, `fcontact`.`addr` AS `faddr`,
+                               `fcontact`.`photo` AS `fphoto`, `fcontact`.`request` AS `frequest`,
+                               `gcontact`.`location` AS `glocation`, `gcontact`.`about` AS `gabout`,
+                               `gcontact`.`keywords` AS `gkeywords`, `gcontact`.`gender` AS `ggender`,
+                               `gcontact`.`network` AS `gnetwork`, `gcontact`.`addr` AS `gaddr`
+                       FROM `intro`
+                               LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id`
+                               LEFT JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
+                               LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id`
+                       WHERE `intro`.`uid` = ? $sql_extra
+                       LIMIT ?, ?",
+                               $_SESSION['uid'],
+                               $start,
+                               $limit
+                       );
+
+                       while ($notification = $this->dba->fetch($stmtNotifications)) {
+                               // 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'] ?? '') {
+                                       $return_addr = bin2hex($this->nick . '@' .
+                                                              $this->baseUrl->getHostName() .
+                                                              (($this->baseUrl->getURLPath()) ? '/' . $this->baseUrl->getURLPath() : ''));
+
+                                       $formattedNotifications[] = new 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'          => (!empty($notification['fphoto']) ? Proxy::proxifyUrl($notification['fphoto'], false, Proxy::SIZE_SMALL) : "images/person-300.jpg"),
+                                               'name'           => $notification['fname'],
+                                               'url'            => $notification['furl'],
+                                               'zrl'            => Contact::magicLink($notification['furl']),
+                                               'hidden'         => $notification['hidden'] == 1,
+                                               'post_newfriend' => (intval($this->pConfig->get(local_user(), 'system', 'post_newfriend')) ? '1' : 0),
+                                               'note'           => $notification['note'],
+                                               'request'        => $notification['frequest'] . '?addr=' . $return_addr]);
+
+                                       // Normal connection requests
+                               } else {
+                                       $notification = $this->getMissingIntroData($notification);
+
+                                       if (empty($notification['url'])) {
+                                               continue;
+                                       }
+
+                                       // Don't show these data until you are connected. Diaspora is doing the same.
+                                       if ($notification['gnetwork'] === Protocol::DIASPORA) {
+                                               $notification['glocation'] = "";
+                                               $notification['gabout']    = "";
+                                               $notification['ggender']   = "";
+                                       }
+
+                                       $formattedNotifications[] = new 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'],
+                                               'uid'            => $this->session->get('uid'),
+                                               'intro_id'       => $notification['intro_id'],
+                                               'contact_id'     => $notification['contact-id'],
+                                               'photo'          => (!empty($notification['photo']) ? Proxy::proxifyUrl($notification['photo'], false, Proxy::SIZE_SMALL) : "images/person-300.jpg"),
+                                               'name'           => $notification['name'],
+                                               'location'       => BBCode::convert($notification['glocation'], false),
+                                               'about'          => BBCode::convert($notification['gabout'], false),
+                                               'keywords'       => $notification['gkeywords'],
+                                               'gender'         => $notification['ggender'],
+                                               'hidden'         => $notification['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['gaddr'],
+                                               'network'        => $notification['gnetwork'],
+                                               'knowyou'        => $notification['knowyou'],
+                                               'note'           => $notification['note'],
+                                       ]);
+                               }
+                       }
+               } catch (Exception $e) {
+                       $this->logger->warning('Select failed.', ['uid' => $_SESSION['uid'], 'exception' => $e]);
+               }
+
+               return $formattedNotifications;
+       }
+
+       /**
+        * Check for missing contact data and try to fetch the data from
+        * from other sources
+        *
+        * @param array $intro The input array with the intro data
+        *
+        * @return array The array with the intro data
+        *
+        * @throws InternalServerErrorException
+        */
+       private function getMissingIntroData(array $intro)
+       {
+               // If the network and the addr isn't available from the gcontact
+               // table entry, take the one of the contact table entry
+               if (empty($intro['gnetwork']) && !empty($intro['network'])) {
+                       $intro['gnetwork'] = $intro['network'];
+               }
+               if (empty($intro['gaddr']) && !empty($intro['addr'])) {
+                       $intro['gaddr'] = $intro['addr'];
+               }
+
+               // If the network and addr is still not available
+               // get the missing data data from other sources
+               if (empty($intro['gnetwork']) || empty($intro['gaddr'])) {
+                       $ret = Contact::getDetailsByURL($intro['url']);
+
+                       if (empty($intro['gnetwork']) && !empty($ret['network'])) {
+                               $intro['gnetwork'] = $ret['network'];
+                       }
+                       if (empty($intro['gaddr']) && !empty($ret['addr'])) {
+                               $intro['gaddr'] = $ret['addr'];
+                       }
+               }
+
+               return $intro;
+       }
+}
diff --git a/src/Factory/Notification/NotificationFactory.php b/src/Factory/Notification/NotificationFactory.php
new file mode 100644 (file)
index 0000000..2516916
--- /dev/null
@@ -0,0 +1,355 @@
+<?php
+
+namespace Friendica\Factory\Notification;
+
+use Exception;
+use Friendica\App;
+use Friendica\App\BaseURL;
+use Friendica\BaseFactory;
+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\Item;
+use Friendica\Module\BaseNotifications;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Protocol\Activity;
+use Friendica\Repository\Notification;
+use Friendica\Util\DateTimeFormat;
+use Friendica\Util\Proxy;
+use Friendica\Util\Temporal;
+use Friendica\Util\XML;
+use Psr\Log\LoggerInterface;
+
+class NotificationFactory extends BaseFactory
+{
+       /** @var Database */
+       private $dba;
+       /** @var Notification */
+       private $notification;
+       /** @var BaseURL */
+       private $baseUrl;
+       /** @var L10n */
+       private $l10n;
+       /** @var string */
+       private $nurl;
+
+       public function __construct(LoggerInterface $logger, Database $dba, 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;
+               $this->nurl         = $app->contact['nurl'] ?? '';
+       }
+
+       /**
+        * 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['id'] == $item['parent']) ? 'post' : 'comment');
+               $item['link']  = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
+               $item['image'] = Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO);
+               $item['url']   = $item['author-link'];
+               $item['text']  = (($item['id'] == $item['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(
+                                       'like',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s liked %s's post", $item['author-name'], $item['parent-author-name']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       case Activity::DISLIKE:
+                               return new \Friendica\Object\Notification\Notification(
+                                       'dislike',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s disliked %s's post", $item['author-name'], $item['parent-author-name']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       case Activity::ATTEND:
+                               return new \Friendica\Object\Notification\Notification(
+                                       'attend',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s is attending %s's event", $item['author-name'], $item['parent-author-name']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       case Activity::ATTENDNO:
+                               return new \Friendica\Object\Notification\Notification(
+                                       'attendno',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s is not attending %s's event", $item['author-name'], $item['parent-author-name']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       case Activity::ATTENDMAYBE:
+                               return new \Friendica\Object\Notification\Notification(
+                                       'attendmaybe',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s may attending %s's event", $item['author-name'], $item['parent-author-name']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       case Activity::FRIEND:
+                               if (!isset($item['object'])) {
+                                       return new \Friendica\Object\Notification\Notification(
+                                               'friend',
+                                               $item['link'],
+                                               $item['image'],
+                                               $item['url'],
+                                               $item['text'],
+                                               $item['when'] ?? '',
+                                               $item['ago'] ?? '',
+                                               $item['seen'] ?? false);
+                               }
+
+                               $xmlHead       = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
+                               $obj           = XML::parseString($xmlHead . $item['object']);
+                               $item['fname'] = $obj->title;
+
+                               return new \Friendica\Object\Notification\Notification(
+                                       'friend',
+                                       $this->baseUrl->get(true) . '/display/' . $item['parent-guid'],
+                                       Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO),
+                                       $item['author-link'],
+                                       $this->l10n->t("%s is now friends with %s", $item['author-name'], $item['fname']),
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+
+                       default:
+                               return new \Friendica\Object\Notification\Notification(
+                                       $item['label'],
+                                       $item['link'],
+                                       $item['image'],
+                                       $item['url'],
+                                       $item['text'],
+                                       $item['when'] ?? '',
+                                       $item['ago'] ?? '',
+                                       $item['seen'] ?? false);
+                               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(
+                                       'notification',
+                                       $this->baseUrl->get(true) . '/notification/view/' . $notification->id,
+                                       Proxy::proxifyUrl($notification->photo, false, Proxy::SIZE_MICRO),
+                                       $notification->url,
+                                       strip_tags(BBCode::convert($notification->msg)),
+                                       DateTimeFormat::local($notification->date, 'r'),
+                                       Temporal::getRelativeDate($notification->date),
+                                       $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'];
+               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
+
+               $formattedNotifications = [];
+
+               try {
+                       $items = Item::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)
+       {
+               $myUrl    = str_replace('http://', '', $this->nurl);
+               $diaspUrl = str_replace('/profile/', '/u/', $myUrl);
+
+               $condition = ["NOT `wall` AND `uid` = ? AND (`item`.`author-id` = ? OR `item`.`tag` REGEXP ? OR `item`.`tag` REGEXP ?)",
+                       local_user(), public_contact(), $myUrl . '\\]', $diaspUrl . '\\]'];
+
+               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'];
+               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
+
+               $formattedNotifications = [];
+
+               try {
+                       $items = Item::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'];
+               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
+
+               $formattedNotifications = [];
+
+               try {
+                       $items = Item::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;
+       }
+}
index e1848667006ecd5cdd2136b4e3223bddb11b7fe9..ab029f73b6b4416fd0182e9979bac47c3627e314 100644 (file)
 namespace Friendica\Model;
 
 use Exception;
-use Friendica\App;
+use Friendica\BaseModel;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\HTML;
-use Friendica\Core\PConfig\IPConfig;
-use Friendica\Core\L10n;
-use Friendica\Core\Protocol;
-use Friendica\Core\System;
 use Friendica\Database\Database;
-use Friendica\DI;
-use Friendica\Protocol\Activity;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Proxy as ProxyUtils;
 use Friendica\Util\Temporal;
-use Friendica\Util\XML;
-use ImagickException;
 use Psr\Log\LoggerInterface;
-use Friendica\Network\HTTPException;
 
 /**
- * Methods for read and write notifications from/to database
- *  or for formatting notifications
+ * Model for an entry in the notify table
+ * - Including additional calculated properties
+ *
+ * @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.
+ *
+ * @property-read integer timestamp  Unix timestamp
+ * @property-read string  dateRel       Time since the note was posted, eg "1 hour ago"
+ * @property-read string  $msg_html
+ * @property-read string  $msg_plain
  */
-final class Notification
+class Notification extends BaseModel
 {
-       /** @var int The default limit of notifications per page */
-       const DEFAULT_PAGE_LIMIT = 80;
+       /** @var \Friendica\Repository\Notification */
+       private $repo;
+       /** @var $this */
+       private $parentInst;
 
-       const NETWORK  = 'network';
-       const SYSTEM   = 'system';
-       const PERSONAL = 'personal';
-       const HOME     = 'home';
-       const INTRO    = 'intro';
-
-       /** @var Database */
-       private $dba;
-       /** @var L10n */
-       private $l10n;
-       /** @var App\Arguments */
-       private $args;
-       /** @var App\BaseURL */
-       private $baseUrl;
-       /** @var IPConfig */
-       private $pConfig;
-       /** @var LoggerInterface */
-       private $logger;
-
-       public function __construct(Database $dba, L10n $l10n, App\Arguments $args, App\BaseURL $baseUrl,
-                                   IPConfig $pConfig, LoggerInterface $logger)
+       public function __construct(Database $dba, LoggerInterface $logger, \Friendica\Repository\Notification $repo, array $data = [])
        {
-               $this->dba     = $dba;
-               $this->l10n    = $l10n;
-               $this->args    = $args;
-               $this->baseUrl = $baseUrl;
-               $this->pConfig = $pConfig;
-               $this->logger  = $logger;
-       }
+               parent::__construct($dba, $logger, $data);
 
-       /**
-        * Set some extra properties to note array from db:
-        *  - timestamp as int in default TZ
-        *  - date_rel : relative date string
-        *  - msg_html: message as html string
-        *  - msg_plain: message as plain text string
-        *
-        * @param array $notes array of note arrays from db
-        *
-        * @return array Copy of input array with added properties
-        *
-        * @throws Exception
-        */
-       private function setExtra(array $notes)
-       {
-               $retNotes = [];
-               foreach ($notes as $note) {
-                       $local_time        = DateTimeFormat::local($note['date']);
-                       $note['timestamp'] = strtotime($local_time);
-                       $note['date_rel']  = Temporal::getRelativeDate($note['date']);
-                       $note['msg_html']  = BBCode::convert($note['msg'], false);
-                       $note['msg_plain'] = explode("\n", trim(HTML::toPlaintext($note['msg_html'], 0)))[0];
+               $this->repo = $repo;
 
-                       $retNotes[] = $note;
-               }
-               return $retNotes;
+               $this->setNameCache();
+               $this->setTimestamp();
+               $this->setMsg();
        }
 
        /**
-        * Get all notifications for local_user()
+        * Set the notification as seen
         *
-        * @param array  $filter optional Array "column name"=>value: filter query by columns values
-        * @param array  $order  optional Array to order by
-        * @param string $limit  optional Query limits
+        * @param bool $seen true, if seen
         *
-        * @return array|bool of results or false on errors
-        * @throws Exception
+        * @return bool True, if the seen state could be saved
         */
-       public function getAll(array $filter = [], array $order = ['date' => 'DESC'], string $limit = "")
+       public function setSeen(bool $seen = true)
        {
-               $params = [];
-
-               $params['order'] = $order;
-
-               if (!empty($limit)) {
-                       $params['limit'] = $limit;
+               $this->seen = $seen;
+               try {
+                       return $this->repo->update($this);
+               } catch (Exception $e) {
+                       $this->logger->warning('Update failed.', ['$this' => $this, 'exception' => $e]);
+                       return false;
                }
-
-               $dbFilter = array_merge($filter, ['uid' => local_user()]);
-
-               $stmtNotifications = $this->dba->select('notify', [], $dbFilter, $params);
-
-               if ($this->dba->isResult($stmtNotifications)) {
-                       return $this->setExtra($this->dba->toArray($stmtNotifications));
-               }
-
-               return false;
        }
 
        /**
-        * Get one note for local_user() by $id value
-        *
-        * @param int $id identity
-        *
-        * @return array note values or null if not found
-        * @throws Exception
+        * Set some extra properties to the notification from db:
+        *  - timestamp as int in default TZ
+        *  - date_rel : relative date string
         */
-       public function getByID(int $id)
+       private function setTimestamp()
        {
-               $stmtNotify = $this->dba->selectFirst('notify', [], ['id' => $id, 'uid' => local_user()]);
-               if ($this->dba->isResult($stmtNotify)) {
-                       return $this->setExtra([$stmtNotify])[0];
+               try {
+                       $this->timestamp = strtotime(DateTimeFormat::local($this->date));
+               } catch (Exception $e) {
                }
-               return null;
+               $this->dateRel = Temporal::getRelativeDate($this->date);
        }
 
        /**
-        * set seen state of $note of local_user()
+        * Sets the pre-formatted name (caching)
         *
-        * @param array $note note array
-        * @param bool  $seen optional true or false, default true
-        *
-        * @return bool true on success, false on errors
-        * @throws Exception
+        * @throws InternalServerErrorException
         */
-       public function setSeen(array $note, bool $seen = true)
+       private function setNameCache()
        {
-               return $this->dba->update('notify', ['seen' => $seen], [
-                       '(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?',
-                       $note['link'],
-                       $note['parent'],
-                       $note['otype'],
-                       local_user()
-               ]);
+               $this->name_cache = strip_tags(BBCode::convert($this->source_name ?? ''));
        }
 
        /**
-        * Set seen state of all notifications of local_user()
-        *
-        * @param bool $seen optional true or false. default true
-        *
-        * @return bool true on success, false on error
-        * @throws Exception
-        */
-       public function setAllSeen(bool $seen = true)
-       {
-               return $this->dba->update('notify', ['seen' => $seen], ['uid' => local_user()]);
-       }
-
-       /**
-        * Format the notification query in an usable array
-        *
-        * @param array  $notifications The array from the db query
-        * @param string $ident         The notifications identifier (e.g. network)
-        *
-        * @return array
-        *                       string 'label' => The type of the notification
-        *                       string 'link' => URL to the source
-        *                       string 'image' => The avatar image
-        *                       string 'url' => The profile url of the contact
-        *                       string 'text' => The notification text
-        *                       string 'when' => The date of the notification
-        *                       string 'ago' => T relative date of the notification
-        *                       bool 'seen' => Is the notification marked as "seen"
-        * @throws Exception
+        * Set some extra properties to the notification from db:
+        *  - msg_html: message as html string
+        *  - msg_plain: message as plain text string
+        *  - msg_cache: The pre-formatted message (caching)
         */
-       private function formatList(array $notifications, string $ident = "")
+       private function setMsg()
        {
-               $formattedNotifications = [];
-
-               foreach ($notifications as $notification) {
-                       // Because we use different db tables for the notification query
-                       // we have sometimes $notification['unseen'] and sometimes $notification['seen].
-                       // So we will have to transform $notification['unseen']
-                       if (array_key_exists('unseen', $notification)) {
-                               $notification['seen'] = ($notification['unseen'] > 0 ? false : true);
-                       }
-
-                       // For feed items we use the user's contact, since the avatar is mostly self choosen.
-                       if (!empty($notification['network']) && $notification['network'] == Protocol::FEED) {
-                               $notification['author-avatar'] = $notification['contact-avatar'];
-                       }
-
-                       // Depending on the identifier of the notification we need to use different defaults
-                       switch ($ident) {
-                               case self::SYSTEM:
-                                       $default_item_label = 'notification';
-                                       $default_item_link  = $this->baseUrl->get(true) . '/notification/view/' . $notification['id'];
-                                       $default_item_image = ProxyUtils::proxifyUrl($notification['photo'], false, ProxyUtils::SIZE_MICRO);
-                                       $default_item_url   = $notification['url'];
-                                       $default_item_text  = strip_tags(BBCode::convert($notification['msg']));
-                                       $default_item_when  = DateTimeFormat::local($notification['date'], 'r');
-                                       $default_item_ago   = Temporal::getRelativeDate($notification['date']);
-                                       break;
-
-                               case self::HOME:
-                                       $default_item_label = 'comment';
-                                       $default_item_link  = $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'];
-                                       $default_item_image = ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO);
-                                       $default_item_url   = $notification['author-link'];
-                                       $default_item_text  = $this->l10n->t("%s commented on %s's post", $notification['author-name'], $notification['parent-author-name']);
-                                       $default_item_when  = DateTimeFormat::local($notification['created'], 'r');
-                                       $default_item_ago   = Temporal::getRelativeDate($notification['created']);
-                                       break;
-
-                               default:
-                                       $default_item_label = (($notification['id'] == $notification['parent']) ? 'post' : 'comment');
-                                       $default_item_link  = $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'];
-                                       $default_item_image = ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO);
-                                       $default_item_url   = $notification['author-link'];
-                                       $default_item_text  = (($notification['id'] == $notification['parent'])
-                                               ? $this->l10n->t("%s created a new post", $notification['author-name'])
-                                               : $this->l10n->t("%s commented on %s's post", $notification['author-name'], $notification['parent-author-name']));
-                                       $default_item_when  = DateTimeFormat::local($notification['created'], 'r');
-                                       $default_item_ago   = Temporal::getRelativeDate($notification['created']);
-                       }
-
-                       // Transform the different types of notification in an usable array
-                       switch ($notification['verb']) {
-                               case Activity::LIKE:
-                                       $formattedNotification = [
-                                               'label' => 'like',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s liked %s's post", $notification['author-name'], $notification['parent-author-name']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               case Activity::DISLIKE:
-                                       $formattedNotification = [
-                                               'label' => 'dislike',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s disliked %s's post", $notification['author-name'], $notification['parent-author-name']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               case Activity::ATTEND:
-                                       $formattedNotification = [
-                                               'label' => 'attend',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s is attending %s's event", $notification['author-name'], $notification['parent-author-name']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               case Activity::ATTENDNO:
-                                       $formattedNotification = [
-                                               'label' => 'attendno',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s is not attending %s's event", $notification['author-name'], $notification['parent-author-name']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               case Activity::ATTENDMAYBE:
-                                       $formattedNotification = [
-                                               'label' => 'attendmaybe',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s may attend %s's event", $notification['author-name'], $notification['parent-author-name']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               case Activity::FRIEND:
-                                       if (!isset($notification['object'])) {
-                                               $formattedNotification = [
-                                                       'label' => 'friend',
-                                                       'link'  => $default_item_link,
-                                                       'image' => $default_item_image,
-                                                       'url'   => $default_item_url,
-                                                       'text'  => $default_item_text,
-                                                       'when'  => $default_item_when,
-                                                       'ago'   => $default_item_ago,
-                                                       'seen'  => $notification['seen']
-                                               ];
-                                               break;
-                                       }
-                                       /// @todo Check if this part here is used at all
-                                       $this->logger->info('Complete data.', ['notification' => $notification, 'callStack' => System::callstack(20)]);
-
-                                       $xmlHead               = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
-                                       $obj                   = XML::parseString($xmlHead . $notification['object']);
-                                       $notification['fname'] = $obj->title;
-
-                                       $formattedNotification = [
-                                               'label' => 'friend',
-                                               'link'  => $this->baseUrl->get(true) . '/display/' . $notification['parent-guid'],
-                                               'image' => ProxyUtils::proxifyUrl($notification['author-avatar'], false, ProxyUtils::SIZE_MICRO),
-                                               'url'   => $notification['author-link'],
-                                               'text'  => $this->l10n->t("%s is now friends with %s", $notification['author-name'], $notification['fname']),
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                                       break;
-
-                               default:
-                                       $formattedNotification = [
-                                               'label' => $default_item_label,
-                                               'link'  => $default_item_link,
-                                               'image' => $default_item_image,
-                                               'url'   => $default_item_url,
-                                               'text'  => $default_item_text,
-                                               'when'  => $default_item_when,
-                                               'ago'   => $default_item_ago,
-                                               'seen'  => $notification['seen']
-                                       ];
-                       }
-
-                       $formattedNotifications[] = $formattedNotification;
+               try {
+                       $this->msg_html  = BBCode::convert($this->msg, false);
+                       $this->msg_plain = explode("\n", trim(HTML::toPlaintext($this->msg_html, 0)))[0];
+                       $this->msg_cache = self::formatMessage($this->name_cache, strip_tags(BBCode::convert($this->msg)));
+               } catch (InternalServerErrorException $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 array [string, array]
-        *    string 'ident' => Notification identifier
-        *    array 'notifications' => Network notifications
-        *
-        * @throws Exception
-        */
-       public function getNetworkList(bool $seen = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT)
+       public function __get($name)
        {
-               $ident         = self::NETWORK;
-               $notifications = [];
+               $this->checkValid();
 
-               $condition = ['wall' => false, 'uid' => local_user()];
+               $return = null;
 
-               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'];
-               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
+               switch ($name) {
+                       case 'parent':
+                               if (!empty($this->parent)) {
+                                       $this->parentInst = $this->parentInst ?? $this->repo->getByID($this->parent);
 
-               $items = Item::selectForUser(local_user(), $fields, $condition, $params);
-
-               if ($this->dba->isResult($items)) {
-                       $notifications = $this->formatList(Item::inArray($items), $ident);
-               }
-
-               $arr = [
-                       'notifications' => $notifications,
-                       'ident'         => $ident,
-               ];
-
-               return $arr;
-       }
-
-       /**
-        * 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 array [string, array]
-        *    string 'ident' => Notification identifier
-        *    array 'notifications' => System notifications
-        *
-        * @throws Exception
-        */
-       public function getSystemList(bool $seen = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT)
-       {
-               $ident         = self::SYSTEM;
-               $notifications = [];
-
-               $filter = ['uid' => local_user()];
-               if (!$seen) {
-                       $filter['seen'] = false;
-               }
-
-               $params          = [];
-               $params['order'] = ['date' => 'DESC'];
-               $params['limit'] = [$start, $limit];
-
-               $stmtNotifications = $this->dba->select('notify',
-                       ['id', 'url', 'photo', 'msg', 'date', 'seen', 'verb'],
-                       $filter,
-                       $params);
-
-               if ($this->dba->isResult($stmtNotifications)) {
-                       $notifications = $this->formatList($this->dba->toArray($stmtNotifications), $ident);
+                                       $return = $this->parentInst;
+                               }
+                               break;
+                       default:
+                               $return = parent::__get($name);
+                               break;
                }
 
-               $arr = [
-                       'notifications' => $notifications,
-                       'ident'         => $ident,
-               ];
-
-               return $arr;
+               return $return;
        }
 
-       /**
-        * 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 array [string, array]
-        *    string 'ident' => Notification identifier
-        *    array 'notifications' => Personal notifications
-        *
-        * @throws Exception
-        */
-       public function getPersonalList(bool $seen = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT)
+       public function __set($name, $value)
        {
-               $ident         = self::PERSONAL;
-               $notifications = [];
+               parent::__set($name, $value);
 
-               $myurl     = str_replace('http://', '', DI::app()->contact['nurl']);
-               $diasp_url = str_replace('/profile/', '/u/', $myurl);
-
-               $condition = ["NOT `wall` AND `uid` = ? AND (`item`.`author-id` = ? OR `item`.`tag` REGEXP ? OR `item`.`tag` REGEXP ?)",
-                       local_user(), public_contact(), $myurl . '\\]', $diasp_url . '\\]'];
-
-               if (!$seen) {
-                       $condition[0] .= " AND `unseen`";
+               if ($name == 'date') {
+                       $this->setTimestamp();
                }
 
-               $fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
-                       'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
-               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
-
-               $items = Item::selectForUser(local_user(), $fields, $condition, $params);
-
-               if ($this->dba->isResult($items)) {
-                       $notifications = $this->formatList(Item::inArray($items), $ident);
+               if ($name == 'msg') {
+                       $this->setMsg();
                }
 
-               $arr = [
-                       'notifications' => $notifications,
-                       'ident'         => $ident,
-               ];
-
-               return $arr;
-       }
-
-       /**
-        * 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 array [string, array]
-        *    string 'ident' => Notification identifier
-        *    array 'notifications' => Home notifications
-        *
-        * @throws Exception
-        */
-       public function getHomeList(bool $seen = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT)
-       {
-               $ident         = self::HOME;
-               $notifications = [];
-
-               $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'];
-               $params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
-
-               $items = Item::selectForUser(local_user(), $fields, $condition, $params);
-
-               if ($this->dba->isResult($items)) {
-                       $notifications = $this->formatList(Item::inArray($items), $ident);
+               if ($name == 'source_name') {
+                       $this->setNameCache();
                }
-
-               $arr = [
-                       'notifications' => $notifications,
-                       'ident'         => $ident,
-               ];
-
-               return $arr;
        }
 
        /**
-        * Get introductions
+        * Formats a notification message with the notification author
         *
-        * @param bool $all     If false only include introductions into the query
-        *                      which aren't marked as ignored
-        * @param int  $start   Start the query at this point
-        * @param int  $limit   Maximum number of query results
-        * @param int  $id      When set, only the introduction with this id is displayed
+        * Replace the name with {0} but ensure to make that only once. The {0} is used
+        * later and prints the name in bold.
         *
-        * @return array [string, array]
-        *    string 'ident' => Notification identifier
-        *    array 'notifications' => Introductions
+        * @param string $name
+        * @param string $message
         *
-        * @throws ImagickException
-        * @throws Exception
+        * @return string Formatted message
         */
-       public function getIntroList(bool $all = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT, int $id = 0)
+       public static function formatMessage($name, $message)
        {
-               /// @todo sanitize wording according to SELF::INTRO
-               $ident         = 'introductions';
-               $notifications = [];
-               $sql_extra     = "";
-
-               if (empty($id)) {
-                       if (!$all) {
-                               $sql_extra = " AND NOT `ignore` ";
-                       }
-
-                       $sql_extra .= " AND NOT `intro`.`blocked` ";
+               if ($name != '') {
+                       $pos = strpos($message, $name);
                } else {
-                       $sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id));
-               }
-
-               /// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact
-               $stmtNotifications = $this->dba->p(
-                       "SELECT `intro`.`id` AS `intro_id`, `intro`.*, `contact`.*,
-                               `fcontact`.`name` AS `fname`, `fcontact`.`url` AS `furl`, `fcontact`.`addr` AS `faddr`,
-                               `fcontact`.`photo` AS `fphoto`, `fcontact`.`request` AS `frequest`,
-                               `gcontact`.`location` AS `glocation`, `gcontact`.`about` AS `gabout`,
-                               `gcontact`.`keywords` AS `gkeywords`, `gcontact`.`gender` AS `ggender`,
-                               `gcontact`.`network` AS `gnetwork`, `gcontact`.`addr` AS `gaddr`
-                       FROM `intro`
-                               LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id`
-                               LEFT JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
-                               LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id`
-                       WHERE `intro`.`uid` = ? $sql_extra
-                       LIMIT ?, ?",
-                       $_SESSION['uid'],
-                       $start,
-                       $limit
-               );
-               if ($this->dba->isResult($stmtNotifications)) {
-                       $notifications = $this->formatIntroList($this->dba->toArray($stmtNotifications));
-               }
-
-               $arr = [
-                       'ident'         => $ident,
-                       'notifications' => $notifications,
-               ];
-
-               return $arr;
-       }
-
-       /**
-        * Format the notification query in an usable array
-        *
-        * @param array $intros The array from the db query
-        *
-        * @return array with the introductions
-        * @throws HTTPException\InternalServerErrorException
-        * @throws ImagickException
-        */
-       private function formatIntroList(array $intros)
-       {
-               $knowyou = '';
-
-               $formattedIntros = [];
-
-               foreach ($intros as $intro) {
-                       // 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 ($intro['fid']) {
-                               $return_addr = bin2hex(DI::app()->user['nickname'] . '@' .
-                                                      $this->baseUrl->getHostName() .
-                                                      (($this->baseUrl->getURLPath()) ? '/' . $this->baseUrl->getURLPath() : ''));
-
-                               $intro = [
-                                       'label'          => 'friend_suggestion',
-                                       'str_type'       => $this->l10n->t('Friend Suggestion'),
-                                       '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'          => (!empty($intro['fphoto']) ? ProxyUtils::proxifyUrl($intro['fphoto'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
-                                       '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),
-                                       'knowyou'        => $knowyou,
-                                       'note'           => $intro['note'],
-                                       'request'        => $intro['frequest'] . '?addr=' . $return_addr,
-                               ];
-
-                               // Normal connection requests
-                       } else {
-                               $intro = $this->getMissingIntroData($intro);
-
-                               if (empty($intro['url'])) {
-                                       continue;
-                               }
-
-                               // Don't show these data until you are connected. Diaspora is doing the same.
-                               if ($intro['gnetwork'] === Protocol::DIASPORA) {
-                                       $intro['glocation'] = "";
-                                       $intro['gabout']    = "";
-                                       $intro['ggender']   = "";
-                               }
-                               $intro = [
-                                       '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'            => $_SESSION['uid'],
-                                       'intro_id'       => $intro['intro_id'],
-                                       'contact_id'     => $intro['contact-id'],
-                                       'photo'          => (!empty($intro['photo']) ? ProxyUtils::proxifyUrl($intro['photo'], false, ProxyUtils::SIZE_SMALL) : "images/person-300.jpg"),
-                                       'name'           => $intro['name'],
-                                       'location'       => BBCode::convert($intro['glocation'], false),
-                                       'about'          => BBCode::convert($intro['gabout'], false),
-                                       'keywords'       => $intro['gkeywords'],
-                                       'gender'         => $intro['ggender'],
-                                       'hidden'         => $intro['hidden'] == 1,
-                                       'post_newfriend' => (intval($this->pConfig->get(local_user(), 'system', 'post_newfriend')) ? '1' : 0),
-                                       'url'            => $intro['url'],
-                                       'zrl'            => Contact::magicLink($intro['url']),
-                                       'addr'           => $intro['gaddr'],
-                                       'network'        => $intro['gnetwork'],
-                                       'knowyou'        => $intro['knowyou'],
-                                       'note'           => $intro['note'],
-                               ];
-                       }
-
-                       $formattedIntros[] = $intro;
+                       $pos = false;
                }
 
-               return $formattedIntros;
-       }
-
-       /**
-        * Check for missing contact data and try to fetch the data from
-        * from other sources
-        *
-        * @param array $intro The input array with the intro data
-        *
-        * @return array The array with the intro data
-        * @throws HTTPException\InternalServerErrorException
-        */
-       private function getMissingIntroData(array $intro)
-       {
-               // If the network and the addr isn't available from the gcontact
-               // table entry, take the one of the contact table entry
-               if (empty($intro['gnetwork']) && !empty($intro['network'])) {
-                       $intro['gnetwork'] = $intro['network'];
-               }
-               if (empty($intro['gaddr']) && !empty($intro['addr'])) {
-                       $intro['gaddr'] = $intro['addr'];
-               }
-
-               // If the network and addr is still not available
-               // get the missing data data from other sources
-               if (empty($intro['gnetwork']) || empty($intro['gaddr'])) {
-                       $ret = Contact::getDetailsByURL($intro['url']);
-
-                       if (empty($intro['gnetwork']) && !empty($ret['network'])) {
-                               $intro['gnetwork'] = $ret['network'];
-                       }
-                       if (empty($intro['gaddr']) && !empty($ret['addr'])) {
-                               $intro['gaddr'] = $ret['addr'];
-                       }
+               if ($pos !== false) {
+                       $message = substr_replace($message, '{0}', $pos, strlen($name));
                }
 
-               return $intro;
+               return $message;
        }
 }
index 728a9d402d865f5e58d5bd0e834289d611b1d659..9a67bb5667461df278c6ae3816e1df1f6fb0d348 100644 (file)
@@ -8,8 +8,8 @@ use Friendica\Content\Pager;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
 use Friendica\DI;
-use Friendica\Model\Notification;
 use Friendica\Network\HTTPException\ForbiddenException;
+use Friendica\Object\Notification\Notification;
 
 /**
  * Base Module for each tab of the notification display
@@ -47,6 +47,8 @@ abstract class BaseNotifications extends BaseModule
 
        /** @var int The default count of items per page */
        const ITEMS_PER_PAGE = 20;
+       /** @var int The default limit of notifications per page */
+       const DEFAULT_PAGE_LIMIT = 80;
 
        /** @var boolean True, if ALL entries should get shown */
        protected static $showAll;
@@ -104,7 +106,17 @@ abstract class BaseNotifications extends BaseModule
                        return;
                }
 
-               System::jsonExit(static::getNotifications()['notifs'] ?? []);
+               // Set the pager
+               $pager = new Pager(DI::args()->getQueryString(), self::ITEMS_PER_PAGE);
+
+               // Add additional informations (needed for json output)
+               $notifications = [
+                       'notifications' => static::getNotifications(),
+                       'items_page'    => $pager->getItemsPerPage(),
+                       'page'          => $pager->getPage(),
+               ];
+
+               System::jsonExit($notifications);
        }
 
        /**
index 99066bd3c90d515079d62d52ab6e304039c40eaf..b0bdac0cb5d4dec71d06d6f2bbc49da93d44b5b4 100644 (file)
@@ -9,6 +9,7 @@ use Friendica\Core\Renderer;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Module\BaseNotifications;
+use Friendica\Object\Notification\Introduction;
 
 /**
  * Prints notifications about introduction
@@ -23,7 +24,10 @@ class Introductions extends BaseNotifications
                $id  = (int)DI::args()->get(2, 0);
                $all = DI::args()->get(2) == 'all';
 
-               $notifications = DI::notification()->getIntroList($all, self::$firstItemNum, self::ITEMS_PER_PAGE, $id);
+               $notifications = [
+                       'ident'        => 'introductions',
+                       'notifications' => DI::factNotIntro()->getIntroList($all, self::$firstItemNum, self::ITEMS_PER_PAGE, $id),
+               ];
 
                return [
                        'header'        => DI::l10n()->t('Notifications'),
@@ -50,11 +54,12 @@ class Introductions extends BaseNotifications
                // The link to switch between ignored and normal connection requests
                $notificationShowLink = [
                        'href' => (!$all ? 'notifications/intros/all' : 'notifications/intros'),
-                       'text' => (!$all ? DI::l10n()->t('Show Ignored Requests') : DI::l10n()->t('Hide Ignored Requests'))
+                       'text' => (!$all ? DI::l10n()->t('Show Ignored Requests') : DI::l10n()->t('Hide Ignored Requests')),
                ];
 
                // Loop through all introduction notifications.This creates an array with the output html for each
                // introduction
+               /** @var Introduction $notification */
                foreach ($notifications['notifications'] as $notification) {
 
                        // There are two kind of introduction. Contacts suggested by other contacts and normal connection requests.
@@ -62,27 +67,27 @@ class Introductions extends BaseNotifications
                        switch ($notification['label']) {
                                case 'friend_suggestion':
                                        $notificationContent[] = Renderer::replaceMacros($notificationSuggestions, [
-                                               '$type'                 => $notification['label'],
+                                               '$type'                 => $notification->getLabel(),
                                                'str_notification_type' => DI::l10n()->t('Notification type:'),
-                                               'str_type'              => $notification['str_type'],
-                                               '$intro_id'             => $notification['intro_id'],
+                                               'str_type'              => $notification->getType(),
+                                               '$intro_id'             => $notification->getIntroId(),
                                                '$lbl_madeby'           => DI::l10n()->t('Suggested by:'),
-                                               '$madeby'               => $notification['madeby'],
-                                               '$madeby_url'           => $notification['madeby_url'],
-                                               '$madeby_zrl'           => $notification['madeby_zrl'],
-                                               '$madeby_addr'          => $notification['madeby_addr'],
-                                               '$contact_id'           => $notification['contact_id'],
-                                               '$photo'                => $notification['photo'],
-                                               '$fullname'             => $notification['name'],
-                                               '$url'                  => $notification['url'],
-                                               '$zrl'                  => $notification['zrl'],
+                                               '$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(),
+                                               '$url'                  => $notification->getUrl(),
+                                               '$zrl'                  => $notification->getZrl(),
                                                '$lbl_url'              => DI::l10n()->t('Profile URL'),
-                                               '$addr'                 => $notification['addr'],
-                                               '$hidden'               => ['hidden', DI::l10n()->t('Hide this contact from others'), ($notification['hidden'] == 1), ''],
-                                               '$knowyou'              => $notification['knowyou'],
+                                               '$addr'                 => $notification->getAddr(),
+                                               '$hidden'               => ['hidden', DI::l10n()->t('Hide this contact from others'), $notification->isHidden(), ''],
+                                               '$knowyou'              => $notification->getKnowYou(),
                                                '$approve'              => DI::l10n()->t('Approve'),
-                                               '$note'                 => $notification['note'],
-                                               '$request'              => $notification['request'],
+                                               '$note'                 => $notification->getNote(),
+                                               '$request'              => $notification->getRequest(),
                                                '$ignore'               => DI::l10n()->t('Ignore'),
                                                '$discard'              => DI::l10n()->t('Discard'),
                                        ]);
@@ -90,8 +95,8 @@ class Introductions extends BaseNotifications
 
                                // Normal connection requests
                                default:
-                                       $friend_selected = (($notification['network'] !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled ');
-                                       $fan_selected    = (($notification['network'] === Protocol::OSTATUS) ? ' checked="checked" disabled ' : '');
+                                       $friend_selected = (($notification->getNetwork() !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled ');
+                                       $fan_selected    = (($notification->getNetwork() === Protocol::OSTATUS) ? ' checked="checked" disabled ' : '');
 
                                        $lbl_knowyou = '';
                                        $knowyou     = '';
@@ -99,13 +104,13 @@ class Introductions extends BaseNotifications
                                        $helptext2   = '';
                                        $helptext3   = '';
 
-                                       if ($notification['network'] === Protocol::DFRN) {
+                                       if ($notification->getNetwork() === Protocol::DFRN) {
                                                $lbl_knowyou = DI::l10n()->t('Claims to be known to you: ');
-                                               $knowyou     = (($notification['knowyou']) ? DI::l10n()->t('yes') : DI::l10n()->t('no'));
+                                               $knowyou     = ($notification->getKnowYou() ? DI::l10n()->t('yes') : DI::l10n()->t('no'));
                                                $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.', $notification['name'], $notification['name']);
                                                $helptext3   = DI::l10n()->t('Accepting %s as a subscriber allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $notification['name']);
-                                       } elseif ($notification['network'] === Protocol::DIASPORA) {
+                                       } elseif ($notification->getNetwork() === Protocol::DIASPORA) {
                                                $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.', $notification['name'], $notification['name']);
                                                $helptext3 = DI::l10n()->t('Accepting %s as a sharer allows them to subscribe to your posts, but you will not receive updates from them in your news feed.', $notification['name']);
@@ -113,17 +118,17 @@ class Introductions extends BaseNotifications
 
                                        $dfrn_tpl  = Renderer::getMarkupTemplate('notifications/netfriend.tpl');
                                        $dfrn_text = Renderer::replaceMacros($dfrn_tpl, [
-                                               '$intro_id'        => $notification['intro_id'],
+                                               '$intro_id'        => $notification->getIntroId(),
                                                '$friend_selected' => $friend_selected,
                                                '$fan_selected'    => $fan_selected,
                                                '$approve_as1'     => $helptext,
                                                '$approve_as2'     => $helptext2,
                                                '$approve_as3'     => $helptext3,
                                                '$as_friend'       => DI::l10n()->t('Friend'),
-                                               '$as_fan'          => (($notification['network'] == Protocol::DIASPORA) ? DI::l10n()->t('Sharer') : DI::l10n()->t('Subscriber'))
+                                               '$as_fan'          => (($notification->getNetwork() == Protocol::DIASPORA) ? DI::l10n()->t('Sharer') : DI::l10n()->t('Subscriber')),
                                        ]);
 
-                                       $contact = DBA::selectFirst('contact', ['network', 'protocol'], ['id' => $notification['contact_id']]);
+                                       $contact = DBA::selectFirst('contact', ['network', 'protocol'], ['id' => $notification->getContactId()]);
 
                                        if (($contact['network'] != Protocol::DFRN) || ($contact['protocol'] == Protocol::ACTIVITYPUB)) {
                                                $action = 'follow_confirm';
@@ -137,45 +142,45 @@ class Introductions extends BaseNotifications
                                                $header .= ' <' . $notification['addr'] . '>';
                                        }
 
-                                       $header .= ' (' . ContactSelector::networkToName($notification['network'], $notification['url']) . ')';
+                                       $header .= ' (' . ContactSelector::networkToName($notification->getNetwork(), $notification->getUrl()) . ')';
 
-                                       if ($notification['network'] != Protocol::DIASPORA) {
+                                       if ($notification->getNetwork() != Protocol::DIASPORA) {
                                                $discard = DI::l10n()->t('Discard');
                                        } else {
                                                $discard = '';
                                        }
 
                                        $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [
-                                               '$type'                 => $notification['label'],
+                                               '$type'                 => $notification->getLabel(),
                                                '$header'               => $header,
                                                'str_notification_type' => DI::l10n()->t('Notification type:'),
-                                               'str_type'              => $notification['notifytype'],
+                                               'str_type'              => $notification->getType(),
                                                '$dfrn_text'            => $dfrn_text,
-                                               '$dfrn_id'              => $notification['dfrn_id'],
-                                               '$uid'                  => $notification['uid'],
-                                               '$intro_id'             => $notification['intro_id'],
-                                               '$contact_id'           => $notification['contact_id'],
-                                               '$photo'                => $notification['photo'],
-                                               '$fullname'             => $notification['name'],
-                                               '$location'             => $notification['location'],
+                                               '$dfrn_id'              => $notification->getDfrnId(),
+                                               '$uid'                  => $notification->getUid(),
+                                               '$intro_id'             => $notification->getIntroId(),
+                                               '$contact_id'           => $notification->getContactId(),
+                                               '$photo'                => $notification->getPhoto(),
+                                               '$fullname'             => $notification->getName(),
+                                               '$location'             => $notification->getLocation(),
                                                '$lbl_location'         => DI::l10n()->t('Location:'),
-                                               '$about'                => $notification['about'],
+                                               '$about'                => $notification->getAbout(),
                                                '$lbl_about'            => DI::l10n()->t('About:'),
-                                               '$keywords'             => $notification['keywords'],
+                                               '$keywords'             => $notification->getKeywords(),
                                                '$lbl_keywords'         => DI::l10n()->t('Tags:'),
-                                               '$gender'               => $notification['gender'],
+                                               '$gender'               => $notification->getGender(),
                                                '$lbl_gender'           => DI::l10n()->t('Gender:'),
                                                '$hidden'               => ['hidden', DI::l10n()->t('Hide this contact from others'), ($notification['hidden'] == 1), ''],
-                                               '$url'                  => $notification['url'],
-                                               '$zrl'                  => $notification['zrl'],
+                                               '$url'                  => $notification->getUrl(),
+                                               '$zrl'                  => $notification->getZrl(),
                                                '$lbl_url'              => DI::l10n()->t('Profile URL'),
-                                               '$addr'                 => $notification['addr'],
+                                               '$addr'                 => $notification->getAddr(),
                                                '$lbl_knowyou'          => $lbl_knowyou,
                                                '$lbl_network'          => DI::l10n()->t('Network:'),
-                                               '$network'              => ContactSelector::networkToName($notification['network'], $notification['url']),
+                                               '$network'              => ContactSelector::networkToName($notification->getNetwork(), $notification->getUrl()),
                                                '$knowyou'              => $knowyou,
                                                '$approve'              => DI::l10n()->t('Approve'),
-                                               '$note'                 => $notification['note'],
+                                               '$note'                 => $notification->getNote(),
                                                '$ignore'               => DI::l10n()->t('Ignore'),
                                                '$discard'              => $discard,
                                                '$action'               => $action,
index 2f3f927f9c3b4a83aa0932d8996a512d91b9330a..f7fe6f19038651b329afc4126ca6098fc4701201 100644 (file)
@@ -23,7 +23,11 @@ class Notification extends BaseModule
        {
                // @TODO: Replace with parameter from router
                if (DI::args()->get(1) === 'mark' && DI::args()->get(2) === 'all') {
-                       $success = DI::notification()->setAllSeen();
+                       try {
+                               $success = DI::notification()->setAllSeen();
+                       }catch (\Exception $e) {
+                               $success = false;
+                       }
 
                        header('Content-type: application/json; charset=utf-8');
                        echo json_encode([
@@ -43,14 +47,16 @@ class Notification extends BaseModule
        {
                // @TODO: Replace with parameter from router
                if (DI::args()->getArgc() > 2 && DI::args()->get(1) === 'view' && intval(DI::args()->get(2))) {
-                       $notificationManager = DI::notification();
-                       // @TODO: Replace with parameter from router
-                       $note = $notificationManager->getByID(DI::args()->get(2));
-                       if (!empty($note)) {
-                               $notificationManager->setSeen($note);
-                               if (!empty($note['link'])) {
-                                       System::externalRedirect($note['link']);
+                       try {
+                               $notification = DI::notification()->getByID(DI::args()->get(2));
+                               $notification->setSeen();
+
+                               if (!empty($notification->link)) {
+                                       System::externalRedirect($notification->link);
                                }
+
+                       } catch (HTTPException\NotFoundException $e) {
+                               info(DI::l10n()->t('Invalid notification.'));
                        }
 
                        DI::baseUrl()->redirect();
index 3dffe90b5bb1cae71bcd37ac95d0c417d619255c..bff5026df6ecc6827b0b7821067033d8a02a6186 100644 (file)
@@ -3,10 +3,10 @@
 namespace Friendica\Module\Notifications;
 
 use Friendica\Content\Nav;
-use Friendica\Content\Pager;
 use Friendica\Core\Renderer;
 use Friendica\DI;
 use Friendica\Module\BaseNotifications;
+use Friendica\Object\Notification\Notification;
 
 /**
  * Prints all notification types except introduction:
@@ -22,41 +22,46 @@ class Notifications extends BaseNotifications
         */
        public static function getNotifications()
        {
-               $nm = DI::notification();
-
                $notificationHeader = '';
+               /** @var Notification[] $notifications */
+               $notifications = [];
 
                // Get the network notifications
                if ((DI::args()->get(1) == 'network')) {
                        $notificationHeader = DI::l10n()->t('Network Notifications');
-                       $notifications      = $nm->getNetworkList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE);
+                       $notifications      = [
+                               'ident'        => Notification::NETWORK,
+                               'notifications' => DI::factNotification()->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      = $nm->getSystemList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE);
+                       $notifications      = [
+                               'ident'        => Notification::SYSTEM,
+                               'notifications' => DI::factNotification()->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      = $nm->getPersonalList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE);
+                       $notifications      = [
+                               'ident'        => Notification::PERSONAL,
+                               'notifications' => DI::factNotification()->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      = $nm->getHomeList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE);
+                       $notifications      = [
+                               'ident'        => Notification::HOME,
+                               'notifications' => DI::factNotification()->getHomeList(self::$showAll, self::$firstItemNum, self::ITEMS_PER_PAGE),
+                       ];
                        // fallback - redirect to main page
                } else {
                        DI::baseUrl()->redirect('notifications');
                }
 
-               // Set the pager
-               $pager = new Pager(DI::args()->getQueryString(), self::ITEMS_PER_PAGE);
-
-               // Add additional informations (needed for json output)
-               $notifications['items_page'] = $pager->getItemsPerPage();
-               $notifications['page']       = $pager->getPage();
-
                return [
                        'header'        => $notificationHeader,
                        'notifications' => $notifications,
@@ -78,6 +83,7 @@ class Notifications extends BaseNotifications
                if (!empty($notifications['notifications'])) {
                        // 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',
@@ -91,17 +97,17 @@ class Notifications extends BaseNotifications
                                        'notification' => 'notifications/notification.tpl',
                                ];
 
-                               $notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notification['label']]);
+                               $notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notification->getLabel()]);
 
                                $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [
-                                       '$item_label' => $notification['label'],
-                                       '$item_link'  => $notification['link'],
-                                       '$item_image' => $notification['image'],
-                                       '$item_url'   => $notification['url'],
-                                       '$item_text'  => $notification['text'],
-                                       '$item_when'  => $notification['when'],
-                                       '$item_ago'   => $notification['ago'],
-                                       '$item_seen'  => $notification['seen'],
+                                       '$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(),
                                ]);
                        }
                } else {
diff --git a/src/Object/Notification/Introduction.php b/src/Object/Notification/Introduction.php
new file mode 100644 (file)
index 0000000..56bc3ce
--- /dev/null
@@ -0,0 +1,302 @@
+<?php
+
+namespace Friendica\Object\Notification;
+
+class Introduction implements \JsonSerializable
+{
+       /** @var string */
+       private $label = '';
+       /** @var string */
+       private $type = '';
+       /** @var integer */
+       private $intro_id = 0;
+       /** @var string */
+       private $madeBy = '';
+       /** @var string */
+       private $madeByUrl = '';
+       /** @var string */
+       private $madeByZrl = '';
+       /** @var string */
+       private $madeByAddr = '';
+       /** @var integer */
+       private $contactId = 0;
+       /** @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 = 0;
+       /** @var string */
+       private $knowYou = '';
+       /** @var string */
+       private $note = '';
+       /** @var string */
+       private $request = '';
+       /** @var string */
+       private $dfrnId;
+       /** @var string */
+       private $addr;
+       /** @var string */
+       private $network;
+       /** @var int */
+       private $uid;
+       /** @var string */
+       private $keywords;
+       /** @var string */
+       private $gender;
+       /** @var string */
+       private $location;
+       /** @var string */
+       private $about;
+
+       /**
+        * @return string
+        */
+       public function getLabel()
+       {
+               return $this->label;
+       }
+
+       /**
+        * @return string
+        */
+       public function getType()
+       {
+               return $this->type;
+       }
+
+       /**
+        * @return int
+        */
+       public function getIntroId()
+       {
+               return $this->intro_id;
+       }
+
+       /**
+        * @return string
+        */
+       public function getMadeBy()
+       {
+               return $this->madeBy;
+       }
+
+       /**
+        * @return string
+        */
+       public function getMadeByUrl()
+       {
+               return $this->madeByUrl;
+       }
+
+       /**
+        * @return string
+        */
+       public function getMadeByZrl()
+       {
+               return $this->madeByZrl;
+       }
+
+       /**
+        * @return string
+        */
+       public function getMadeByAddr()
+       {
+               return $this->madeByAddr;
+       }
+
+       /**
+        * @return int
+        */
+       public function getContactId()
+       {
+               return $this->contactId;
+       }
+
+       /**
+        * @return string
+        */
+       public function getPhoto()
+       {
+               return $this->photo;
+       }
+
+       /**
+        * @return string
+        */
+       public function getName()
+       {
+               return $this->name;
+       }
+
+       /**
+        * @return string
+        */
+       public function getUrl()
+       {
+               return $this->url;
+       }
+
+       /**
+        * @return string
+        */
+       public function getZrl()
+       {
+               return $this->zrl;
+       }
+
+       /**
+        * @return bool
+        */
+       public function isHidden()
+       {
+               return $this->hidden;
+       }
+
+       /**
+        * @return int
+        */
+       public function getPostNewFriend()
+       {
+               return $this->postNewFriend;
+       }
+
+       /**
+        * @return string
+        */
+       public function getKnowYou()
+       {
+               return $this->knowYou;
+       }
+
+       /**
+        * @return string
+        */
+       public function getNote()
+       {
+               return $this->note;
+       }
+
+       /**
+        * @return string
+        */
+       public function getRequest()
+       {
+               return $this->request;
+       }
+
+       /**
+        * @return string
+        */
+       public function getDfrnId()
+       {
+               return $this->dfrnId;
+       }
+
+       /**
+        * @return string
+        */
+       public function getAddr()
+       {
+               return $this->addr;
+       }
+
+       /**
+        * @return string
+        */
+       public function getNetwork()
+       {
+               return $this->network;
+       }
+
+       /**
+        * @return int
+        */
+       public function getUid()
+       {
+               return $this->uid;
+       }
+
+       /**
+        * @return string
+        */
+       public function getKeywords()
+       {
+               return $this->keywords;
+       }
+
+       /**
+        * @return string
+        */
+       public function getGender()
+       {
+               return $this->gender;
+       }
+
+       /**
+        * @return string
+        */
+       public function getLocation()
+       {
+               return $this->location;
+       }
+
+       /**
+        * @return string
+        */
+       public function getAbout()
+       {
+               return $this->about;
+       }
+
+       public function __construct(array $data = [])
+       {
+               $this->label         = $data['label'] ?? '';
+               $this->type          = $data['str_$type'] ?? '';
+               $this->intro_id      = $data['$intro_id'] ?? '';
+               $this->madeBy        = $data['$madeBy'] ?? '';
+               $this->madeByUrl     = $data['$madeByUrl'] ?? '';
+               $this->madeByZrl     = $data['$madeByZrl'] ?? '';
+               $this->madeByAddr    = $data['$madeByAddr'] ?? '';
+               $this->contactId     = $data['$contactId'] ?? '';
+               $this->photo         = $data['$photo'] ?? '';
+               $this->name          = $data['$name'] ?? '';
+               $this->url           = $data['$url'] ?? '';
+               $this->zrl           = $data['$zrl'] ?? '';
+               $this->hidden        = $data['$hidden'] ?? '';
+               $this->postNewFriend = $data['$postNewFriend'] ?? '';
+               $this->knowYou       = $data['$knowYou'] ?? '';
+               $this->note          = $data['$note'] ?? '';
+               $this->request       = $data['$request'] ?? '';
+               $this->dfrnId        = $data['dfrn_id'] ?? '';
+               $this->addr          = $data['addr'] ?? '';
+               $this->network       = $data['network'] ?? '';
+               $this->uid           = $data['uid'] ?? '';
+               $this->keywords      = $data['keywords'] ?? '';
+               $this->gender        = $data['gender'] ?? '';
+               $this->location      = $data['location'] ?? '';
+               $this->about         = $data['about'] ?? '';
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function jsonSerialize()
+       {
+               return $this->toArray();
+       }
+
+       /**
+        * @return array
+        */
+       public function toArray()
+       {
+               return get_object_vars($this);
+       }
+}
diff --git a/src/Object/Notification/Notification.php b/src/Object/Notification/Notification.php
new file mode 100644 (file)
index 0000000..2e853a6
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+
+namespace Friendica\Object\Notification;
+
+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(string $label = '', string $link = '', string $image = '',
+                                   string $url = '', string $text = '',
+                                   string $when = '', string $ago = '', bool $seen = false)
+       {
+               $this->label = $label;
+               $this->link  = $link;
+               $this->image = $image;
+               $this->url   = $url;
+               $this->text  = $text;
+               $this->when  = $when;
+               $this->ago   = $ago;
+               $this->seen  = $seen;
+       }
+
+       /**
+        * @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
new file mode 100644 (file)
index 0000000..a17d122
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace Friendica\Repository;
+
+use Exception;
+use Friendica\BaseRepository;
+use Friendica\Core\Hook;
+use Friendica\Model;
+use Friendica\Collection;
+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'];
+
+               $condition = array_merge($condition, ['uid' => local_user()]);
+
+               return parent::select($condition, $params);
+       }
+
+       /**
+        * {@inheritDoc}
+        *
+        * @return Model\Notification
+        * @throws NotFoundException
+        */
+       public function getByID(int $id)
+       {
+               return $this->selectFirst(['id' => $id, 'uid' => local_user()]);
+       }
+
+       /**
+        * {@inheritDoc}
+        *
+        * @return bool true on success, false on error
+        * @throws Exception
+        */
+       public function setAllSeen(bool $seen = true)
+       {
+               return $this->dba->update('notify', ['seen' => $seen], ['uid' => local_user()]);
+       }
+
+       /**
+        * @param array $fields
+        *
+        * @return Model\Notification
+        *
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws Exception
+        */
+       public function insert(array $fields)
+       {
+               $fields['date']  = DateTimeFormat::utcNow();
+               $fields['abort'] = false;
+
+               Hook::callAll('enotify_store', $fields);
+
+               if ($fields['abort']) {
+                       $this->logger->debug('Abort adding notification entry', ['fields' => $fields]);
+                       return null;
+               }
+
+               $this->logger->debug('adding notification entry', ['fields' => $fields]);
+
+               return parent::insert($fields);
+       }
+}