ReWork Notification Model/Module/Object/Repository/Factory

- 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, ..)
This commit is contained in:
Philipp Holzer 2020-01-25 02:01:49 +01:00
parent 230bb6dd53
commit 0850fb88dd
No known key found for this signature in database
GPG key ID: D8365C3D36B77D90
17 changed files with 1413 additions and 851 deletions

View file

@ -5905,19 +5905,20 @@ function api_friendica_notification($type)
if ($a->argc!==3) { if ($a->argc!==3) {
throw new BadRequestException("Invalid argument count"); 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") { if ($type == "xml") {
$xmlnotes = []; $xmlnotes = [];
if (!empty($notes)) { if (!empty($notifications)) {
foreach ($notes as $note) { foreach ($notifications as $notification) {
$xmlnotes[] = ["@attributes" => $note]; $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) function api_friendica_notification_seen($type)
{ {
$a = DI::app(); $a = DI::app();
$user_info = api_get_user($a); $user_info = api_get_user($a);
if (api_user() === false || $user_info === false) { if (api_user() === false || $user_info === false) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
if ($a->argc!==4) { if ($a->argc !== 4) {
throw new BadRequestException("Invalid argument count"); throw new BadRequestException("Invalid argument count");
} }
$id = (!empty($_REQUEST['id']) ? intval($_REQUEST['id']) : 0); $id = (!empty($_REQUEST['id']) ? intval($_REQUEST['id']) : 0);
$nm = DI::notification(); try {
$note = $nm->getByID($id); $notification = DI::notification()->getByID($id);
if (is_null($note)) { $notification->setSeen();
throw new BadRequestException("Invalid argument");
}
$nm->setSeen($note); if ($notification->otype == 'item') {
if ($note['otype']=='item') { // would be really better with an ItemsManager and $im->getByID() :-P
// would be really better with an ItemsManager and $im->getByID() :-P $item = Item::selectFirstForUser(api_user(), [], ['id' => $notification->iid, 'uid' => api_user()]);
$item = Item::selectFirstForUser(api_user(), [], ['id' => $note['iid'], 'uid' => api_user()]); if (DBA::isResult($item)) {
if (DBA::isResult($item)) { // we found the item, return it to the user
// we found the item, return it to the user $ret = api_format_items([$item], $user_info, false, $type);
$ret = api_format_items([$item], $user_info, false, $type); $data = ['status' => $ret];
$data = ['status' => $ret]; return api_format_data("status", $type, $data);
return api_format_data("status", $type, $data); }
// the item can't be found, but we set the notification as seen, so we count this as a success
} }
// 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 /// @TODO move to top of file or somewhere better

View file

@ -482,47 +482,25 @@ function notification($params)
$notify_id = 0; $notify_id = 0;
if ($show_in_notification_page) { if ($show_in_notification_page) {
Logger::log("adding notification entry", Logger::DEBUG); $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'],
]);
/// @TODO One statement is enough $notification->link = DI::baseUrl() . '/notification/view/' . $notification->id;
$datarray = []; $notification->msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $notification->link]);
$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); DI::notification()->update($notification);
if ($datarray['abort']) { $itemlink = $notification->link;
return false; $notify_id = $notification->id;
}
// 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);
$notify_id = DBA::lastInsertId();
$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)));
$fields = ['msg' => $msg, 'msg_cache' => $msg_cache];
$condition = ['id' => $notify_id, 'uid' => $params['uid']];
DBA::update('notify', $fields, $condition);
} }
// send email notification if notification preferences permit // send email notification if notification preferences permit
@ -732,27 +710,3 @@ function check_item_notification($itemid, $uid, $notification_type) {
notification($params); 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;
}

View file

@ -433,7 +433,7 @@ function ping_get_notifications($uid)
$notification["message"] = $notification["msg_cache"]; $notification["message"] = $notification["msg_cache"];
} else { } else {
$notification["name"] = strip_tags(BBCode::convert($notification["name"])); $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( q(
"UPDATE `notify` SET `name_cache` = '%s', `msg_cache` = '%s' WHERE `id` = %d", "UPDATE `notify` SET `name_cache` = '%s', `msg_cache` = '%s' WHERE `id` = %d",

View file

@ -53,6 +53,11 @@ abstract class BaseModel
return $this->originalData; return $this->originalData;
} }
public function resetOriginalData()
{
$this->originalData = $this->data;
}
/** /**
* Performance-improved model creation in a loop * Performance-improved model creation in a loop
* *

View file

@ -122,7 +122,12 @@ abstract class BaseRepository extends BaseFactory
*/ */
public function update(BaseModel $model) 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;
} }
/** /**

View file

@ -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();
}
}

View file

@ -244,6 +244,22 @@ abstract class DI
return self::$dice->create(Factory\Mastodon\Relationship::class); 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 // "Model" namespace instances
// //
@ -257,11 +273,11 @@ abstract class DI
} }
/** /**
* @return Model\Notification * @return Repository\Notification
*/ */
public static function notification() public static function notification()
{ {
return self::$dice->create(Model\Notification::class); return self::$dice->create(Repository\Notification::class);
} }
/** /**

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -3,721 +3,180 @@
namespace Friendica\Model; namespace Friendica\Model;
use Exception; use Exception;
use Friendica\App; use Friendica\BaseModel;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML; 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\Database\Database;
use Friendica\DI; use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Temporal; use Friendica\Util\Temporal;
use Friendica\Util\XML;
use ImagickException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Friendica\Network\HTTPException;
/** /**
* Methods for read and write notifications from/to database * Model for an entry in the notify table
* or for formatting notifications * - 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 */ /** @var \Friendica\Repository\Notification */
const DEFAULT_PAGE_LIMIT = 80; private $repo;
/** @var $this */
private $parentInst;
const NETWORK = 'network'; public function __construct(Database $dba, LoggerInterface $logger, \Friendica\Repository\Notification $repo, array $data = [])
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)
{ {
$this->dba = $dba; parent::__construct($dba, $logger, $data);
$this->l10n = $l10n;
$this->args = $args; $this->repo = $repo;
$this->baseUrl = $baseUrl;
$this->pConfig = $pConfig; $this->setNameCache();
$this->logger = $logger; $this->setTimestamp();
$this->setMsg();
} }
/** /**
* Set some extra properties to note array from db: * Set the notification as seen
*
* @param bool $seen true, if seen
*
* @return bool True, if the seen state could be saved
*/
public function setSeen(bool $seen = true)
{
$this->seen = $seen;
try {
return $this->repo->update($this);
} catch (Exception $e) {
$this->logger->warning('Update failed.', ['$this' => $this, 'exception' => $e]);
return false;
}
}
/**
* Set some extra properties to the notification from db:
* - timestamp as int in default TZ * - timestamp as int in default TZ
* - date_rel : relative date string * - date_rel : relative date string
*/
private function setTimestamp()
{
try {
$this->timestamp = strtotime(DateTimeFormat::local($this->date));
} catch (Exception $e) {
}
$this->dateRel = Temporal::getRelativeDate($this->date);
}
/**
* Sets the pre-formatted name (caching)
*
* @throws InternalServerErrorException
*/
private function setNameCache()
{
$this->name_cache = strip_tags(BBCode::convert($this->source_name ?? ''));
}
/**
* Set some extra properties to the notification from db:
* - msg_html: message as html string * - msg_html: message as html string
* - msg_plain: message as plain text string * - msg_plain: message as plain text string
* * - msg_cache: The pre-formatted message (caching)
* @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) private function setMsg()
{ {
$retNotes = []; try {
foreach ($notes as $note) { $this->msg_html = BBCode::convert($this->msg, false);
$local_time = DateTimeFormat::local($note['date']); $this->msg_plain = explode("\n", trim(HTML::toPlaintext($this->msg_html, 0)))[0];
$note['timestamp'] = strtotime($local_time); $this->msg_cache = self::formatMessage($this->name_cache, strip_tags(BBCode::convert($this->msg)));
$note['date_rel'] = Temporal::getRelativeDate($note['date']); } catch (InternalServerErrorException $e) {
$note['msg_html'] = BBCode::convert($note['msg'], false); }
$note['msg_plain'] = explode("\n", trim(HTML::toPlaintext($note['msg_html'], 0)))[0]; }
$retNotes[] = $note; public function __get($name)
{
$this->checkValid();
$return = null;
switch ($name) {
case 'parent':
if (!empty($this->parent)) {
$this->parentInst = $this->parentInst ?? $this->repo->getByID($this->parent);
$return = $this->parentInst;
}
break;
default:
$return = parent::__get($name);
break;
}
return $return;
}
public function __set($name, $value)
{
parent::__set($name, $value);
if ($name == 'date') {
$this->setTimestamp();
}
if ($name == 'msg') {
$this->setMsg();
}
if ($name == 'source_name') {
$this->setNameCache();
} }
return $retNotes;
} }
/** /**
* Get all notifications for local_user() * Formats a notification message with the notification author
* *
* @param array $filter optional Array "column name"=>value: filter query by columns values * Replace the name with {0} but ensure to make that only once. The {0} is used
* @param array $order optional Array to order by * later and prints the name in bold.
* @param string $limit optional Query limits
* *
* @return array|bool of results or false on errors * @param string $name
* @throws Exception * @param string $message
*
* @return string Formatted message
*/ */
public function getAll(array $filter = [], array $order = ['date' => 'DESC'], string $limit = "") public static function formatMessage($name, $message)
{ {
$params = []; if ($name != '') {
$pos = strpos($message, $name);
$params['order'] = $order;
if (!empty($limit)) {
$params['limit'] = $limit;
}
$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
*/
public function getByID(int $id)
{
$stmtNotify = $this->dba->selectFirst('notify', [], ['id' => $id, 'uid' => local_user()]);
if ($this->dba->isResult($stmtNotify)) {
return $this->setExtra([$stmtNotify])[0];
}
return null;
}
/**
* set seen state of $note of local_user()
*
* @param array $note note array
* @param bool $seen optional true or false, default true
*
* @return bool true on success, false on errors
* @throws Exception
*/
public function setSeen(array $note, bool $seen = true)
{
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()
]);
}
/**
* 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
*/
private function formatList(array $notifications, string $ident = "")
{
$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;
}
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)
{
$ident = self::NETWORK;
$notifications = [];
$condition = ['wall' => false, 'uid' => local_user()];
if (!$seen) {
$condition['unseen'] = true;
}
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
$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);
}
$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);
}
$arr = [
'notifications' => $notifications,
'ident' => $ident,
];
return $arr;
}
/**
* 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)
{
$ident = self::PERSONAL;
$notifications = [];
$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`";
}
$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);
}
$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);
}
$arr = [
'notifications' => $notifications,
'ident' => $ident,
];
return $arr;
}
/**
* 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 array [string, array]
* string 'ident' => Notification identifier
* array 'notifications' => Introductions
*
* @throws ImagickException
* @throws Exception
*/
public function getIntroList(bool $all = false, int $start = 0, int $limit = self::DEFAULT_PAGE_LIMIT, int $id = 0)
{
/// @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` ";
} else { } else {
$sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id)); $pos = false;
} }
/// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact if ($pos !== false) {
$stmtNotifications = $this->dba->p( $message = substr_replace($message, '{0}', $pos, strlen($name));
"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 = [ return $message;
'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;
}
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'];
}
}
return $intro;
} }
} }

View file

@ -8,8 +8,8 @@ use Friendica\Content\Pager;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Notification;
use Friendica\Network\HTTPException\ForbiddenException; use Friendica\Network\HTTPException\ForbiddenException;
use Friendica\Object\Notification\Notification;
/** /**
* Base Module for each tab of the notification display * 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 */ /** @var int The default count of items per page */
const ITEMS_PER_PAGE = 20; 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 */ /** @var boolean True, if ALL entries should get shown */
protected static $showAll; protected static $showAll;
@ -104,7 +106,17 @@ abstract class BaseNotifications extends BaseModule
return; 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);
} }
/** /**

View file

@ -9,6 +9,7 @@ use Friendica\Core\Renderer;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Module\BaseNotifications; use Friendica\Module\BaseNotifications;
use Friendica\Object\Notification\Introduction;
/** /**
* Prints notifications about introduction * Prints notifications about introduction
@ -23,7 +24,10 @@ class Introductions extends BaseNotifications
$id = (int)DI::args()->get(2, 0); $id = (int)DI::args()->get(2, 0);
$all = DI::args()->get(2) == 'all'; $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 [ return [
'header' => DI::l10n()->t('Notifications'), 'header' => DI::l10n()->t('Notifications'),
@ -50,11 +54,12 @@ class Introductions extends BaseNotifications
// The link to switch between ignored and normal connection requests // The link to switch between ignored and normal connection requests
$notificationShowLink = [ $notificationShowLink = [
'href' => (!$all ? 'notifications/intros/all' : 'notifications/intros'), '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 // Loop through all introduction notifications.This creates an array with the output html for each
// introduction // introduction
/** @var Introduction $notification */
foreach ($notifications['notifications'] as $notification) { foreach ($notifications['notifications'] as $notification) {
// There are two kind of introduction. Contacts suggested by other contacts and normal connection requests. // 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']) { switch ($notification['label']) {
case 'friend_suggestion': case 'friend_suggestion':
$notificationContent[] = Renderer::replaceMacros($notificationSuggestions, [ $notificationContent[] = Renderer::replaceMacros($notificationSuggestions, [
'$type' => $notification['label'], '$type' => $notification->getLabel(),
'str_notification_type' => DI::l10n()->t('Notification type:'), 'str_notification_type' => DI::l10n()->t('Notification type:'),
'str_type' => $notification['str_type'], 'str_type' => $notification->getType(),
'$intro_id' => $notification['intro_id'], '$intro_id' => $notification->getIntroId(),
'$lbl_madeby' => DI::l10n()->t('Suggested by:'), '$lbl_madeby' => DI::l10n()->t('Suggested by:'),
'$madeby' => $notification['madeby'], '$madeby' => $notification->getMadeBy(),
'$madeby_url' => $notification['madeby_url'], '$madeby_url' => $notification->getMadeByUrl(),
'$madeby_zrl' => $notification['madeby_zrl'], '$madeby_zrl' => $notification->getMadeByZrl(),
'$madeby_addr' => $notification['madeby_addr'], '$madeby_addr' => $notification->getMadeByAddr(),
'$contact_id' => $notification['contact_id'], '$contact_id' => $notification->getContactId(),
'$photo' => $notification['photo'], '$photo' => $notification->getPhoto(),
'$fullname' => $notification['name'], '$fullname' => $notification->getName(),
'$url' => $notification['url'], '$url' => $notification->getUrl(),
'$zrl' => $notification['zrl'], '$zrl' => $notification->getZrl(),
'$lbl_url' => DI::l10n()->t('Profile URL'), '$lbl_url' => DI::l10n()->t('Profile URL'),
'$addr' => $notification['addr'], '$addr' => $notification->getAddr(),
'$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), ($notification['hidden'] == 1), ''], '$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), $notification->isHidden(), ''],
'$knowyou' => $notification['knowyou'], '$knowyou' => $notification->getKnowYou(),
'$approve' => DI::l10n()->t('Approve'), '$approve' => DI::l10n()->t('Approve'),
'$note' => $notification['note'], '$note' => $notification->getNote(),
'$request' => $notification['request'], '$request' => $notification->getRequest(),
'$ignore' => DI::l10n()->t('Ignore'), '$ignore' => DI::l10n()->t('Ignore'),
'$discard' => DI::l10n()->t('Discard'), '$discard' => DI::l10n()->t('Discard'),
]); ]);
@ -90,8 +95,8 @@ class Introductions extends BaseNotifications
// Normal connection requests // Normal connection requests
default: default:
$friend_selected = (($notification['network'] !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled '); $friend_selected = (($notification->getNetwork() !== Protocol::OSTATUS) ? ' checked="checked" ' : ' disabled ');
$fan_selected = (($notification['network'] === Protocol::OSTATUS) ? ' checked="checked" disabled ' : ''); $fan_selected = (($notification->getNetwork() === Protocol::OSTATUS) ? ' checked="checked" disabled ' : '');
$lbl_knowyou = ''; $lbl_knowyou = '';
$knowyou = ''; $knowyou = '';
@ -99,13 +104,13 @@ class Introductions extends BaseNotifications
$helptext2 = ''; $helptext2 = '';
$helptext3 = ''; $helptext3 = '';
if ($notification['network'] === Protocol::DFRN) { if ($notification->getNetwork() === Protocol::DFRN) {
$lbl_knowyou = DI::l10n()->t('Claims to be known to you: '); $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?'); $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']); $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']); $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?'); $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']); $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']); $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_tpl = Renderer::getMarkupTemplate('notifications/netfriend.tpl');
$dfrn_text = Renderer::replaceMacros($dfrn_tpl, [ $dfrn_text = Renderer::replaceMacros($dfrn_tpl, [
'$intro_id' => $notification['intro_id'], '$intro_id' => $notification->getIntroId(),
'$friend_selected' => $friend_selected, '$friend_selected' => $friend_selected,
'$fan_selected' => $fan_selected, '$fan_selected' => $fan_selected,
'$approve_as1' => $helptext, '$approve_as1' => $helptext,
'$approve_as2' => $helptext2, '$approve_as2' => $helptext2,
'$approve_as3' => $helptext3, '$approve_as3' => $helptext3,
'$as_friend' => DI::l10n()->t('Friend'), '$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)) { if (($contact['network'] != Protocol::DFRN) || ($contact['protocol'] == Protocol::ACTIVITYPUB)) {
$action = 'follow_confirm'; $action = 'follow_confirm';
@ -137,45 +142,45 @@ class Introductions extends BaseNotifications
$header .= ' <' . $notification['addr'] . '>'; $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'); $discard = DI::l10n()->t('Discard');
} else { } else {
$discard = ''; $discard = '';
} }
$notificationContent[] = Renderer::replaceMacros($notificationTemplate, [ $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [
'$type' => $notification['label'], '$type' => $notification->getLabel(),
'$header' => $header, '$header' => $header,
'str_notification_type' => DI::l10n()->t('Notification type:'), 'str_notification_type' => DI::l10n()->t('Notification type:'),
'str_type' => $notification['notifytype'], 'str_type' => $notification->getType(),
'$dfrn_text' => $dfrn_text, '$dfrn_text' => $dfrn_text,
'$dfrn_id' => $notification['dfrn_id'], '$dfrn_id' => $notification->getDfrnId(),
'$uid' => $notification['uid'], '$uid' => $notification->getUid(),
'$intro_id' => $notification['intro_id'], '$intro_id' => $notification->getIntroId(),
'$contact_id' => $notification['contact_id'], '$contact_id' => $notification->getContactId(),
'$photo' => $notification['photo'], '$photo' => $notification->getPhoto(),
'$fullname' => $notification['name'], '$fullname' => $notification->getName(),
'$location' => $notification['location'], '$location' => $notification->getLocation(),
'$lbl_location' => DI::l10n()->t('Location:'), '$lbl_location' => DI::l10n()->t('Location:'),
'$about' => $notification['about'], '$about' => $notification->getAbout(),
'$lbl_about' => DI::l10n()->t('About:'), '$lbl_about' => DI::l10n()->t('About:'),
'$keywords' => $notification['keywords'], '$keywords' => $notification->getKeywords(),
'$lbl_keywords' => DI::l10n()->t('Tags:'), '$lbl_keywords' => DI::l10n()->t('Tags:'),
'$gender' => $notification['gender'], '$gender' => $notification->getGender(),
'$lbl_gender' => DI::l10n()->t('Gender:'), '$lbl_gender' => DI::l10n()->t('Gender:'),
'$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), ($notification['hidden'] == 1), ''], '$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), ($notification['hidden'] == 1), ''],
'$url' => $notification['url'], '$url' => $notification->getUrl(),
'$zrl' => $notification['zrl'], '$zrl' => $notification->getZrl(),
'$lbl_url' => DI::l10n()->t('Profile URL'), '$lbl_url' => DI::l10n()->t('Profile URL'),
'$addr' => $notification['addr'], '$addr' => $notification->getAddr(),
'$lbl_knowyou' => $lbl_knowyou, '$lbl_knowyou' => $lbl_knowyou,
'$lbl_network' => DI::l10n()->t('Network:'), '$lbl_network' => DI::l10n()->t('Network:'),
'$network' => ContactSelector::networkToName($notification['network'], $notification['url']), '$network' => ContactSelector::networkToName($notification->getNetwork(), $notification->getUrl()),
'$knowyou' => $knowyou, '$knowyou' => $knowyou,
'$approve' => DI::l10n()->t('Approve'), '$approve' => DI::l10n()->t('Approve'),
'$note' => $notification['note'], '$note' => $notification->getNote(),
'$ignore' => DI::l10n()->t('Ignore'), '$ignore' => DI::l10n()->t('Ignore'),
'$discard' => $discard, '$discard' => $discard,
'$action' => $action, '$action' => $action,

View file

@ -23,7 +23,11 @@ class Notification extends BaseModule
{ {
// @TODO: Replace with parameter from router // @TODO: Replace with parameter from router
if (DI::args()->get(1) === 'mark' && DI::args()->get(2) === 'all') { 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'); header('Content-type: application/json; charset=utf-8');
echo json_encode([ echo json_encode([
@ -43,14 +47,16 @@ class Notification extends BaseModule
{ {
// @TODO: Replace with parameter from router // @TODO: Replace with parameter from router
if (DI::args()->getArgc() > 2 && DI::args()->get(1) === 'view' && intval(DI::args()->get(2))) { if (DI::args()->getArgc() > 2 && DI::args()->get(1) === 'view' && intval(DI::args()->get(2))) {
$notificationManager = DI::notification(); try {
// @TODO: Replace with parameter from router $notification = DI::notification()->getByID(DI::args()->get(2));
$note = $notificationManager->getByID(DI::args()->get(2)); $notification->setSeen();
if (!empty($note)) {
$notificationManager->setSeen($note); if (!empty($notification->link)) {
if (!empty($note['link'])) { System::externalRedirect($notification->link);
System::externalRedirect($note['link']);
} }
} catch (HTTPException\NotFoundException $e) {
info(DI::l10n()->t('Invalid notification.'));
} }
DI::baseUrl()->redirect(); DI::baseUrl()->redirect();

View file

@ -3,10 +3,10 @@
namespace Friendica\Module\Notifications; namespace Friendica\Module\Notifications;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\DI; use Friendica\DI;
use Friendica\Module\BaseNotifications; use Friendica\Module\BaseNotifications;
use Friendica\Object\Notification\Notification;
/** /**
* Prints all notification types except introduction: * Prints all notification types except introduction:
@ -22,41 +22,46 @@ class Notifications extends BaseNotifications
*/ */
public static function getNotifications() public static function getNotifications()
{ {
$nm = DI::notification();
$notificationHeader = ''; $notificationHeader = '';
/** @var Notification[] $notifications */
$notifications = [];
// Get the network notifications // Get the network notifications
if ((DI::args()->get(1) == 'network')) { if ((DI::args()->get(1) == 'network')) {
$notificationHeader = DI::l10n()->t('Network Notifications'); $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 // Get the system notifications
} elseif ((DI::args()->get(1) == 'system')) { } elseif ((DI::args()->get(1) == 'system')) {
$notificationHeader = DI::l10n()->t('System Notifications'); $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 // Get the personal notifications
} elseif ((DI::args()->get(1) == 'personal')) { } elseif ((DI::args()->get(1) == 'personal')) {
$notificationHeader = DI::l10n()->t('Personal Notifications'); $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 // Get the home notifications
} elseif ((DI::args()->get(1) == 'home')) { } elseif ((DI::args()->get(1) == 'home')) {
$notificationHeader = DI::l10n()->t('Home Notifications'); $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 // fallback - redirect to main page
} else { } else {
DI::baseUrl()->redirect('notifications'); 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 [ return [
'header' => $notificationHeader, 'header' => $notificationHeader,
'notifications' => $notifications, 'notifications' => $notifications,
@ -78,6 +83,7 @@ class Notifications extends BaseNotifications
if (!empty($notifications['notifications'])) { if (!empty($notifications['notifications'])) {
// Loop trough ever notification This creates an array with the output html for each // 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). // notification and apply the correct template according to the notificationtype (label).
/** @var Notification $notification */
foreach ($notifications['notifications'] as $notification) { foreach ($notifications['notifications'] as $notification) {
$notification_templates = [ $notification_templates = [
'like' => 'notifications/likes_item.tpl', 'like' => 'notifications/likes_item.tpl',
@ -91,17 +97,17 @@ class Notifications extends BaseNotifications
'notification' => 'notifications/notification.tpl', 'notification' => 'notifications/notification.tpl',
]; ];
$notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notification['label']]); $notificationTemplate = Renderer::getMarkupTemplate($notification_templates[$notification->getLabel()]);
$notificationContent[] = Renderer::replaceMacros($notificationTemplate, [ $notificationContent[] = Renderer::replaceMacros($notificationTemplate, [
'$item_label' => $notification['label'], '$item_label' => $notification->getLabel(),
'$item_link' => $notification['link'], '$item_link' => $notification->getLink(),
'$item_image' => $notification['image'], '$item_image' => $notification->getImage(),
'$item_url' => $notification['url'], '$item_url' => $notification->getUrl(),
'$item_text' => $notification['text'], '$item_text' => $notification->getText(),
'$item_when' => $notification['when'], '$item_when' => $notification->getWhen(),
'$item_ago' => $notification['ago'], '$item_ago' => $notification->getAgo(),
'$item_seen' => $notification['seen'], '$item_seen' => $notification->isSeen(),
]); ]);
} }
} else { } else {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}