From 4bfd0abec73c028a85aa143dc1fd704c5e3c2f54 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 4 Jan 2020 12:21:42 +0000 Subject: [PATCH 01/22] New "UserItem" class, new notification type field --- database.sql | 5 ++- src/Model/UserItem.php | 85 +++++++++++++++++++++++++++++++++++ static/dbstructure.config.php | 5 ++- 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 src/Model/UserItem.php diff --git a/database.sql b/database.sql index 6a55db5269..02d1ac895c 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ --- Friendica 2019.12-rc (Dalmatian Bellflower) --- DB_UPDATE_VERSION 1328 +-- Friendica 2020.03-dev (Dalmatian Bellflower) +-- DB_UPDATE_VERSION 1329 -- ------------------------------------------ @@ -1285,6 +1285,7 @@ CREATE TABLE IF NOT EXISTS `user-item` ( `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide an item from the user', `ignored` boolean COMMENT 'Ignore this thread if set', `pinned` boolean COMMENT 'The item is pinned on the profile page', + `notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', PRIMARY KEY(`uid`,`iid`), INDEX `uid_pinned` (`uid`,`pinned`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php new file mode 100644 index 0000000000..26c42f1093 --- /dev/null +++ b/src/Model/UserItem.php @@ -0,0 +1,85 @@ + $item['contact-id'], 'notify_new_posts' => true])) { + return true; + } + + // Or the contact is a mentioned forum + $tags = DBA::select('term', ['url'], ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => TERM_MENTION, 'uid' => $uid]); + while ($tag = DBA::fetch($tags)) { + $condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY]; + if (DBA::exists('contact', $condition)) { + return true; + } + } + + return false; + } + + // Is the user mentioned in this post? + private static function checkTagged($item, $uid) + { + if ($item["mention"]) { + return true; + } + + foreach ($profiles AS $profile) { + if (strpos($item["tag"], "=".$profile."]") || strpos($item["body"], "=".$profile."]")) + return true; + } + + if ($defaulttype == NOTIFY_TAGSELF) { + return true; + } + + return false; + } + + private static function checkCommented($item, $uid) + { + // Is it a post that the user had started? + $fields = ['ignored', 'mention']; + $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item["parent"], 'deleted' => false]); + + if ($thread['mention'] && !$thread['ignored']) { + return true; + } + + // And now we check for participation of one of our contacts in the thread + $condition = ['parent' => $item["parent"], 'author-id' => $contacts, 'deleted' => false]; + + if (!$thread['ignored'] && Item::exists($condition)) { + return true; + } + + return false; + } +} diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index ada837fb26..a7d851943d 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -34,7 +34,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1328); + define('DB_UPDATE_VERSION', 1329); } return [ @@ -1388,7 +1388,8 @@ return [ "uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["user" => "uid"], "comment" => "User id"], "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide an item from the user"], "ignored" => ["type" => "boolean", "comment" => "Ignore this thread if set"], - "pinned" => ["type" => "boolean", "comment" => "The item is pinned on the profile page"] + "pinned" => ["type" => "boolean", "comment" => "The item is pinned on the profile page"], + "notification-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], ], "indexes" => [ "PRIMARY" => ["uid", "iid"], From 3a1798d1c3498acc8f64443eab6d504e8a38e685 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 4 Jan 2020 21:45:20 +0000 Subject: [PATCH 02/22] First testable version --- src/Model/UserItem.php | 84 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 26c42f1093..1888906fa7 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -6,7 +6,10 @@ namespace Friendica\Model; +use Friendica\Core\Hook; use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Util\Strings; class UserItem { @@ -18,6 +21,66 @@ class UserItem */ public static function setNotification($item, $uid) { + if (self::checkShared($item, $uid)) { + echo "shared\n"; + } + + $profiles = self::getProfileForUser($uid); + + if (self::checkTagged($item, $uid, $profiles)) { + echo "tagged\n"; + } + + if (self::checkCommented($item, $uid, $profiles)) { + echo "commented\n"; + } + } + + private static function getProfileForUser($uid) + { + $notification_data = ["uid" => $uid, "profiles" => []]; + Hook::callAll('check_item_notification', $notification_data); + + $profiles = $notification_data["profiles"]; + + $fields = ['nickname']; + $user = DBA::selectFirst('user', $fields, ['uid' => $uid]); + if (!DBA::isResult($user)) { + return false; + } + + $owner = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]); + if (!DBA::isResult($owner)) { + return false; + } + + // This is our regular URL format + $profiles[] = $owner["url"]; + + // Notifications from Diaspora are often with an URL in the Diaspora format + $profiles[] = DI::baseUrl()."/u/".$user["nickname"]; + + $profiles2 = []; + + foreach ($profiles AS $profile) { + // Check for invalid profile urls. 13 should be the shortest possible profile length: + // http://a.bc/d + // Additionally check for invalid urls that would return the normalised value "http:" + if ((strlen($profile) >= 13) && (Strings::normaliseLink($profile) != "http:")) { + if (!in_array($profile, $profiles2)) + $profiles2[] = $profile; + + $profile = Strings::normaliseLink($profile); + if (!in_array($profile, $profiles2)) + $profiles2[] = $profile; + + $profile = str_replace("http://", "https://", $profile); + if (!in_array($profile, $profiles2)) + $profiles2[] = $profile; + } + } + + return $profiles2; } private static function checkShared($item, $uid) @@ -45,34 +108,33 @@ class UserItem } // Is the user mentioned in this post? - private static function checkTagged($item, $uid) + private static function checkTagged($item, $uid, $profiles) { - if ($item["mention"]) { - return true; - } - foreach ($profiles AS $profile) { if (strpos($item["tag"], "=".$profile."]") || strpos($item["body"], "=".$profile."]")) return true; } - if ($defaulttype == NOTIFY_TAGSELF) { - return true; - } - return false; } - private static function checkCommented($item, $uid) + // Fetch all contacts for the given profiles + private static function checkCommented($item, $uid, $profiles) { // Is it a post that the user had started? $fields = ['ignored', 'mention']; $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item["parent"], 'deleted' => false]); - if ($thread['mention'] && !$thread['ignored']) { return true; } + $contacts = []; + $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); + while ($contact = DBA::fetch($ret)) { + $contacts[] = $contact['id']; + } + DBA::close($ret); + // And now we check for participation of one of our contacts in the thread $condition = ['parent' => $item["parent"], 'author-id' => $contacts, 'deleted' => false]; From ecb4848dfd093cbb69d717b9a9da08db89407869 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 00:54:18 +0000 Subject: [PATCH 03/22] Tests are now working --- src/Model/UserItem.php | 129 ++++++++++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 35 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 1888906fa7..ea61696e39 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -13,6 +13,15 @@ use Friendica\Util\Strings; class UserItem { + const NOTIF_NONE = 0; + const NOTIF_EXPLICIT_TAGGED = 1; + const NOTIF_IMPLICIT_TAGGED = 2; + const NOTIF_THREAD_COMMENT = 4; + const NOTIF_DIRECT_COMMENT = 8; + const NOTIF_COMMENT_PARTICIPATION = 16; + const NOTIF_ACTIVITY_PARTICIPATION = 32; + const NOTIF_SHARED = 128; + /** * Checks an item for notifications and sets the "notification-type" field * @@ -21,27 +30,66 @@ class UserItem */ public static function setNotification($item, $uid) { + // Don't check for own posts + if ($item['origin'] || empty($uid)) { + return; + } + + $fields = ['ignored', 'mention']; + $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item['parent'], 'deleted' => false]); + if ($thread['ignored']) { + return; + } + + $notification_type = self::NOTIF_NONE; + if (self::checkShared($item, $uid)) { - echo "shared\n"; + $notification_type = $notification_type | self::NOTIF_SHARED; } $profiles = self::getProfileForUser($uid); - if (self::checkTagged($item, $uid, $profiles)) { - echo "tagged\n"; + if (self::checkImplicitMention($item, $uid, $profiles)) { + $notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED; } - if (self::checkCommented($item, $uid, $profiles)) { - echo "commented\n"; + if (self::checkExplicitMention($item, $uid, $profiles)) { + $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; } + + $contacts = []; + $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); + while ($contact = DBA::fetch($ret)) { + $contacts[] = $contact['id']; + } + DBA::close($ret); + + if (self::checkCommentedThread($item, $uid, $contacts)) { + $notification_type = $notification_type | self::NOTIF_THREAD_COMMENT; + } + + if (self::checkDirectComment($item, $uid, $contacts, $thread)) { + $notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT; + } + + if (self::checkCommentedParticipation($item, $uid, $contacts)) { + $notification_type = $notification_type | self::NOTIF_COMMENT_PARTICIPATION; + } + + if (self::checkActivityParticipation($item, $uid, $contacts)) { + $notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION; + } + + DBA::update('user-item', ['notification-type' => $notification_type], ['iid' => $item['id'], 'uid' => $uid], true); } + // Fetch all contacts for the given profiles private static function getProfileForUser($uid) { - $notification_data = ["uid" => $uid, "profiles" => []]; + $notification_data = ['uid' => $uid, 'profiles' => []]; Hook::callAll('check_item_notification', $notification_data); - $profiles = $notification_data["profiles"]; + $profiles = $notification_data['profiles']; $fields = ['nickname']; $user = DBA::selectFirst('user', $fields, ['uid' => $uid]); @@ -55,10 +103,10 @@ class UserItem } // This is our regular URL format - $profiles[] = $owner["url"]; + $profiles[] = $owner['url']; // Notifications from Diaspora are often with an URL in the Diaspora format - $profiles[] = DI::baseUrl()."/u/".$user["nickname"]; + $profiles[] = DI::baseUrl().'/u/'.$user['nickname']; $profiles2 = []; @@ -66,7 +114,7 @@ class UserItem // Check for invalid profile urls. 13 should be the shortest possible profile length: // http://a.bc/d // Additionally check for invalid urls that would return the normalised value "http:" - if ((strlen($profile) >= 13) && (Strings::normaliseLink($profile) != "http:")) { + if ((strlen($profile) >= 13) && (Strings::normaliseLink($profile) != 'http:')) { if (!in_array($profile, $profiles2)) $profiles2[] = $profile; @@ -74,7 +122,7 @@ class UserItem if (!in_array($profile, $profiles2)) $profiles2[] = $profile; - $profile = str_replace("http://", "https://", $profile); + $profile = str_replace('http://', 'https://', $profile); if (!in_array($profile, $profiles2)) $profiles2[] = $profile; } @@ -98,7 +146,7 @@ class UserItem // Or the contact is a mentioned forum $tags = DBA::select('term', ['url'], ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => TERM_MENTION, 'uid' => $uid]); while ($tag = DBA::fetch($tags)) { - $condition = ['nurl' => Strings::normaliseLink($tag["url"]), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY]; + $condition = ['nurl' => Strings::normaliseLink($tag['url']), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY]; if (DBA::exists('contact', $condition)) { return true; } @@ -108,40 +156,51 @@ class UserItem } // Is the user mentioned in this post? - private static function checkTagged($item, $uid, $profiles) + private static function checkImplicitMention($item, $uid, $profiles) { foreach ($profiles AS $profile) { - if (strpos($item["tag"], "=".$profile."]") || strpos($item["body"], "=".$profile."]")) + if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) return true; } return false; } - // Fetch all contacts for the given profiles - private static function checkCommented($item, $uid, $profiles) + private static function checkExplicitMention($item, $uid, $profiles) { - // Is it a post that the user had started? - $fields = ['ignored', 'mention']; - $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item["parent"], 'deleted' => false]); - if ($thread['mention'] && !$thread['ignored']) { - return true; - } - - $contacts = []; - $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); - while ($contact = DBA::fetch($ret)) { - $contacts[] = $contact['id']; - } - DBA::close($ret); - - // And now we check for participation of one of our contacts in the thread - $condition = ['parent' => $item["parent"], 'author-id' => $contacts, 'deleted' => false]; - - if (!$thread['ignored'] && Item::exists($condition)) { - return true; + foreach ($profiles AS $profile) { + if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) + return !(strpos($item['body'], $profile) === false); } return false; } + + // Is it a post that the user had started? + private static function checkCommentedThread($item, $uid, $contacts) + { + // Additional check for connector posts + $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; + return Item::exists($condition); + } + + private static function checkDirectComment($item, $uid, $contacts) + { + // Additional check for connector posts + $condition = ['uri' => $item['thr-parent'], 'uid' => [0, $uid], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; + return Item::exists($condition); + } + + // Check for participation of one of our contacts in the thread + private static function checkCommentedParticipation($item, $uid, $contacts) + { + $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; + return Item::exists($condition); + } + + private static function checkActivityParticipation($item, $uid, $contacts) + { + $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY]; + return Item::exists($condition); + } } From 8e2494568e4e9dcd704dbe8756eba6ec3cbae878 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 01:23:40 +0000 Subject: [PATCH 04/22] Functionality is now added --- src/Model/Item.php | 2 ++ src/Model/UserItem.php | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index c9d71e8c1e..e030141fa8 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2026,6 +2026,8 @@ class Item self::updateContact($item); + UserItem::setNotification($current_post, $item['uid']); + check_user_notification($current_post); if ($notify || ($item['visible'] && ((!empty($parent) && $parent['origin']) || $item['origin']))) { diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index ea61696e39..d0d1ebad98 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -6,6 +6,7 @@ namespace Friendica\Model; +use Friendica\Core\Logger; use Friendica\Core\Hook; use Friendica\Database\DBA; use Friendica\DI; @@ -28,8 +29,12 @@ class UserItem * @param array $item The message array that is checked for notifications * @param int $uid User ID */ - public static function setNotification($item, $uid) + public static function setNotification($iid, $uid) { + $fields = ['id', 'body', 'origin', 'parent', 'gravity', 'tag', 'contact-id', + 'thr-parent', 'parent-uri', 'mention']; + $item = Item::selectFirst($fields, ['id' => $iid]); + // Don't check for own posts if ($item['origin'] || empty($uid)) { return; @@ -80,6 +85,12 @@ class UserItem $notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION; } + if (empty($notification_type)) { + return; + } + + Logger::info('Set notification', ['iid' => $item['id'], 'uid' => $uid, 'notification-type' => $notification_type]); + DBA::update('user-item', ['notification-type' => $notification_type], ['iid' => $item['id'], 'uid' => $uid], true); } @@ -144,7 +155,7 @@ class UserItem } // Or the contact is a mentioned forum - $tags = DBA::select('term', ['url'], ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => TERM_MENTION, 'uid' => $uid]); + $tags = DBA::select('term', ['url'], ['otype' => TERM_OBJ_POST, 'oid' => $item['id'], 'type' => TERM_MENTION, 'uid' => $uid]); while ($tag = DBA::fetch($tags)) { $condition = ['nurl' => Strings::normaliseLink($tag['url']), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY]; if (DBA::exists('contact', $condition)) { From e728c4829fd323a2ccbdf37d81963a667dafeb9d Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 02:18:11 +0000 Subject: [PATCH 05/22] Restructuring --- src/Model/Item.php | 2 +- src/Model/UserItem.php | 93 +++++++++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index e030141fa8..f58225c032 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2026,7 +2026,7 @@ class Item self::updateContact($item); - UserItem::setNotification($current_post, $item['uid']); + UserItem::setNotification($current_post); check_user_notification($current_post); diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index d0d1ebad98..7a0c4dee80 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -26,20 +26,26 @@ class UserItem /** * Checks an item for notifications and sets the "notification-type" field * - * @param array $item The message array that is checked for notifications - * @param int $uid User ID + * @param int $iid Item ID */ - public static function setNotification($iid, $uid) + public static function setNotification(int $iid) { - $fields = ['id', 'body', 'origin', 'parent', 'gravity', 'tag', 'contact-id', + $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'mention']; - $item = Item::selectFirst($fields, ['id' => $iid]); - - // Don't check for own posts - if ($item['origin'] || empty($uid)) { + $item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]); + if (!DBA::isResult($item)) { return; } + if (!empty($item['uid'])) { + self::setNotificationForUser($item, $item['uid']); + return; + } + // Alle user des Threads ermitteln + } + + private static function setNotificationForUser(array $item, int $uid) + { $fields = ['ignored', 'mention']; $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item['parent'], 'deleted' => false]); if ($thread['ignored']) { @@ -62,6 +68,7 @@ class UserItem $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; } + // Fetch all contacts for the given profiles $contacts = []; $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); while ($contact = DBA::fetch($ret)) { @@ -77,11 +84,11 @@ class UserItem $notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT; } - if (self::checkCommentedParticipation($item, $uid, $contacts)) { + if (self::checkCommentedParticipation($item, $contacts)) { $notification_type = $notification_type | self::NOTIF_COMMENT_PARTICIPATION; } - if (self::checkActivityParticipation($item, $uid, $contacts)) { + if (self::checkActivityParticipation($item, $contacts)) { $notification_type = $notification_type | self::NOTIF_ACTIVITY_PARTICIPATION; } @@ -94,7 +101,12 @@ class UserItem DBA::update('user-item', ['notification-type' => $notification_type], ['iid' => $item['id'], 'uid' => $uid], true); } - // Fetch all contacts for the given profiles + /** + * Fetch all profiles of a given user + * @param int $uid User ID + * + * @return array Profiles + */ private static function getProfileForUser($uid) { $notification_data = ['uid' => $uid, 'profiles' => []]; @@ -105,12 +117,12 @@ class UserItem $fields = ['nickname']; $user = DBA::selectFirst('user', $fields, ['uid' => $uid]); if (!DBA::isResult($user)) { - return false; + return []; } $owner = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]); if (!DBA::isResult($owner)) { - return false; + return []; } // This is our regular URL format @@ -142,6 +154,11 @@ class UserItem return $profiles2; } + /** + * Check for a "shared" notification for the given item and user + * @param array $item + * @param int $uid User ID + */ private static function checkShared($item, $uid) { if ($item['gravity'] != GRAVITY_PARENT) { @@ -167,27 +184,48 @@ class UserItem } // Is the user mentioned in this post? + /** + * Check for a "shared" notification for the given item and user + * @param array $item + * @param int $uid User ID + */ private static function checkImplicitMention($item, $uid, $profiles) { foreach ($profiles AS $profile) { - if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) - return true; + if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { + if (strpos($item['body'], $profile) === false) { + return true; + } + } } return false; } + /** + * Check for a "shared" notification for the given item and user + * @param array $item + * @param int $uid User ID + */ private static function checkExplicitMention($item, $uid, $profiles) { foreach ($profiles AS $profile) { - if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) - return !(strpos($item['body'], $profile) === false); + if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { + if (!(strpos($item['body'], $profile) === false)) { + return true; + } + } } return false; } // Is it a post that the user had started? + /** + * Check for a "shared" notification for the given item and user + * @param array $item + * @param int $uid User ID + */ private static function checkCommentedThread($item, $uid, $contacts) { // Additional check for connector posts @@ -195,6 +233,12 @@ class UserItem return Item::exists($condition); } + /** + * Check for a direct comment to a post of the given user + * @param array $item + * @param int $uid User ID + * @param array $contacts Array of contacts + */ private static function checkDirectComment($item, $uid, $contacts) { // Additional check for connector posts @@ -202,14 +246,23 @@ class UserItem return Item::exists($condition); } - // Check for participation of one of our contacts in the thread - private static function checkCommentedParticipation($item, $uid, $contacts) + /** + * Check if the user had commented in this thread + * @param array $item + * @param array $contacts Array of contacts + */ + private static function checkCommentedParticipation($item, $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Item::exists($condition); } - private static function checkActivityParticipation($item, $uid, $contacts) + /** + * Check if the user had interacted in this thread (Like, Dislike, ...) + * @param array $item + * @param array $contacts Array of contacts + */ + private static function checkActivityParticipation($item, $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY]; return Item::exists($condition); From fe1a702e8c3cb1003927e2d7a3d1683fb8eca762 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 09:08:40 +0000 Subject: [PATCH 06/22] Improved documentation, now checking all items --- src/Model/UserItem.php | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 7a0c4dee80..029e7afb62 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -14,6 +14,7 @@ use Friendica\Util\Strings; class UserItem { + // Notification types const NOTIF_NONE = 0; const NOTIF_EXPLICIT_TAGGED = 1; const NOTIF_IMPLICIT_TAGGED = 2; @@ -37,13 +38,22 @@ class UserItem return; } - if (!empty($item['uid'])) { - self::setNotificationForUser($item, $item['uid']); - return; + // fetch all users in the thread + $users = DBA::p("SELECT DISTINCT(`contact`.`uid`) FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` != 0 + WHERE `parent` IN (SELECT `parent` FROM `item` WHERE `id`=?)", $iid); + while ($user = DBA::fetch($users)) { + self::setNotificationForUser($item, $user['uid']); } - // Alle user des Threads ermitteln + DBA::close($users); } + /** + * Checks an item for notifications for the given user and sets the "notification-type" field + * + * @param array $item Item array + * @param int $uid User ID + */ private static function setNotificationForUser(array $item, int $uid) { $fields = ['ignored', 'mention']; @@ -102,7 +112,7 @@ class UserItem } /** - * Fetch all profiles of a given user + * Fetch all profiles (contact URL) of a given user * @param int $uid User ID * * @return array Profiles @@ -114,8 +124,7 @@ class UserItem $profiles = $notification_data['profiles']; - $fields = ['nickname']; - $user = DBA::selectFirst('user', $fields, ['uid' => $uid]); + $user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]); if (!DBA::isResult($user)) { return []; } @@ -155,9 +164,10 @@ class UserItem } /** - * Check for a "shared" notification for the given item and user + * Check for a "shared" notification for every new post of contacts from the given user * @param array $item * @param int $uid User ID + * @return bool A contact had shared something */ private static function checkShared($item, $uid) { @@ -165,7 +175,6 @@ class UserItem return false; } - // Send a notification for every new post? // Either the contact had posted something directly if (DBA::exists('contact', ['id' => $item['contact-id'], 'notify_new_posts' => true])) { return true; @@ -183,11 +192,11 @@ class UserItem return false; } - // Is the user mentioned in this post? /** - * Check for a "shared" notification for the given item and user + * Check for an implicit mention (only tag, no body) of the given user * @param array $item * @param int $uid User ID + * @return bool The user is mentioned */ private static function checkImplicitMention($item, $uid, $profiles) { @@ -203,9 +212,10 @@ class UserItem } /** - * Check for a "shared" notification for the given item and user + * Check for an explicit mention (tag and body) of the given user * @param array $item * @param int $uid User ID + * @return bool The user is mentioned */ private static function checkExplicitMention($item, $uid, $profiles) { @@ -220,15 +230,14 @@ class UserItem return false; } - // Is it a post that the user had started? /** - * Check for a "shared" notification for the given item and user + * Check if the given user had created this thread * @param array $item * @param int $uid User ID + * @return bool The user had created this thread */ private static function checkCommentedThread($item, $uid, $contacts) { - // Additional check for connector posts $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; return Item::exists($condition); } @@ -238,10 +247,10 @@ class UserItem * @param array $item * @param int $uid User ID * @param array $contacts Array of contacts + * @return bool The item is a direct comment to a user comment */ private static function checkDirectComment($item, $uid, $contacts) { - // Additional check for connector posts $condition = ['uri' => $item['thr-parent'], 'uid' => [0, $uid], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Item::exists($condition); } @@ -250,6 +259,7 @@ class UserItem * Check if the user had commented in this thread * @param array $item * @param array $contacts Array of contacts + * @return bool The user had commented in the thread */ private static function checkCommentedParticipation($item, $contacts) { @@ -261,6 +271,7 @@ class UserItem * Check if the user had interacted in this thread (Like, Dislike, ...) * @param array $item * @param array $contacts Array of contacts + * @return bool The user had interacted in the thread */ private static function checkActivityParticipation($item, $contacts) { From f183816b7da6c40a8db44c3b8f356b8f995accbe Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 09:10:03 +0000 Subject: [PATCH 07/22] Notification type can now be selected --- src/Model/Item.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index f58225c032..4363a9a789 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -656,7 +656,7 @@ class Item 'iaid' => 'internal-iaid']; if ($usermode) { - $fields['user-item'] = ['pinned', 'ignored' => 'internal-user-ignored']; + $fields['user-item'] = ['pinned', 'notification-type', 'ignored' => 'internal-user-ignored']; } $fields['item-activity'] = ['activity', 'activity' => 'internal-activity']; From a4268a973f627429ec0294a98a1024b457f29a2c Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 09:10:32 +0000 Subject: [PATCH 08/22] Added post update --- src/Database/PostUpdate.php | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index 885c9eea01..5a2bbae058 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -10,6 +10,7 @@ use Friendica\Core\Protocol; use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\ItemURI; +use Friendica\Model\UserItem; use Friendica\Model\PermissionSet; /** @@ -40,6 +41,9 @@ class PostUpdate if (!self::update1322()) { return false; } + if (!self::update1329()) { + return false; + } return true; } @@ -453,4 +457,52 @@ class PostUpdate return true; } + + /** + * @brief update user-item data with notifications + * + * @return bool "true" when the job is done + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function update1329() + { + // Was the script completed? + if (Config::get('system', 'post_update_version') >= 1329) { + return true; + } + + $id = Config::get('system', 'post_update_version_1329_id', 0); + + Logger::info('Start', ['item' => $id]); + + $start_id = $id; + $rows = 0; + $condition = ["`id` > ?", $id]; + $params = ['order' => ['id'], 'limit' => 10000]; + $items = DBA::select('item', ['id'], $condition, $params); + + if (DBA::errorNo() != 0) { + Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]); + return false; + } + + while ($item = DBA::fetch($items)) { + UserItem::setNotification($item['id']); + + ++$rows; + } + DBA::close($items); + + Config::set('system', 'post_update_version_1329_id', $id); + + Logger::info('Processed', ['rows' => $rows, 'last' => $id]); + + if ($start_id == $id) { + Config::set('system', 'post_update_version', 1329); + Logger::info('Done'); + return true; + } + + return false; + } } From 4b44aca50735dc047e495825bff7dfa717acb615 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 09:48:31 +0000 Subject: [PATCH 09/22] Don't create notifications for own posts --- src/Model/UserItem.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 029e7afb62..698176454d 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -31,8 +31,7 @@ class UserItem */ public static function setNotification(int $iid) { - $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', - 'thr-parent', 'parent-uri', 'mention']; + $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'author-id']; $item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]); if (!DBA::isResult($item)) { return; @@ -56,8 +55,7 @@ class UserItem */ private static function setNotificationForUser(array $item, int $uid) { - $fields = ['ignored', 'mention']; - $thread = Item::selectFirstThreadForUser($uid, $fields, ['iid' => $item['parent'], 'deleted' => false]); + $thread = Item::selectFirstThreadForUser($uid, ['ignored'], ['iid' => $item['parent'], 'deleted' => false]); if ($thread['ignored']) { return; } @@ -70,14 +68,6 @@ class UserItem $profiles = self::getProfileForUser($uid); - if (self::checkImplicitMention($item, $uid, $profiles)) { - $notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED; - } - - if (self::checkExplicitMention($item, $uid, $profiles)) { - $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; - } - // Fetch all contacts for the given profiles $contacts = []; $ret = DBA::select('contact', ['id'], ['uid' => 0, 'nurl' => $profiles]); @@ -86,6 +76,19 @@ class UserItem } DBA::close($ret); + // Don't create notifications for user's posts + if (in_array($item['author-id'], $contacts)) { + return; + } + + if (self::checkImplicitMention($item, $uid, $profiles)) { + $notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED; + } + + if (self::checkExplicitMention($item, $uid, $profiles)) { + $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; + } + if (self::checkCommentedThread($item, $uid, $contacts)) { $notification_type = $notification_type | self::NOTIF_THREAD_COMMENT; } From 20d570823d081105505de324eb0644b8463a4abc Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 10:16:01 +0000 Subject: [PATCH 10/22] Fix post update / clean up code --- src/Database/PostUpdate.php | 2 ++ src/Model/UserItem.php | 17 +++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index 5a2bbae058..f0050e0ef3 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -487,6 +487,8 @@ class PostUpdate } while ($item = DBA::fetch($items)) { + $id = $item['id']; + UserItem::setNotification($item['id']); ++$rows; diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 698176454d..8b3b2b9f14 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -81,19 +81,19 @@ class UserItem return; } - if (self::checkImplicitMention($item, $uid, $profiles)) { + if (self::checkImplicitMention($item, $profiles)) { $notification_type = $notification_type | self::NOTIF_IMPLICIT_TAGGED; } - if (self::checkExplicitMention($item, $uid, $profiles)) { + if (self::checkExplicitMention($item, $profiles)) { $notification_type = $notification_type | self::NOTIF_EXPLICIT_TAGGED; } - if (self::checkCommentedThread($item, $uid, $contacts)) { + if (self::checkCommentedThread($item, $contacts)) { $notification_type = $notification_type | self::NOTIF_THREAD_COMMENT; } - if (self::checkDirectComment($item, $uid, $contacts, $thread)) { + if (self::checkDirectComment($item, $uid, $contacts)) { $notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT; } @@ -198,10 +198,9 @@ class UserItem /** * Check for an implicit mention (only tag, no body) of the given user * @param array $item - * @param int $uid User ID * @return bool The user is mentioned */ - private static function checkImplicitMention($item, $uid, $profiles) + private static function checkImplicitMention($item, $profiles) { foreach ($profiles AS $profile) { if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { @@ -217,10 +216,9 @@ class UserItem /** * Check for an explicit mention (tag and body) of the given user * @param array $item - * @param int $uid User ID * @return bool The user is mentioned */ - private static function checkExplicitMention($item, $uid, $profiles) + private static function checkExplicitMention($item, $profiles) { foreach ($profiles AS $profile) { if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { @@ -236,10 +234,9 @@ class UserItem /** * Check if the given user had created this thread * @param array $item - * @param int $uid User ID * @return bool The user had created this thread */ - private static function checkCommentedThread($item, $uid, $contacts) + private static function checkCommentedThread($item, $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; return Item::exists($condition); From 6cbfa5b862ce7a241fd4339129749f87dc19760c Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 11:00:57 +0000 Subject: [PATCH 11/22] Added type hints --- src/Model/UserItem.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 8b3b2b9f14..a95630577d 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -31,7 +31,7 @@ class UserItem */ public static function setNotification(int $iid) { - $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'author-id']; + $fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id']; $item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]); if (!DBA::isResult($item)) { return; @@ -120,7 +120,7 @@ class UserItem * * @return array Profiles */ - private static function getProfileForUser($uid) + private static function getProfileForUser(int $uid) { $notification_data = ['uid' => $uid, 'profiles' => []]; Hook::callAll('check_item_notification', $notification_data); @@ -172,7 +172,7 @@ class UserItem * @param int $uid User ID * @return bool A contact had shared something */ - private static function checkShared($item, $uid) + private static function checkShared(array $item, int $uid) { if ($item['gravity'] != GRAVITY_PARENT) { return false; @@ -198,9 +198,10 @@ class UserItem /** * Check for an implicit mention (only tag, no body) of the given user * @param array $item + * @param array $profiles * @return bool The user is mentioned */ - private static function checkImplicitMention($item, $profiles) + private static function checkImplicitMention(array $item, array $profiles) { foreach ($profiles AS $profile) { if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { @@ -216,9 +217,10 @@ class UserItem /** * Check for an explicit mention (tag and body) of the given user * @param array $item + * @param array $profiles * @return bool The user is mentioned */ - private static function checkExplicitMention($item, $profiles) + private static function checkExplicitMention(array $item, array $profiles) { foreach ($profiles AS $profile) { if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { @@ -234,9 +236,10 @@ class UserItem /** * Check if the given user had created this thread * @param array $item + * @param array $contacts Array of contact IDs * @return bool The user had created this thread */ - private static function checkCommentedThread($item, $contacts) + private static function checkCommentedThread(array $item, array $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT]; return Item::exists($condition); @@ -246,10 +249,10 @@ class UserItem * Check for a direct comment to a post of the given user * @param array $item * @param int $uid User ID - * @param array $contacts Array of contacts + * @param array $contacts Array of contact IDs * @return bool The item is a direct comment to a user comment */ - private static function checkDirectComment($item, $uid, $contacts) + private static function checkDirectComment(array $item, int $uid, array $contacts) { $condition = ['uri' => $item['thr-parent'], 'uid' => [0, $uid], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Item::exists($condition); @@ -258,10 +261,10 @@ class UserItem /** * Check if the user had commented in this thread * @param array $item - * @param array $contacts Array of contacts + * @param array $contacts Array of contact IDs * @return bool The user had commented in the thread */ - private static function checkCommentedParticipation($item, $contacts) + private static function checkCommentedParticipation(array $item, array $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Item::exists($condition); @@ -270,10 +273,10 @@ class UserItem /** * Check if the user had interacted in this thread (Like, Dislike, ...) * @param array $item - * @param array $contacts Array of contacts + * @param array $contacts Array of contact IDs * @return bool The user had interacted in the thread */ - private static function checkActivityParticipation($item, $contacts) + private static function checkActivityParticipation(array $item, array $contacts) { $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY]; return Item::exists($condition); From 23b73854a1e0d574293bf098d818d4b3e0f696a9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 12:00:15 +0000 Subject: [PATCH 12/22] Spaces --- src/Model/UserItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index a95630577d..fc778f72e5 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -141,7 +141,7 @@ class UserItem $profiles[] = $owner['url']; // Notifications from Diaspora are often with an URL in the Diaspora format - $profiles[] = DI::baseUrl().'/u/'.$user['nickname']; + $profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; $profiles2 = []; @@ -204,7 +204,7 @@ class UserItem private static function checkImplicitMention(array $item, array $profiles) { foreach ($profiles AS $profile) { - if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { + if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) { if (strpos($item['body'], $profile) === false) { return true; } @@ -223,7 +223,7 @@ class UserItem private static function checkExplicitMention(array $item, array $profiles) { foreach ($profiles AS $profile) { - if (strpos($item['tag'], '='.$profile.']') || strpos($item['body'], '='.$profile.']')) { + if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) { if (!(strpos($item['body'], $profile) === false)) { return true; } From dcf5471e19051e9a6c04f5702028796714d3b7df Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 12:07:02 +0000 Subject: [PATCH 13/22] Clarified description --- src/Model/UserItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index fc778f72e5..2d00bf73e2 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -118,7 +118,7 @@ class UserItem * Fetch all profiles (contact URL) of a given user * @param int $uid User ID * - * @return array Profiles + * @return array Profile links */ private static function getProfileForUser(int $uid) { @@ -198,7 +198,7 @@ class UserItem /** * Check for an implicit mention (only tag, no body) of the given user * @param array $item - * @param array $profiles + * @param array $profiles Profile links * @return bool The user is mentioned */ private static function checkImplicitMention(array $item, array $profiles) @@ -217,7 +217,7 @@ class UserItem /** * Check for an explicit mention (tag and body) of the given user * @param array $item - * @param array $profiles + * @param array $profiles Profile links * @return bool The user is mentioned */ private static function checkExplicitMention(array $item, array $profiles) From bad50d31c09fc04e978f1de78756d22b60711ee9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 12:48:18 +0000 Subject: [PATCH 14/22] Added index for performance considerations --- database.sql | 3 ++- static/dbstructure.config.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/database.sql b/database.sql index 02d1ac895c..6252f80579 100644 --- a/database.sql +++ b/database.sql @@ -1287,7 +1287,8 @@ CREATE TABLE IF NOT EXISTS `user-item` ( `pinned` boolean COMMENT 'The item is pinned on the profile page', `notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', PRIMARY KEY(`uid`,`iid`), - INDEX `uid_pinned` (`uid`,`pinned`) + INDEX `uid_pinned` (`uid`,`pinned`), + INDEX `iid_uid` (`iid`,`uid`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data'; -- diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index a7d851943d..20bd937690 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -1393,7 +1393,8 @@ return [ ], "indexes" => [ "PRIMARY" => ["uid", "iid"], - "uid_pinned" => ["uid", "pinned"] + "uid_pinned" => ["uid", "pinned"], + "iid_uid" => ["iid", "uid"] ] ], "worker-ipc" => [ From 4e7aa33598fc805b77c731457d2a87b3c45a424e Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 12:49:57 +0000 Subject: [PATCH 15/22] Restructured profile generation --- src/Model/UserItem.php | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 2d00bf73e2..ab81be5e4c 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -125,7 +125,7 @@ class UserItem $notification_data = ['uid' => $uid, 'profiles' => []]; Hook::callAll('check_item_notification', $notification_data); - $profiles = $notification_data['profiles']; + $raw_profiles = $notification_data['profiles']; $user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]); if (!DBA::isResult($user)) { @@ -138,32 +138,41 @@ class UserItem } // This is our regular URL format - $profiles[] = $owner['url']; + $raw_profiles[] = $owner['url']; // Notifications from Diaspora are often with an URL in the Diaspora format - $profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; + $raw_profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; - $profiles2 = []; + $profiles = []; - foreach ($profiles AS $profile) { + // Validate and add profile links + foreach ($raw_profiles AS $profile) { // Check for invalid profile urls. 13 should be the shortest possible profile length: // http://a.bc/d // Additionally check for invalid urls that would return the normalised value "http:" - if ((strlen($profile) >= 13) && (Strings::normaliseLink($profile) != 'http:')) { - if (!in_array($profile, $profiles2)) - $profiles2[] = $profile; + if ((strlen($profile) < 13) || (Strings::normaliseLink($profile) == 'http:')) { + continue; + } - $profile = Strings::normaliseLink($profile); - if (!in_array($profile, $profiles2)) - $profiles2[] = $profile; + // Add the profile if it wasn't already added + if (!in_array($profile, $profiles)) { + $profiles[] = $profile; + } - $profile = str_replace('http://', 'https://', $profile); - if (!in_array($profile, $profiles2)) - $profiles2[] = $profile; + // Add the normalized form + $profile = Strings::normaliseLink($profile); + if (!in_array($profile, $profiles)) { + $profiles[] = $profile; + } + + // Add the SSL form + $profile = str_replace('http://', 'https://', $profile); + if (!in_array($profile, $profiles)) { + $profiles[] = $profile; } } - return $profiles2; + return $profiles; } /** From 45b747f13bd276a2245f6481c5377c1138b2d96d Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 13:05:00 +0000 Subject: [PATCH 16/22] Remove duplicates in a better way --- src/Model/UserItem.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index ab81be5e4c..fb89a9bc50 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -154,25 +154,19 @@ class UserItem continue; } - // Add the profile if it wasn't already added - if (!in_array($profile, $profiles)) { - $profiles[] = $profile; - } + // Add the profile + $profiles[] = $profile; // Add the normalized form $profile = Strings::normaliseLink($profile); - if (!in_array($profile, $profiles)) { - $profiles[] = $profile; - } + $profiles[] = $profile; // Add the SSL form $profile = str_replace('http://', 'https://', $profile); - if (!in_array($profile, $profiles)) { - $profiles[] = $profile; - } + $profiles[] = $profile; } - return $profiles; + return array_unique($profiles); } /** From 4dec002dcb0b4abfd08f8761b37295e602243d96 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 13:13:36 +0000 Subject: [PATCH 17/22] Now using only a single array --- src/Model/UserItem.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index fb89a9bc50..03e7f51f64 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -125,38 +125,37 @@ class UserItem $notification_data = ['uid' => $uid, 'profiles' => []]; Hook::callAll('check_item_notification', $notification_data); - $raw_profiles = $notification_data['profiles']; + $profiles = $notification_data['profiles']; $user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]); if (!DBA::isResult($user)) { return []; } - $owner = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]); + $owner = DBA::selectFirst('contact', ['url', 'alias'], ['self' => true, 'uid' => $uid]); if (!DBA::isResult($owner)) { return []; } // This is our regular URL format - $raw_profiles[] = $owner['url']; + $profiles[] = $owner['url']; + + // Now the alias + $profiles[] = $owner['alias']; // Notifications from Diaspora are often with an URL in the Diaspora format - $raw_profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; - - $profiles = []; + $profiles[] = DI::baseUrl() . '/u/' . $user['nickname']; // Validate and add profile links - foreach ($raw_profiles AS $profile) { + foreach ($profiles AS $key => $profile) { // Check for invalid profile urls. 13 should be the shortest possible profile length: // http://a.bc/d // Additionally check for invalid urls that would return the normalised value "http:" if ((strlen($profile) < 13) || (Strings::normaliseLink($profile) == 'http:')) { + unset($profiles[$key]); continue; } - // Add the profile - $profiles[] = $profile; - // Add the normalized form $profile = Strings::normaliseLink($profile); $profiles[] = $profile; From 31b66804024f5b27e4dc2ce8d1c15ed0e9645a63 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 13:19:11 +0000 Subject: [PATCH 18/22] Improved check for invalid profiles --- src/Model/UserItem.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 03e7f51f64..15c7730054 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -148,10 +148,8 @@ class UserItem // Validate and add profile links foreach ($profiles AS $key => $profile) { - // Check for invalid profile urls. 13 should be the shortest possible profile length: - // http://a.bc/d - // Additionally check for invalid urls that would return the normalised value "http:" - if ((strlen($profile) < 13) || (Strings::normaliseLink($profile) == 'http:')) { + // Check for invalid profile urls (without scheme, host or path) and remove them + if (empty(parse_url($profile, PHP_URL_SCHEME)) || empty(parse_url($profile, PHP_URL_HOST)) || empty(parse_url($profile, PHP_URL_PATH))) { unset($profiles[$key]); continue; } From 4734242f632e197bba1802d44da8619a8ac6e85a Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 13:37:24 +0000 Subject: [PATCH 19/22] Added to-do --- src/Model/UserItem.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index 15c7730054..ae2663d68d 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -26,6 +26,8 @@ class UserItem /** * Checks an item for notifications and sets the "notification-type" field + * @ToDo: + * - Check for mentions in posts with "uid=0" where the user hadn't interacted before * * @param int $iid Item ID */ From f134903fcfd2687a51c9c57e6597dd8508e00a18 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 19:32:39 +0000 Subject: [PATCH 20/22] API: We now use the "notification-type" field for mentions --- include/api.php | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/include/api.php b/include/api.php index ac0ba26c12..a390c89c94 100644 --- a/include/api.php +++ b/include/api.php @@ -29,6 +29,7 @@ use Friendica\Model\Mail; use Friendica\Model\Photo; use Friendica\Model\Profile; use Friendica\Model\User; +use Friendica\Model\UserItem; use Friendica\Network\FKOAuth1; use Friendica\Network\HTTPException; use Friendica\Network\HTTPException\BadRequestException; @@ -1519,7 +1520,7 @@ function api_search($type) } elseif (!empty($_REQUEST['count'])) { $count = $_REQUEST['count']; } - + $since_id = $_REQUEST['since_id'] ?? 0; $max_id = $_REQUEST['max_id'] ?? 0; $page = $_REQUEST['page'] ?? 1; @@ -1555,7 +1556,7 @@ function api_search($type) $condition = [implode(' AND ', $preCondition)]; } else { - $condition = ["`id` > ? + $condition = ["`id` > ? " . ($exclude_replies ? " AND `id` = `parent` " : ' ') . " AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) AND `body` LIKE CONCAT('%',?,'%')", @@ -2158,17 +2159,34 @@ function api_statuses_mentions($type) $start = max(0, ($page - 1) * $count); - $condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `author-id` != ? AND `mention` - AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `thread`.`uid` = ? AND NOT `thread`.`ignored`)", - api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $user_info['pid'], api_user()]; + $query = "SELECT `item`.`id` FROM `user-item` + INNER JOIN `item` ON `item`.`id` = `user-item`.`iid` AND `item`.`gravity` IN (?, ?) + WHERE (`user-item`.`hidden` IS NULL OR NOT `user-item`.`hidden`) AND + `user-item`.`uid` = ? AND `user-item`.`notification-type` & ? != 0 + AND `user-item`.`iid` > ?"; + $condition = [GRAVITY_PARENT, GRAVITY_COMMENT, api_user(), + UserItem::NOTIF_EXPLICIT_TAGGED | UserItem::NOTIF_IMPLICIT_TAGGED | + UserItem::NOTIF_THREAD_COMMENT | UserItem::NOTIF_DIRECT_COMMENT, + $since_id]; if ($max_id > 0) { $condition[0] .= " AND `item`.`id` <= ?"; $condition[] = $max_id; } + $query .= " ORDER BY `user-item`.`iid` DESC LIMIT ?, ?"; + $condition[] = $start; + $condition[] = $count; + + $useritems = DBA::p($query, $condition); + $itemids = []; + while ($useritem = DBA::fetch($useritems)) { + $itemids[] = $useritem['id']; + } + DBA::close($useritems); + $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; - $statuses = Item::selectForUser(api_user(), [], $condition, $params); + $statuses = Item::selectForUser(api_user(), [], ['id' => $itemids], $params); $ret = api_format_items(Item::inArray($statuses), $user_info, false, $type); From 6670a2409f5fdc8c088ab97ee69e1af8a3bea111 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 19:48:01 +0000 Subject: [PATCH 21/22] Fix condition creation --- include/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/api.php b/include/api.php index a390c89c94..e19c895241 100644 --- a/include/api.php +++ b/include/api.php @@ -2170,7 +2170,7 @@ function api_statuses_mentions($type) $since_id]; if ($max_id > 0) { - $condition[0] .= " AND `item`.`id` <= ?"; + $query .= " AND `item`.`id` <= ?"; $condition[] = $max_id; } From 6d7608fbc7c1f4cfce1752cdd04c1220ba77912f Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 5 Jan 2020 20:42:32 +0000 Subject: [PATCH 22/22] We only search for items with the same uid --- src/Model/UserItem.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Model/UserItem.php b/src/Model/UserItem.php index ae2663d68d..18409ff7c3 100644 --- a/src/Model/UserItem.php +++ b/src/Model/UserItem.php @@ -95,7 +95,7 @@ class UserItem $notification_type = $notification_type | self::NOTIF_THREAD_COMMENT; } - if (self::checkDirectComment($item, $uid, $contacts)) { + if (self::checkDirectComment($item, $contacts)) { $notification_type = $notification_type | self::NOTIF_DIRECT_COMMENT; } @@ -250,13 +250,12 @@ class UserItem /** * Check for a direct comment to a post of the given user * @param array $item - * @param int $uid User ID * @param array $contacts Array of contact IDs * @return bool The item is a direct comment to a user comment */ - private static function checkDirectComment(array $item, int $uid, array $contacts) + private static function checkDirectComment(array $item, array $contacts) { - $condition = ['uri' => $item['thr-parent'], 'uid' => [0, $uid], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; + $condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT]; return Item::exists($condition); }