diff --git a/mod/item.php b/mod/item.php index f43e78e6ba..aa673969f6 100644 --- a/mod/item.php +++ b/mod/item.php @@ -374,6 +374,22 @@ function item_content(App $a) Contact\User::setBlocked($item['author-id'], DI::userSession()->getLocalUserId(), true); + if (DI::mode()->isAjax()) { + // ajax return: [, 0 (no perm) | ] + System::jsonExit([intval($args->get(2)), DI::userSession()->getLocalUserId()]); + } else { + item_redirect_after_action($item, $args->get(3)); + } + break; + + case 'ignore': + $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), ['guid', 'author-id', 'parent', 'gravity'], ['id' => $args->get(2)]); + if (empty($item['author-id'])) { + throw new HTTPException\NotFoundException('Item not found'); + } + + Contact\User::setIgnored($item['author-id'], DI::userSession()->getLocalUserId(), true); + if (DI::mode()->isAjax()) { // ajax return: [, 0 (no perm) | ] System::jsonExit([intval($args->get(2)), DI::userSession()->getLocalUserId()]); diff --git a/src/App/Page.php b/src/App/Page.php index 1e9d6892f2..f7adb18d63 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -245,6 +245,7 @@ class Page implements ArrayAccess '$generator' => 'Friendica' . ' ' . App::VERSION, '$delitem' => $l10n->t('Delete this item?'), '$blockAuthor' => $l10n->t('Block this author? They won\'t be able to follow you nor see your public posts, and you won\'t be able to see their posts and their notifications.'), + '$ignoreAuthor' => $l10n->t('Ignore this author? You won\'t be able to see their posts and their notifications.'), '$update_interval' => $interval, '$shortcut_icon' => $shortcut_icon, '$touch_icon' => $touch_icon, diff --git a/src/Model/Item.php b/src/Model/Item.php index fb638d6e93..0b8c03b8e9 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1333,6 +1333,19 @@ class Item $transmit = $notify || ($posted_item['visible'] && ($parent_origin || $posted_item['origin'])); if ($transmit) { + if ($posted_item['uid'] && Contact\User::isBlocked($posted_item['author-id'], $posted_item['uid'])) { + Logger::info('Message from blocked author will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'cid' => $posted_item['author-id']]); + $transmit = false; + } + if ($transmit && $posted_item['uid'] && Contact\User::isBlocked($posted_item['owner-id'], $posted_item['uid'])) { + Logger::info('Message from blocked owner will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'cid' => $posted_item['owner-id']]); + $transmit = false; + } + if ($transmit && !empty($posted_item['causer-id']) && $posted_item['uid'] && Contact\User::isBlocked($posted_item['causer-id'], $posted_item['uid'])) { + Logger::info('Message from blocked causer will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'cid' => $posted_item['causer-id']]); + $transmit = false; + } + // Don't relay participation messages if (($posted_item['verb'] == Activity::FOLLOW) && (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid)))) { @@ -3720,7 +3733,17 @@ class Item return false; } - if (!empty($item['causer-id']) && ($item['gravity'] === self::GRAVITY_PARENT) && Contact\User::isIgnored($item['causer-id'], $user_id)) { + if (!empty($item['author-id']) && Contact\User::isIgnored($item['author-id'], $user_id)) { + Logger::notice('Author is ignored by user', ['author-link' => $item['author-link'], 'uid' => $user_id, 'item-uri' => $item['uri']]); + return false; + } + + if (!empty($item['owner-id']) && Contact\User::isIgnored($item['owner-id'], $user_id)) { + Logger::notice('Owner is ignored by user', ['owner-link' => $item['owner-link'], 'uid' => $user_id, 'item-uri' => $item['uri']]); + return false; + } + + if (!empty($item['causer-id']) && Contact\User::isIgnored($item['causer-id'], $user_id)) { Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'] ?? $item['causer-id'], 'uid' => $user_id, 'item-uri' => $item['uri']]); return false; } diff --git a/src/Model/Post.php b/src/Model/Post.php index 7ac7a51d2d..062f22db9c 100644 --- a/src/Model/Post.php +++ b/src/Model/Post.php @@ -412,13 +412,13 @@ class Post AND NOT `author-blocked` AND NOT `owner-blocked` AND (NOT `causer-blocked` OR `causer-id` = ? OR `causer-id` IS NULL) AND NOT `contact-blocked` AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?))) - OR `self` OR `gravity` != ? OR `contact-uid` = ?) + OR `self` OR `contact-uid` = ?) AND NOT `" . $view . "`.`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ? AND `hidden`) AND NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `author-id`) AND NOT `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `owner-id`) - AND NOT (`gravity` = ? AND `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `author-id`)) - AND NOT (`gravity` = ? AND `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `owner-id`))", - 0, Contact::SHARING, Contact::FRIEND, Item::GRAVITY_PARENT, 0, $uid, $uid, $uid, Item::GRAVITY_PARENT, $uid, Item::GRAVITY_PARENT, $uid]); + AND NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `author-id`) + AND NOT `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `owner-id`)", + 0, Contact::SHARING, Contact::FRIEND, 0, $uid, $uid, $uid, $uid, $uid]); $select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected)); diff --git a/src/Model/Post/UserNotification.php b/src/Model/Post/UserNotification.php index a9affdd76e..2a42b36340 100644 --- a/src/Model/Post/UserNotification.php +++ b/src/Model/Post/UserNotification.php @@ -133,7 +133,8 @@ class UserNotification public static function setNotification(int $uri_id, int $uid) { $fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity', 'vid', 'gravity', - 'private', 'contact-id', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'author-id', 'verb']; + 'contact-id', 'author-id', 'owner-id', 'causer-id', + 'private', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'verb']; $item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]); if (!DBA::isResult($item)) { return; @@ -174,6 +175,18 @@ class UserNotification */ private static function setNotificationForUser(array $item, int $uid) { + if (Contact\User::isBlocked($item['author-id'], $uid) || Contact\User::isIgnored($item['author-id'], $uid) || Contact\User::isCollapsed($item['author-id'], $uid)) { + return; + } + + if (Contact\User::isBlocked($item['owner-id'], $uid) || Contact\User::isIgnored($item['owner-id'], $uid) || Contact\User::isCollapsed($item['owner-id'], $uid)) { + return; + } + + if (!empty($item['causer-id']) && (Contact\User::isBlocked($item['causer-id'], $uid) || Contact\User::isIgnored($item['causer-id'], $uid) || Contact\User::isCollapsed($item['causer-id'], $uid))) { + return; + } + if (Post\ThreadUser::getIgnored($item['parent-uri-id'], $uid)) { return; } diff --git a/src/Object/Post.php b/src/Object/Post.php index dfe5aba383..0a05429dfd 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -191,7 +191,7 @@ class Post $pinned = ''; $pin = false; $star = false; - $ignore = false; + $ignore_thread = false; $ispinned = 'unpinned'; $isstarred = 'unstarred'; $indent = ''; @@ -246,8 +246,9 @@ class Post // Showing the one or the other text, depending upon if we can only hide it or really delete it. $delete = $origin ? DI::l10n()->t('Delete globally') : DI::l10n()->t('Remove locally'); - $drop = false; - $block = false; + $drop = false; + $block = false; + $ignore = false; if (DI::userSession()->getLocalUserId()) { $drop = [ 'dropping' => $dropping, @@ -259,9 +260,14 @@ class Post if (!$item['self'] && DI::userSession()->getLocalUserId()) { $block = [ - 'blocking' => true, - 'block' => DI::l10n()->t('Block %s', $item['author-name']), - 'author_id' => $item['author-id'], + 'blocking' => true, + 'block' => DI::l10n()->t('Block %s', $item['author-name']), + 'author_id' => $item['author-id'], + ]; + $ignore = [ + 'ignoring' => true, + 'ignore' => DI::l10n()->t('Ignore %s', $item['author-name']), + 'author_id' => $item['author-id'], ]; } @@ -327,14 +333,14 @@ class Post if ($this->isToplevel()) { if (DI::userSession()->getLocalUserId()) { - $ignored = PostModel\ThreadUser::getIgnored($item['uri-id'], DI::userSession()->getLocalUserId()); - if ($item['mention'] || $ignored) { - $ignore = [ + $ignored_thread = PostModel\ThreadUser::getIgnored($item['uri-id'], DI::userSession()->getLocalUserId()); + if ($item['mention'] || $ignored_thread) { + $ignore_thread = [ 'do' => DI::l10n()->t('Ignore thread'), 'undo' => DI::l10n()->t('Unignore thread'), 'toggle' => DI::l10n()->t('Toggle ignore status'), - 'classdo' => $ignored ? 'hidden' : '', - 'classundo' => $ignored ? '' : 'hidden', + 'classdo' => $ignored_thread ? 'hidden' : '', + 'classundo' => $ignored_thread ? '' : 'hidden', 'ignored' => DI::l10n()->t('Ignored'), ]; } @@ -518,12 +524,13 @@ class Post 'pinned' => $pinned, 'isstarred' => $isstarred, 'star' => $star, - 'ignore' => $ignore, + 'ignore' => $ignore_thread, 'tagger' => $tagger, 'filer' => $filer, 'language' => $languages, 'drop' => $drop, 'block' => $block, + 'ignore_author' => $ignore, 'vote' => $buttons, 'like_html' => $responses['like']['output'], 'dislike_html' => $responses['dislike']['output'], diff --git a/view/theme/frio/js/textedit.js b/view/theme/frio/js/textedit.js index e8e4a6eb44..a6048686d2 100644 --- a/view/theme/frio/js/textedit.js +++ b/view/theme/frio/js/textedit.js @@ -202,6 +202,10 @@ function confirmBlock() { return confirm(aStr.blockAuthor); } +function confirmIgnore() { + return confirm(aStr.ignoreAuthor); +} + /** * Hide and removes an item element from the DOM after the deletion url is * successful, restore it else. @@ -258,4 +262,34 @@ function blockAuthor(url, elementId) { }); } } + +/** + * Ignored an author and hide and removes an item element from the DOM after the block is + * successful, restore it else. + * + * @param {string} url The item removal URL + * @param {string} elementId The DOM id of the item element + * @returns {undefined} + */ +function ignoreAuthor(url, elementId) { + if (confirmIgnore()) { + $("body").css("cursor", "wait"); + + var $el = $(document.getElementById(elementId)); + + $el.fadeTo("fast", 0.33, function () { + $.get(url) + .then(function () { + $el.remove(); + }) + .fail(function () { + // @todo Show related error message + $el.show(); + }) + .always(function () { + $("body").css("cursor", "auto"); + }); + }); + } +} // @license-end diff --git a/view/theme/frio/templates/js_strings.tpl b/view/theme/frio/templates/js_strings.tpl index 066e4bd585..c27921afdb 100644 --- a/view/theme/frio/templates/js_strings.tpl +++ b/view/theme/frio/templates/js_strings.tpl @@ -6,7 +6,8 @@ They are loaded into the html so that js functions can use them *}} var localUser = {{if $local_user}}{{$local_user}}{{else}}false{{/if}}; var aStr = { - 'delitem' : "{{$delitem|escape:'javascript' nofilter}}", - 'blockAuthor' : "{{$blockAuthor|escape:'javascript' nofilter}}", + 'delitem' : "{{$delitem|escape:'javascript' nofilter}}", + 'blockAuthor' : "{{$blockAuthor|escape:'javascript' nofilter}}", + 'ignoreAuthor' : "{{$ignoreAuthor|escape:'javascript' nofilter}}", }; diff --git a/view/theme/frio/templates/wall_thread.tpl b/view/theme/frio/templates/wall_thread.tpl index 05cbdfbb6c..943522a988 100644 --- a/view/theme/frio/templates/wall_thread.tpl +++ b/view/theme/frio/templates/wall_thread.tpl @@ -404,7 +404,12 @@ as the value of $top_child_total (this is done at the end of this file) {{$item.block.block}} {{/if}} - + {{if $item.ignore_author}} +
  • + {{$item.ignore_author.ignore}} +
  • + {{/if}} + {{else}}