Merge pull request #13288 from MrPetovan/task/user-server-block

Add Ignore Author Server button to post actions
This commit is contained in:
Michael Vogel 2023-08-20 20:41:17 +02:00 committed by GitHub
commit 1edb7b6464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1677 additions and 465 deletions

View File

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2023.09-dev (Giant Rhubarb) -- Friendica 2023.09-dev (Giant Rhubarb)
-- DB_UPDATE_VERSION 1525 -- DB_UPDATE_VERSION 1527
-- ------------------------------------------ -- ------------------------------------------
@ -101,6 +101,18 @@ CREATE TABLE IF NOT EXISTS `user` (
FOREIGN KEY (`parent-uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE FOREIGN KEY (`parent-uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='The local users'; ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='The local users';
--
-- TABLE user-gserver
--
CREATE TABLE IF NOT EXISTS `user-gserver` (
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner User id',
`gsid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Gserver id',
`ignored` boolean NOT NULL DEFAULT '0' COMMENT 'server accounts are ignored for the user',
PRIMARY KEY(`uid`,`gsid`),
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User settings about remote servers';
-- --
-- TABLE item-uri -- TABLE item-uri
-- --
@ -2044,6 +2056,7 @@ CREATE VIEW `post-user-view` AS SELECT
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`, `owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`, `owner`.`updated` AS `owner-updated`,
`owner`.`gsid` AS `owner-gsid`,
`owner`.`contact-type` AS `owner-contact-type`, `owner`.`contact-type` AS `owner-contact-type`,
`post-user`.`causer-id` AS `causer-id`, `post-user`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`, `causer`.`uri-id` AS `causer-uri-id`,
@ -2056,6 +2069,7 @@ CREATE VIEW `post-user-view` AS SELECT
`causer`.`network` AS `causer-network`, `causer`.`network` AS `causer-network`,
`causer`.`blocked` AS `causer-blocked`, `causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`, `causer`.`hidden` AS `causer-hidden`,
`causer`.`gsid` AS `causer-gsid`,
`causer`.`contact-type` AS `causer-contact-type`, `causer`.`contact-type` AS `causer-contact-type`,
`post-delivery-data`.`postopts` AS `postopts`, `post-delivery-data`.`postopts` AS `postopts`,
`post-delivery-data`.`inform` AS `inform`, `post-delivery-data`.`inform` AS `inform`,
@ -2189,6 +2203,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`contact`.`pending` AS `contact-pending`, `contact`.`pending` AS `contact-pending`,
`contact`.`rel` AS `contact-rel`, `contact`.`rel` AS `contact-rel`,
`contact`.`uid` AS `contact-uid`, `contact`.`uid` AS `contact-uid`,
`contact`.`gsid` AS `contact-gsid`,
`contact`.`contact-type` AS `contact-contact-type`, `contact`.`contact-type` AS `contact-contact-type`,
IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`, IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`,
`contact`.`self` AS `self`, `contact`.`self` AS `self`,
@ -2224,6 +2239,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`, `owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`, `owner`.`updated` AS `owner-updated`,
`owner`.`gsid` AS `owner-gsid`,
`owner`.`contact-type` AS `owner-contact-type`, `owner`.`contact-type` AS `owner-contact-type`,
`post-thread-user`.`causer-id` AS `causer-id`, `post-thread-user`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`, `causer`.`uri-id` AS `causer-uri-id`,
@ -2236,6 +2252,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`causer`.`network` AS `causer-network`, `causer`.`network` AS `causer-network`,
`causer`.`blocked` AS `causer-blocked`, `causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`, `causer`.`hidden` AS `causer-hidden`,
`causer`.`gsid` AS `causer-gsid`,
`causer`.`contact-type` AS `causer-contact-type`, `causer`.`contact-type` AS `causer-contact-type`,
`post-delivery-data`.`postopts` AS `postopts`, `post-delivery-data`.`postopts` AS `postopts`,
`post-delivery-data`.`inform` AS `inform`, `post-delivery-data`.`inform` AS `inform`,
@ -2391,6 +2408,7 @@ CREATE VIEW `post-view` AS SELECT
`owner`.`hidden` AS `owner-hidden`, `owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`, `owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`, `owner`.`contact-type` AS `owner-contact-type`,
`owner`.`gsid` AS `owner-gsid`,
`post`.`causer-id` AS `causer-id`, `post`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`, `causer`.`uri-id` AS `causer-uri-id`,
`causer`.`url` AS `causer-link`, `causer`.`url` AS `causer-link`,
@ -2403,6 +2421,7 @@ CREATE VIEW `post-view` AS SELECT
`causer`.`blocked` AS `causer-blocked`, `causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`, `causer`.`hidden` AS `causer-hidden`,
`causer`.`contact-type` AS `causer-contact-type`, `causer`.`contact-type` AS `causer-contact-type`,
`causer`.`gsid` AS `causer-gsid`,
`post-question`.`id` AS `question-id`, `post-question`.`id` AS `question-id`,
`post-question`.`multiple` AS `question-multiple`, `post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`, `post-question`.`voters` AS `question-voters`,
@ -2533,6 +2552,7 @@ CREATE VIEW `post-thread-view` AS SELECT
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`, `owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`, `owner`.`updated` AS `owner-updated`,
`owner`.`gsid` AS `owner-gsid`,
`owner`.`contact-type` AS `owner-contact-type`, `owner`.`contact-type` AS `owner-contact-type`,
`post-thread`.`causer-id` AS `causer-id`, `post-thread`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`, `causer`.`uri-id` AS `causer-uri-id`,
@ -2545,6 +2565,7 @@ CREATE VIEW `post-thread-view` AS SELECT
`causer`.`network` AS `causer-network`, `causer`.`network` AS `causer-network`,
`causer`.`blocked` AS `causer-blocked`, `causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`, `causer`.`hidden` AS `causer-hidden`,
`causer`.`gsid` AS `causer-gsid`,
`causer`.`contact-type` AS `causer-contact-type`, `causer`.`contact-type` AS `causer-contact-type`,
`post-question`.`id` AS `question-id`, `post-question`.`id` AS `question-id`,
`post-question`.`multiple` AS `question-multiple`, `post-question`.`multiple` AS `question-multiple`,
@ -2666,7 +2687,7 @@ CREATE VIEW `network-item-view` AS SELECT
`post-user`.`contact-id` AS `contact-id`, `post-user`.`contact-id` AS `contact-id`,
`ownercontact`.`contact-type` AS `contact-type` `ownercontact`.`contact-type` AS `contact-type`
FROM `post-user` FROM `post-user`
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid` INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id` STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id` STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id` STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
@ -2707,7 +2728,8 @@ CREATE VIEW `network-thread-view` AS SELECT
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`) AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked` AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`) AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`); AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`)
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
-- --
-- VIEW owner-view -- VIEW owner-view

View File

@ -86,6 +86,7 @@ Database Tables
| [tag](help/database/db_tag) | tags and mentions | | [tag](help/database/db_tag) | tags and mentions |
| [user](help/database/db_user) | The local users | | [user](help/database/db_user) | The local users |
| [user-contact](help/database/db_user-contact) | User specific public contact data | | [user-contact](help/database/db_user-contact) | User specific public contact data |
| [user-gserver](help/database/db_user-gserver) | User settings about remote servers |
| [userd](help/database/db_userd) | Deleted usernames | | [userd](help/database/db_userd) | Deleted usernames |
| [verb](help/database/db_verb) | Activity Verbs | | [verb](help/database/db_verb) | Activity Verbs |
| [worker-ipc](help/database/db_worker-ipc) | Inter process communication between the frontend and the worker | | [worker-ipc](help/database/db_worker-ipc) | Inter process communication between the frontend and the worker |

View File

@ -0,0 +1,30 @@
Table user-gserver
===========
User settings about remote servers
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------- | ---------------------------------------- | ------------------ | ---- | --- | ------- | ----- |
| uid | Owner User id | mediumint unsigned | NO | | 0 | |
| gsid | Gserver id | int unsigned | NO | | 0 | |
| ignored | server accounts are ignored for the user | boolean | NO | | 0 | |
Indexes
------------
| Name | Fields |
| ------- | --------- |
| PRIMARY | uid, gsid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uid | [user](help/database/db_user) | uid |
| gsid | [gserver](help/database/db_gserver) | id |
Return to [database documentation](help/database)

View File

@ -245,10 +245,12 @@ class Page implements ArrayAccess
*/ */
$this->page['htmlhead'] = Renderer::replaceMacros($tpl, [ $this->page['htmlhead'] = Renderer::replaceMacros($tpl, [
'$l10n' => [ '$l10n' => [
'delitem' => $l10n->t('Delete this item?'), '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.'), '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.'), 'ignoreAuthor' => $l10n->t("Ignore this author? You won't be able to see their posts and their notifications."),
'collapseAuthor' => $l10n->t('Collapse this author\'s posts?'), 'collapseAuthor' => $l10n->t("Collapse this author's posts?"),
'ignoreServer' => $l10n->t("Ignore this author's server?"),
'ignoreServerDesc' => $l10n->t("You won't see any content from this server including reshares in your Network page, the community pages and individual conversations."),
'likeError' => $l10n->t('Like not successful'), 'likeError' => $l10n->t('Like not successful'),
'dislikeError' => $l10n->t('Dislike not successful'), 'dislikeError' => $l10n->t('Dislike not successful'),

View File

@ -45,6 +45,8 @@ use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Object\Post as PostObject; use Friendica\Object\Post as PostObject;
use Friendica\Object\Thread; use Friendica\Object\Thread;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\User\Settings\Entity\UserGServer;
use Friendica\User\Settings\Repository;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
@ -90,22 +92,25 @@ class Conversation
private $mode; private $mode;
/** @var IHandleUserSessions */ /** @var IHandleUserSessions */
private $session; private $session;
/** @var Repository\UserGServer */
private $userGServer;
public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session) public function __construct(Repository\UserGServer $userGServer, LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
{ {
$this->activity = $activity; $this->activity = $activity;
$this->item = $item; $this->item = $item;
$this->config = $config; $this->config = $config;
$this->mode = $mode; $this->mode = $mode;
$this->baseURL = $baseURL; $this->baseURL = $baseURL;
$this->profiler = $profiler; $this->profiler = $profiler;
$this->logger = $logger; $this->logger = $logger;
$this->l10n = $l10n; $this->l10n = $l10n;
$this->args = $args; $this->args = $args;
$this->pConfig = $pConfig; $this->pConfig = $pConfig;
$this->page = $page; $this->page = $page;
$this->app = $app; $this->app = $app;
$this->session = $session; $this->session = $session;
$this->userGServer = $userGServer;
} }
/** /**
@ -459,8 +464,14 @@ class Conversation
$live_update_div = ''; $live_update_div = '';
$userGservers = $this->userGServer->listIgnoredByUser($this->session->getLocalUserId());
$ignoredGsids = array_map(function (UserGServer $userGServer) {
return $userGServer->gsid;
}, $userGservers->getArrayCopy());
if ($mode === self::MODE_NETWORK) { if ($mode === self::MODE_NETWORK) {
$items = $this->addChildren($items, false, $order, $uid, $mode); $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) { if (!$update) {
/* /*
* The special div is needed for liveUpdate to kick in for this page. * The special div is needed for liveUpdate to kick in for this page.
@ -486,7 +497,7 @@ class Conversation
. "'; </script>\r\n"; . "'; </script>\r\n";
} }
} elseif ($mode === self::MODE_PROFILE) { } elseif ($mode === self::MODE_PROFILE) {
$items = $this->addChildren($items, false, $order, $uid, $mode); $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) { if (!$update) {
$tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts'; $tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts';
@ -511,7 +522,7 @@ class Conversation
. "; var netargs = '?f='; </script>\r\n"; . "; var netargs = '?f='; </script>\r\n";
} }
} elseif ($mode === self::MODE_DISPLAY) { } elseif ($mode === self::MODE_DISPLAY) {
$items = $this->addChildren($items, false, $order, $uid, $mode); $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) { if (!$update) {
$live_update_div = '<div id="live-display"></div>' . "\r\n" $live_update_div = '<div id="live-display"></div>' . "\r\n"
@ -519,7 +530,7 @@ class Conversation
. "</script>"; . "</script>";
} }
} elseif ($mode === self::MODE_COMMUNITY) { } elseif ($mode === self::MODE_COMMUNITY) {
$items = $this->addChildren($items, true, $order, $uid, $mode); $items = $this->addChildren($items, true, $order, $uid, $mode, $ignoredGsids);
if (!$update) { if (!$update) {
$live_update_div = '<div id="live-community"></div>' . "\r\n" $live_update_div = '<div id="live-community"></div>' . "\r\n"
@ -530,7 +541,7 @@ class Conversation
. "'; </script>\r\n"; . "'; </script>\r\n";
} }
} elseif ($mode === self::MODE_CONTACTS) { } elseif ($mode === self::MODE_CONTACTS) {
$items = $this->addChildren($items, false, $order, $uid, $mode); $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) { if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n" $live_update_div = '<div id="live-contact"></div>' . "\r\n"
@ -812,13 +823,14 @@ class Conversation
* *
* @param array $parents Parent items * @param array $parents Parent items
* @param bool $block_authors * @param bool $block_authors
* @param bool $order * @param string $order Either "received" or "commented"
* @param int $uid * @param int $uid
* @param string $mode * @param string $mode One of self::MODE_*
* @param array $ignoredGsids List of ids of servers ignored by the user
* @return array items with parents and comments * @return array items with parents and comments
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
*/ */
private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode): array private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode, array $ignoredGsids = []): array
{ {
$this->profiler->startRecording('rendering'); $this->profiler->startRecording('rendering');
if (count($parents) > 1) { if (count($parents) > 1) {
@ -900,6 +912,13 @@ class Conversation
continue; continue;
} }
if (in_array($row['author-gsid'], $ignoredGsids)
|| in_array($row['owner-gsid'], $ignoredGsids)
|| in_array($row['causer-gsid'], $ignoredGsids)
) {
continue;
}
if (($mode != self::MODE_CONTACTS) && !$row['origin']) { if (($mode != self::MODE_CONTACTS) && !$row['origin']) {
$row['featured'] = false; $row['featured'] = false;
} }
@ -1462,6 +1481,7 @@ class Conversation
'received' => $item['received'], 'received' => $item['received'],
'created_date' => $item['created'], 'created_date' => $item['created'],
'uriid' => $item['uri-id'], 'uriid' => $item['uri-id'],
'author_gsid' => $item['author-gsid'],
'network' => $item['network'], 'network' => $item['network'],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']), 'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']), 'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),

View File

@ -50,6 +50,7 @@ use Friendica\Protocol\Activity;
use Friendica\Util\ACLFormatter; use Friendica\Util\ACLFormatter;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Emailer; use Friendica\Util\Emailer;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl; use Friendica\Util\ParseUrl;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Friendica\Util\Proxy; use Friendica\Util\Proxy;
@ -367,7 +368,7 @@ class Item
{ {
$this->profiler->startRecording('rendering'); $this->profiler->startRecording('rendering');
$sub_link = $contact_url = $pm_url = $status_link = ''; $sub_link = $contact_url = $pm_url = $status_link = '';
$photos_link = $posts_link = $block_link = $ignore_link = ''; $photos_link = $posts_link = $block_link = $ignore_link = $collapse_link = $ignoreserver_link = '';
if ($this->userSession->getLocalUserId() && $this->userSession->getLocalUserId() == $item['uid'] && $item['gravity'] == ItemModel::GRAVITY_PARENT && !$item['self'] && !$item['mention']) { if ($this->userSession->getLocalUserId() && $this->userSession->getLocalUserId() == $item['uid'] && $item['gravity'] == ItemModel::GRAVITY_PARENT && !$item['self'] && !$item['mention']) {
$sub_link = 'javascript:doFollowThread(' . $item['id'] . '); return false;'; $sub_link = 'javascript:doFollowThread(' . $item['id'] . '); return false;';
@ -407,6 +408,10 @@ class Item
$collapse_link = $item['self'] ? '' : $contact_url . '/collapse?t=' . $formSecurityToken; $collapse_link = $item['self'] ? '' : $contact_url . '/collapse?t=' . $formSecurityToken;
} }
if (!empty($item['author-gsid'])) {
$ignoreserver_link = Network::isLocalLink($contact_url) ? '' : 'settings/server/' . $item['author-gsid'] . '/ignore';
}
if ($cid && !$item['self']) { if ($cid && !$item['self']) {
$contact_url = 'contact/' . $cid; $contact_url = 'contact/' . $cid;
$posts_link = $contact_url . '/posts'; $posts_link = $contact_url . '/posts';
@ -427,7 +432,8 @@ class Item
$this->l10n->t('Send PM') => $pm_url, $this->l10n->t('Send PM') => $pm_url,
$this->l10n->t('Block') => $block_link, $this->l10n->t('Block') => $block_link,
$this->l10n->t('Ignore') => $ignore_link, $this->l10n->t('Ignore') => $ignore_link,
$this->l10n->t('Collapse') => $collapse_link $this->l10n->t('Collapse') => $collapse_link,
$this->l10n->t("Ignore %s's server", $item['author-name']) => $ignoreserver_link,
]; ];
if (!empty($item['language'])) { if (!empty($item['language'])) {

View File

@ -671,6 +671,15 @@ abstract class DI
return self::$dice->create(Security\Authentication::class); return self::$dice->create(Security\Authentication::class);
} }
//
// "User" namespace instances
//
public static function userGServer(): User\Settings\Repository\UserGServer
{
return self::$dice->create(User\Settings\Repository\UserGServer::class);
}
// //
// "Util" namespace instances // "Util" namespace instances
// //

View File

@ -0,0 +1,151 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Federation\Entity;
use DateTimeImmutable;
use Psr\Http\Message\UriInterface;
/**
* @property-read int $id
* @property-read string $url
* @property-read string $nurl
* @property-read string $version
* @property-read string $siteName
* @property-read string $info
* @property-read int $registerPolicy
* @property-read int $registeredUsers
* @property-read string $poco
* @property-read string $noscrape
* @property-read string $network
* @property-read string $platform
* @property-read int $relaySubscribe
* @property-read string $relayScope
* @property-read DateTimeImmutable $created
* @property-read ?DateTimeImmutable $lastPocoQuery
* @property-read ?DateTimeImmutable $lastContact
* @property-read ?DateTimeImmutable $lastFailure
* @property-read int $directoryType
* @property-read int $detectionMethod
* @property-read bool $failed
* @property-read DateTimeImmutable $nextContact
* @property-read int $protocol
* @property-read int $activeWeekUsers
* @property-read int $activeMonthUsers
* @property-read int $activeHalfyearUsers
* @property-read int $localPosts
* @property-read int $localComments
* @property-read bool $blocked
*/
class GServer extends \Friendica\BaseEntity
{
/** @var ?int */
protected $id;
/** @var UriInterface */
protected $url;
/** @var UriInterface */
protected $nurl;
/** @var string */
protected $version;
/** @var string */
protected $siteName;
/** @var string */
protected $info;
/** @var int One of Module\Register::* constant values */
protected $registerPolicy;
/** @var int */
protected $registeredUsers;
/** @var ?UriInterface */
protected $poco;
/** @var ?UriInterface */
protected $noscrape;
/** @var string One of the Protocol::* constant values */
protected $network;
/** @var string */
protected $platform;
/** @var bool */
protected $relaySubscribe;
/** @var string */
protected $relayScope;
/** @var DateTimeImmutable */
protected $created;
/** @var DateTimeImmutable */
protected $lastPocoQuery;
/** @var DateTimeImmutable */
protected $lastContact;
/** @var DateTimeImmutable */
protected $lastFailure;
/** @var int One of Model\Gserver::DT_* constant values */
protected $directoryType;
/** @var ?int One of Model\Gserver::DETECT_* constant values */
protected $detectionMethod;
/** @var bool */
protected $failed;
/** @var DateTimeImmutable */
protected $nextContact;
/** @var ?int One of Model\Post\DeliveryData::* constant values */
protected $protocol;
/** @var ?int */
protected $activeWeekUsers;
/** @var ?int */
protected $activeMonthUsers;
/** @var ?int */
protected $activeHalfyearUsers;
/** @var ?int */
protected $localPosts;
/** @var ?int */
protected $localComments;
/** @var bool */
protected $blocked;
public function __construct(UriInterface $url, UriInterface $nurl, string $version, string $siteName, string $info, int $registerPolicy, int $registeredUsers, ?UriInterface $poco, ?UriInterface $noscrape, string $network, string $platform, bool $relaySubscribe, string $relayScope, DateTimeImmutable $created, ?DateTimeImmutable $lastPocoQuery, ?DateTimeImmutable $lastContact, ?DateTimeImmutable $lastFailure, int $directoryType, ?int $detectionMethod, bool $failed, ?DateTimeImmutable $nextContact, ?int $protocol, ?int $activeWeekUsers, ?int $activeMonthUsers, ?int $activeHalfyearUsers, ?int $localPosts, ?int $localComments, bool $blocked, ?int $id = null)
{
$this->url = $url;
$this->nurl = $nurl;
$this->version = $version;
$this->siteName = $siteName;
$this->info = $info;
$this->registerPolicy = $registerPolicy;
$this->registeredUsers = $registeredUsers;
$this->poco = $poco;
$this->noscrape = $noscrape;
$this->network = $network;
$this->platform = $platform;
$this->relaySubscribe = $relaySubscribe;
$this->relayScope = $relayScope;
$this->created = $created;
$this->lastPocoQuery = $lastPocoQuery;
$this->lastContact = $lastContact;
$this->lastFailure = $lastFailure;
$this->directoryType = $directoryType;
$this->detectionMethod = $detectionMethod;
$this->failed = $failed;
$this->nextContact = $nextContact;
$this->protocol = $protocol;
$this->activeWeekUsers = $activeWeekUsers;
$this->activeMonthUsers = $activeMonthUsers;
$this->activeHalfyearUsers = $activeHalfyearUsers;
$this->localPosts = $localPosts;
$this->localComments = $localComments;
$this->blocked = $blocked;
$this->id = $id;
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Federation\Factory;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Database\DBA;
use Friendica\Federation\Entity;
use GuzzleHttp\Psr7\Uri;
class GServer extends \Friendica\BaseFactory implements ICanCreateFromTableRow
{
/**
* @inheritDoc
*/
public function createFromTableRow(array $row): Entity\GServer
{
return new Entity\GServer(
new Uri($row['url']),
new Uri($row['nurl']),
$row['version'],
$row['site_name'],
$row['info'] ?? '',
$row['register_policy'],
$row['registered-users'],
$row['poco'] ? new Uri($row['poco']) : null,
$row['noscrape'] ? new Uri($row['noscrape']) : null,
$row['network'],
$row['platform'],
$row['relay-subscribe'],
$row['relay-scope'],
new \DateTimeImmutable($row['created']),
$row['last_poco_query'] !== DBA::NULL_DATETIME ? new \DateTimeImmutable($row['last_poco_query']) : null,
$row['last_contact'] !== DBA::NULL_DATETIME ? new \DateTimeImmutable($row['last_contact']) : null,
$row['last_failure'] !== DBA::NULL_DATETIME ? new \DateTimeImmutable($row['last_failure']) : null,
$row['directory-type'],
$row['detection-method'],
$row['failed'],
$row['next_contact'] !== DBA::NULL_DATETIME ? new \DateTimeImmutable($row['next_contact']) : null,
$row['protocol'],
$row['active-week-users'],
$row['active-month-users'],
$row['active-halfyear-users'],
$row['local-posts'],
$row['local-comments'],
$row['blocked'],
$row['id'],
);
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Federation\Repository;
use Friendica\Database\Database;
use Friendica\Federation\Factory;
use Friendica\Federation\Entity;
use Psr\Log\LoggerInterface;
class GServer extends \Friendica\BaseRepository
{
protected static $table_name = 'gserver';
public function __construct(Database $database, LoggerInterface $logger, Factory\GServer $factory)
{
parent::__construct($database, $logger, $factory);
}
/**
* @param int $gsid
* @return Entity\GServer
* @throws \Friendica\Network\HTTPException\NotFoundException
*/
public function selectOneById(int $gsid): Entity\GServer
{
return $this->_selectOne(['id' => $gsid]);
}
}

View File

@ -96,8 +96,8 @@ class Item
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object', 'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
'quote-uri', 'quote-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global', 'quote-uri', 'quote-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
'author-id', 'author-link', 'author-alias', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id', 'author-id', 'author-link', 'author-alias', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id',
'owner-id', 'owner-link', 'owner-alias', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'owner-id', 'owner-link', 'owner-alias', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'owner-gsid',
'causer-id', 'causer-link', 'causer-alias', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'causer-id', 'causer-link', 'causer-alias', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'causer-gsid',
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar', 'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar',
'writable', 'self', 'cid', 'alias', 'writable', 'self', 'cid', 'alias',
'event-created', 'event-edited', 'event-start', 'event-finish', 'event-created', 'event-edited', 'event-start', 'event-finish',

View File

@ -453,12 +453,10 @@ class Post
AND (NOT `causer-blocked` OR `causer-id` = ? OR `causer-id` IS NULL) AND NOT `contact-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 (?, ?))) AND ((NOT `contact-readonly` AND NOT `contact-pending` AND (`contact-rel` IN (?, ?)))
OR `self` 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 EXISTS(SELECT `uri-id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = " . DBA::quoteIdentifier($view) . ".`uri-id` AND `hidden`)
AND NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `author-id`) AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` IN (`author-id`, `owner-id`) AND (`blocked` OR `ignored`))
AND NOT `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `owner-id`) AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = ? AND `gsid` IN (`author-gsid`, `owner-gsid`, `causer-gsid`) AND `ignored`)",
AND NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `author-id`) 0, Contact::SHARING, Contact::FRIEND, 0, $uid, $uid, $uid]);
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)); $select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));

View File

@ -133,14 +133,14 @@ class UserNotification
public static function setNotification(int $uri_id, int $uid) public static function setNotification(int $uri_id, int $uid)
{ {
$fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity', 'vid', 'gravity', $fields = ['id', 'uri-id', 'parent-uri-id', 'uid', 'body', 'parent', 'gravity', 'vid', 'gravity',
'contact-id', 'author-id', 'owner-id', 'causer-id', 'contact-id', 'author-id', 'author-gsid', 'owner-id', 'owner-gsid', 'causer-id', 'causer-gsid',
'private', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'verb']; 'private', 'thr-parent', 'thr-parent-id', 'parent-uri-id', 'parent-uri', 'verb'];
$item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]); $item = Post::selectFirst($fields, ['uri-id' => $uri_id, 'uid' => $uid, 'origin' => false]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
return; return;
} }
$parent = Post::selectFirstPost(['author-id', 'owner-id', 'causer-id'], ['uri-id' => $item['parent-uri-id']]); $parent = Post::selectFirstPost(['author-id', 'author-gsid', 'owner-id', 'owner-gsid', 'causer-id', 'causer-gsid',], ['uri-id' => $item['parent-uri-id']]);
if (!DBA::isResult($parent)) { if (!DBA::isResult($parent)) {
return; return;
} }
@ -195,6 +195,13 @@ class UserNotification
} }
} }
foreach (array_unique([$parent['author-gsid'], $parent['owner-gsid'], $parent['causer-gsid'], $item['author-gsid'], $item['owner-gsid'], $item['causer-gsid']]) as $gsid) {
if ($gsid && DI::userGServer()->isIgnoredByUser($uid, $gsid)) {
Logger::debug('Server is ignored by user', ['uid' => $uid, 'gsid' => $gsid, 'uri-id' => $item['uri-id']]);
return;
}
}
$user = User::getById($uid, ['account-type', 'account_removed', 'account_expired']); $user = User::getById($uid, ['account-type', 'account_removed', 'account_expired']);
if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) { if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) {
return; return;

View File

@ -151,6 +151,13 @@ class BaseSettings extends BaseModule
'accesskey' => 'b', 'accesskey' => 'b',
]; ];
$tabs[] = [
'label' => $this->t('Remote servers'),
'url' => 'settings/server',
'selected' => static::class == Settings\Server\Index::class ? 'active' : '',
'accesskey' => 's',
];
$tabs[] = [ $tabs[] = [
'label' => $this->t('Export personal data'), 'label' => $this->t('Export personal data'),
'url' => 'settings/userexport', 'url' => 'settings/userexport',

View File

@ -23,8 +23,7 @@ namespace Friendica\Module\Contact;
use Friendica\App; use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Contact\LocalRelationship\Entity; use Friendica\Contact\LocalRelationship;
use Friendica\Contact\LocalRelationship\Repository;
use Friendica\Content\ContactSelector; use Friendica\Content\ContactSelector;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
@ -34,13 +33,16 @@ use Friendica\Core\Hook;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Circle; use Friendica\Model\Circle;
use Friendica\Model\Contact;
use Friendica\Module; use Friendica\Module;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\User\Settings;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -50,31 +52,37 @@ use Psr\Log\LoggerInterface;
*/ */
class Profile extends BaseModule class Profile extends BaseModule
{ {
/** /** @var LocalRelationship\Repository\LocalRelationship */
* @var Repository\LocalRelationship
*/
private $localRelationship; private $localRelationship;
/** /** @var App\Page */
* @var App\Page
*/
private $page; private $page;
/** /** @var IManageConfigValues */
* @var IManageConfigValues
*/
private $config; private $config;
/** @var IHandleUserSessions */
private $session;
/** @var SystemMessages */
private $systemMessages;
/** @var Database */
private $db;
/** @var Settings\Repository\UserGServer */
private $userGServer;
public function __construct(L10n $l10n, Repository\LocalRelationship $localRelationship, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, App\Page $page, IManageConfigValues $config, array $server, array $parameters = []) public function __construct(Settings\Repository\UserGServer $userGServer, Database $db, SystemMessages $systemMessages, IHandleUserSessions $session, L10n $l10n, LocalRelationship\Repository\LocalRelationship $localRelationship, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, App\Page $page, IManageConfigValues $config, array $server, array $parameters = [])
{ {
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->localRelationship = $localRelationship; $this->localRelationship = $localRelationship;
$this->page = $page; $this->page = $page;
$this->config = $config; $this->config = $config;
$this->session = $session;
$this->systemMessages = $systemMessages;
$this->db = $db;
$this->userGServer = $userGServer;
} }
protected function post(array $request = []) protected function post(array $request = [])
{ {
if (!DI::userSession()->getLocalUserId()) { if (!$this->session->getLocalUserId()) {
return; return;
} }
@ -82,8 +90,8 @@ class Profile extends BaseModule
// Backward compatibility: The update still needs a user-specific contact ID // Backward compatibility: The update still needs a user-specific contact ID
// Change to user-contact table check by version 2022.03 // Change to user-contact table check by version 2022.03
$cdata = Contact::getPublicAndUserContactID($contact_id, DI::userSession()->getLocalUserId()); $cdata = Contact::getPublicAndUserContactID($contact_id, $this->session->getLocalUserId());
if (empty($cdata['user']) || !DBA::exists('contact', ['id' => $cdata['user'], 'deleted' => false])) { if (empty($cdata['user']) || !$this->db->exists('contact', ['id' => $cdata['user'], 'deleted' => false])) {
return; return;
} }
@ -124,35 +132,35 @@ class Profile extends BaseModule
$fields['info'] = $_POST['info']; $fields['info'] = $_POST['info'];
} }
if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => DI::userSession()->getLocalUserId()])) { if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) {
DI::sysmsg()->addNotice($this->t('Failed to update contact record.')); $this->systemMessages->addNotice($this->t('Failed to update contact record.'));
} }
} }
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
if (!DI::userSession()->getLocalUserId()) { if (!$this->session->getLocalUserId()) {
return Module\Security\Login::form($_SERVER['REQUEST_URI']); return Module\Security\Login::form($_SERVER['REQUEST_URI']);
} }
// Backward compatibility: Ensure to use the public contact when the user contact is provided // Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03 // Remove by version 2022.03
$data = Contact::getPublicAndUserContactID(intval($this->parameters['id']), DI::userSession()->getLocalUserId()); $data = Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->session->getLocalUserId());
if (empty($data)) { if (empty($data)) {
throw new HTTPException\NotFoundException($this->t('Contact not found.')); throw new HTTPException\NotFoundException($this->t('Contact not found.'));
} }
$contact = Contact::getById($data['public']); $contact = Contact::getById($data['public']);
if (!DBA::isResult($contact)) { if (!$this->db->isResult($contact)) {
throw new HTTPException\NotFoundException($this->t('Contact not found.')); throw new HTTPException\NotFoundException($this->t('Contact not found.'));
} }
// Don't display contacts that are about to be deleted // Don't display contacts that are about to be deleted
if (DBA::isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) { if ($this->db->isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) {
throw new HTTPException\NotFoundException($this->t('Contact not found.')); throw new HTTPException\NotFoundException($this->t('Contact not found.'));
} }
$localRelationship = $this->localRelationship->getForUserContact(DI::userSession()->getLocalUserId(), $contact['id']); $localRelationship = $this->localRelationship->getForUserContact($this->session->getLocalUserId(), $contact['id']);
if ($localRelationship->rel === Contact::SELF) { if ($localRelationship->rel === Contact::SELF) {
$this->baseUrl->redirect('profile/' . $contact['nick'] . '/profile'); $this->baseUrl->redirect('profile/' . $contact['nick'] . '/profile');
@ -167,55 +175,55 @@ class Profile extends BaseModule
} }
if ($cmd === 'updateprofile') { if ($cmd === 'updateprofile') {
self::updateContactFromProbe($contact['id']); $this->updateContactFromProbe($contact['id']);
} }
if ($cmd === 'block') { if ($cmd === 'block') {
if ($localRelationship->blocked) { if ($localRelationship->blocked) {
// @TODO Backward compatibility, replace with $localRelationship->unblock() // @TODO Backward compatibility, replace with $localRelationship->unblock()
Contact\User::setBlocked($contact['id'], DI::userSession()->getLocalUserId(), false); Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), false);
$message = $this->t('Contact has been unblocked'); $message = $this->t('Contact has been unblocked');
} else { } else {
// @TODO Backward compatibility, replace with $localRelationship->block() // @TODO Backward compatibility, replace with $localRelationship->block()
Contact\User::setBlocked($contact['id'], DI::userSession()->getLocalUserId(), true); Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), true);
$message = $this->t('Contact has been blocked'); $message = $this->t('Contact has been blocked');
} }
// @TODO: add $this->localRelationship->save($localRelationship); // @TODO: add $this->localRelationship->save($localRelationship);
DI::sysmsg()->addInfo($message); $this->systemMessages->addInfo($message);
} }
if ($cmd === 'ignore') { if ($cmd === 'ignore') {
if ($localRelationship->ignored) { if ($localRelationship->ignored) {
// @TODO Backward compatibility, replace with $localRelationship->unblock() // @TODO Backward compatibility, replace with $localRelationship->unblock()
Contact\User::setIgnored($contact['id'], DI::userSession()->getLocalUserId(), false); Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), false);
$message = $this->t('Contact has been unignored'); $message = $this->t('Contact has been unignored');
} else { } else {
// @TODO Backward compatibility, replace with $localRelationship->block() // @TODO Backward compatibility, replace with $localRelationship->block()
Contact\User::setIgnored($contact['id'], DI::userSession()->getLocalUserId(), true); Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), true);
$message = $this->t('Contact has been ignored'); $message = $this->t('Contact has been ignored');
} }
// @TODO: add $this->localRelationship->save($localRelationship); // @TODO: add $this->localRelationship->save($localRelationship);
DI::sysmsg()->addInfo($message); $this->systemMessages->addInfo($message);
} }
if ($cmd === 'collapse') { if ($cmd === 'collapse') {
if ($localRelationship->collapsed) { if ($localRelationship->collapsed) {
// @TODO Backward compatibility, replace with $localRelationship->unblock() // @TODO Backward compatibility, replace with $localRelationship->unblock()
Contact\User::setCollapsed($contact['id'], DI::userSession()->getLocalUserId(), false); Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), false);
$message = $this->t('Contact has been uncollapsed'); $message = $this->t('Contact has been uncollapsed');
} else { } else {
// @TODO Backward compatibility, replace with $localRelationship->block() // @TODO Backward compatibility, replace with $localRelationship->block()
Contact\User::setCollapsed($contact['id'], DI::userSession()->getLocalUserId(), true); Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), true);
$message = $this->t('Contact has been collapsed'); $message = $this->t('Contact has been collapsed');
} }
// @TODO: add $this->localRelationship->save($localRelationship); // @TODO: add $this->localRelationship->save($localRelationship);
DI::sysmsg()->addInfo($message); $this->systemMessages->addInfo($message);
} }
$this->baseUrl->redirect('contact/' . $contact['id']); $this->baseUrl->redirect('contact/' . $contact['id']);
@ -259,6 +267,11 @@ class Profile extends BaseModule
$insecure = $this->t('Private communications are not available for this contact.'); $insecure = $this->t('Private communications are not available for this contact.');
$serverIgnored =
$this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) ?
$this->t('This contact is on a server you ignored.')
: '';
$last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? $this->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A')); $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? $this->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
if ($contact['last-update'] > DBA::NULL_DATETIME) { if ($contact['last-update'] > DBA::NULL_DATETIME) {
@ -363,6 +376,8 @@ class Profile extends BaseModule
'$collapsed' => $localRelationship->collapsed ? $this->t('Currently collapsed') : '', '$collapsed' => $localRelationship->collapsed ? $this->t('Currently collapsed') : '',
'$archived' => ($contact['archive'] ? $this->t('Currently archived') : ''), '$archived' => ($contact['archive'] ? $this->t('Currently archived') : ''),
'$insecure' => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure), '$insecure' => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
'$serverIgnored' => $serverIgnored,
'$manageServers' => $this->t('Manage remote servers'),
'$cinfo' => ['info', '', $localRelationship->info, ''], '$cinfo' => ['info', '', $localRelationship->info, ''],
'$hidden' => ['hidden', $this->t('Hide this contact from others'), $localRelationship->hidden, $this->t('Replies/likes to your public posts <strong>may</strong> still be visible')], '$hidden' => ['hidden', $this->t('Hide this contact from others'), $localRelationship->hidden, $this->t('Replies/likes to your public posts <strong>may</strong> still be visible')],
'$notify_new_posts' => ['notify_new_posts', $this->t('Notification for new posts'), ($localRelationship->notifyNewPosts), $this->t('Send a notification of every new post of this contact')], '$notify_new_posts' => ['notify_new_posts', $this->t('Notification for new posts'), ($localRelationship->notifyNewPosts), $this->t('Send a notification of every new post of this contact')],
@ -413,11 +428,11 @@ class Profile extends BaseModule
* This includes actions like e.g. 'block', 'hide', 'delete' and others * This includes actions like e.g. 'block', 'hide', 'delete' and others
* *
* @param array $contact Public contact row * @param array $contact Public contact row
* @param Entity\LocalRelationship $localRelationship * @param LocalRelationship\Entity\LocalRelationship $localRelationship
* @return array with contact related actions * @return array with contact related actions
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
*/ */
private function getContactActions(array $contact, Entity\LocalRelationship $localRelationship): array private function getContactActions(array $contact, LocalRelationship\Entity\LocalRelationship $localRelationship): array
{ {
$poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]); $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$contact_actions = []; $contact_actions = [];
@ -518,10 +533,9 @@ class Profile extends BaseModule
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
*/ */
private static function updateContactFromProbe(int $contact_id) private function updateContactFromProbe(int $contact_id)
{ {
$contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => [0, DI::userSession()->getLocalUserId()], 'deleted' => false]); if (!$this->db->exists('contact', ['id' => $contact_id, 'uid' => [0, $this->session->getLocalUserId()], 'deleted' => false])) {
if (!DBA::isResult($contact)) {
return; return;
} }

View File

@ -356,7 +356,7 @@ class Community extends BaseModule
} }
} }
$r = Post::selectThreadForUser(0, ['uri-id', 'commented', 'author-link'], $condition, $params); $r = Post::selectThreadForUser(DI::userSession()->getLocalUserId() ?: 0, ['uri-id', 'commented', 'author-link'], $condition, $params);
$items = Post::toArray($r); $items = Post::toArray($r);
if (empty($items)) { if (empty($items)) {

View File

@ -133,7 +133,9 @@ class Display extends BaseModule
} }
if (empty($item)) { if (empty($item)) {
throw new HTTPException\NotFoundException($this->t('The requested item doesn\'t exist or has been deleted.')); $this->page['aside'] = '';
$displayNotFound = new DisplayNotFound($this->l10n, $this->baseUrl, $this->args, $this->logger, $this->profiler, $this->response, $this->server, $this->parameters);
return $displayNotFound->content();
} }
if ($item['gravity'] != Item::GRAVITY_PARENT) { if ($item['gravity'] != Item::GRAVITY_PARENT) {

View File

@ -0,0 +1,117 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Settings\Server;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System;
use Friendica\Federation\Repository\GServer;
use Friendica\Module\Response;
use Friendica\Network\HTTPException\BadRequestException;
use Friendica\User\Settings\Repository\UserGServer;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
class Action extends \Friendica\BaseModule
{
/** @var IHandleUserSessions */
private $session;
/** @var UserGServer */
private $repository;
/** @var GServer */
private $gserverRepo;
public function __construct(GServer $gserverRepo, UserGServer $repository, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->repository = $repository;
$this->gserverRepo = $gserverRepo;
}
public function content(array $request = []): string
{
$GServer = $this->gserverRepo->selectOneById($this->parameters['gsid']);
switch ($this->parameters['action']) {
case 'ignore':
$action = $this->t('Do you want to ignore this server?');
$desc = $this->t("You won't see any content from this server including reshares in your Network page, the community pages and individual conversations.");
break;
case 'unignore':
$action = $this->t('Do you want to unignore this server?');
$desc = '';
break;
default:
throw new BadRequestException('Unknown user server action ' . $this->parameters['action']);
}
$tpl = Renderer::getMarkupTemplate('settings/server/action.tpl');
return Renderer::replaceMacros($tpl, [
'$l10n' => [
'title' => $this->t('Remote server settings'),
'action' => $action,
'siteName' => $this->t('Server Name'),
'siteUrl' => $this->t('Server URL'),
'desc' => $desc,
'submit' => $this->t('Submit'),
],
'$action' => $this->args->getQueryString(),
'$GServer' => $GServer,
'$form_security_token' => self::getFormSecurityToken('settings-server'),
]);
}
public function post(array $request = [])
{
if (!empty($request['redirect_url'])) {
self::checkFormSecurityTokenRedirectOnError($this->args->getQueryString(), 'settings-server');
}
$userGServer = $this->repository->getOneByUserAndServer($this->session->getLocalUserId(), $this->parameters['gsid']);
switch ($this->parameters['action']) {
case 'ignore':
$userGServer->ignore();
break;
case 'unignore':
$userGServer->unignore();
break;
default:
throw new BadRequestException('Unknown user server action ' . $this->parameters['action']);
}
$this->repository->save($userGServer);
if (!empty($request['redirect_url'])) {
$this->baseUrl->redirect($request['redirect_url']);
}
System::exit();
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Settings\Server;
use Friendica\App;
use Friendica\Content\Pager;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Module\BaseSettings;
use Friendica\Module\Response;
use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\User\Settings\Entity\UserGServer;
use Friendica\User\Settings\Repository;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
class Index extends BaseSettings
{
/** @var Repository\UserGServer */
private $repository;
/** @var SystemMessages */
private $systemMessages;
public function __construct(SystemMessages $systemMessages, Repository\UserGServer $repository, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->repository = $repository;
$this->systemMessages = $systemMessages;
}
protected function post(array $request = [])
{
self::checkFormSecurityTokenRedirectOnError($this->args->getQueryString(), 'settings-server');
foreach ($request['delete'] ?? [] as $gsid => $delete) {
if ($delete) {
unset($request['ignored'][$gsid]);
try {
$userGServer = $this->repository->selectOneByUserAndServer($this->session->getLocalUserId(), $gsid, false);
$this->repository->delete($userGServer);
} catch (NotFoundException $e) {
// Nothing to delete
}
}
}
foreach ($request['ignored'] ?? [] as $gsid => $ignored) {
$userGServer = $this->repository->getOneByUserAndServer($this->session->getLocalUserId(), $gsid, false);
if ($userGServer->ignored != $ignored) {
$userGServer->toggleIgnored();
$this->repository->save($userGServer);
}
}
$this->systemMessages->addInfo($this->t('Settings saved'));
$this->baseUrl->redirect($this->args->getQueryString());
}
protected function content(array $request = []): string
{
parent::content();
$pager = new Pager($this->l10n, $this->args->getQueryString(), 30);
$total = $this->repository->countByUser($this->session->getLocalUserId());
$servers = $this->repository->selectByUserWithPagination($this->session->getLocalUserId(), $pager);
$ignoredCheckboxes = array_map(function (UserGServer $server) {
return ['ignored[' . $server->gsid . ']', '', $server->ignored];
}, $servers->getArrayCopy());
$deleteCheckboxes = array_map(function (UserGServer $server) {
return ['delete[' . $server->gsid . ']'];
}, $servers->getArrayCopy());
$tpl = Renderer::getMarkupTemplate('settings/server/index.tpl');
return Renderer::replaceMacros($tpl, [
'$l10n' => [
'title' => $this->t('Remote server settings'),
'desc' => $this->t('Here you can find all the remote servers you have taken individual moderation actions against. For a list of servers your node has blocked, please check out the <a href="friendica">Information</a> page.'),
'siteName' => $this->t('Server Name'),
'ignored' => $this->t('Ignored'),
'ignored_title' => $this->t("You won't see any content from this server including reshares in your Network page, the community pages and individual conversations."),
'delete' => $this->t('Delete'),
'delete_title' => $this->t('Delete all your settings for the remote server'),
'submit' => $this->t('Save changes'),
],
'$count' => $total,
'$servers' => $servers,
'$form_security_token' => self::getFormSecurityToken('settings-server'),
'$ignoredCheckboxes' => $ignoredCheckboxes,
'$deleteCheckboxes' => $deleteCheckboxes,
'$paginate' => $pager->renderFull($total),
]);
}
}

View File

@ -31,18 +31,21 @@ class DisplayNotFound extends \Friendica\BaseModule
{ {
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
$reasons = [
$this->t("The top-level post isn't visible."),
$this->t('The top-level post was deleted.'),
$this->t('This node has blocked the top-level author or the author of the shared post.'),
$this->t('You have ignored or blocked the top-level author or the author of the shared post.'),
$this->t("You have ignored the top-level author's server or the shared post author's server."),
];
$tpl = Renderer::getMarkupTemplate('special/displaynotfound.tpl'); $tpl = Renderer::getMarkupTemplate('special/displaynotfound.tpl');
return Renderer::replaceMacros($tpl, [ return Renderer::replaceMacros($tpl, [
'$l10n' => [ '$l10n' => [
'title' => $this->t('Not Found'), 'title' => $this->t('Conversation Not Found'),
'message' => $this->t("<p>Unfortunately, the requested conversation isn't available to you.</p> 'desc1' => $this->t("Unfortunately, the requested conversation isn't available to you."),
<p>Possible reasons include:</p> 'desc2' => $this->t('Possible reasons include:'),
<ul> 'reasons' => $reasons,
<li>The top-level post isn't visible.</li>
<li>The top-level post was deleted.</li>
<li>The node has blocked the top-level author or the author of the shared post.</li>
<li>You have ignored or blocked the top-level author or the author of the shared post.</li>
</ul>"),
] ]
]); ]);
} }

View File

@ -38,6 +38,7 @@ use Friendica\Model\User;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Proxy; use Friendica\Util\Proxy;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use Friendica\Util\Temporal; use Friendica\Util\Temporal;
@ -248,11 +249,12 @@ class Post
$pinned = DI::l10n()->t('Pinned item'); $pinned = DI::l10n()->t('Pinned item');
} }
$drop = false; $drop = false;
$block = false; $block = false;
$ignore = false; $ignore = false;
$collapse = false; $collapse = false;
$report = false; $report = false;
$ignoreServer = false;
if (DI::userSession()->getLocalUserId()) { if (DI::userSession()->getLocalUserId()) {
$drop = [ $drop = [
'dropping' => $dropping, 'dropping' => $dropping,
@ -282,6 +284,11 @@ class Post
'label' => DI::l10n()->t('Report post'), 'label' => DI::l10n()->t('Report post'),
'href' => 'moderation/report/create?' . http_build_query(['cid' => $item['author-id'], 'uri-ids' => [$item['uri-id']]]), 'href' => 'moderation/report/create?' . http_build_query(['cid' => $item['author-id'], 'uri-ids' => [$item['uri-id']]]),
]; ];
if (!Network::isLocalLink($item['plink'])) {
$ignoreServer = [
'label' => DI::l10n()->t("Ignore %s's server", $item['author-name']),
];
}
} }
$filer = DI::userSession()->getLocalUserId() ? DI::l10n()->t('Save to folder') : false; $filer = DI::userSession()->getLocalUserId() ? DI::l10n()->t('Save to folder') : false;
@ -557,6 +564,7 @@ class Post
'ignore_author' => $ignore, 'ignore_author' => $ignore,
'collapse' => $collapse, 'collapse' => $collapse,
'report' => $report, 'report' => $report,
'ignore_server' => $ignoreServer,
'vote' => $buttons, 'vote' => $buttons,
'like_html' => $responses['like']['output'], 'like_html' => $responses['like']['output'],
'dislike_html' => $responses['dislike']['output'], 'dislike_html' => $responses['dislike']['output'],
@ -571,6 +579,7 @@ class Post
'wait' => DI::l10n()->t('Please wait'), 'wait' => DI::l10n()->t('Please wait'),
'thread_level' => $thread_level, 'thread_level' => $thread_level,
'edited' => $edited, 'edited' => $edited,
'author_gsid' => $item['author-gsid'],
'network' => $item['network'], 'network' => $item['network'],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']), 'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']), 'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),

View File

@ -0,0 +1,30 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\User\Settings\Collection;
class UserGServers extends \Friendica\BaseCollection
{
public function current(): \Friendica\User\Settings\Entity\UserGServer
{
return parent::current();
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\User\Settings\Entity;
use Friendica\Federation\Entity\GServer;
/**
* @property-read int $uid
* @property-read int $gsid
* @property-read bool $ignored
* @property-read ?GServer $gserver
*/
class UserGServer extends \Friendica\BaseEntity
{
/** @var int User id */
protected $uid;
/** @var int GServer id */
protected $gsid;
/** @var bool Whether the user ignored this server */
protected $ignored;
/** @var ?GServer */
protected $gserver;
public function __construct(int $uid, int $gsid, bool $ignored = false, ?GServer $gserver = null)
{
$this->uid = $uid;
$this->gsid = $gsid;
$this->ignored = $ignored;
$this->gserver = $gserver;
}
/**
* Toggle the ignored property.
*
* Chainable.
*
* @return $this
*/
public function toggleIgnored(): UserGServer
{
$this->ignored = !$this->ignored;
return $this;
}
/**
* Set the ignored property.
*
* Chainable.
*
* @return $this
*/
public function ignore(): UserGServer
{
$this->ignored = true;
return $this;
}
/**
* Unset the ignored property.
*
* Chainable.
*
* @return $this
*/
public function unignore(): UserGServer
{
$this->ignored = false;
return $this;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\User\Settings\Factory;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Federation\Entity\GServer;
use Friendica\User\Settings\Entity;
class UserGServer extends \Friendica\BaseFactory implements ICanCreateFromTableRow
{
/**
* @param array $row `user-gserver` table row
* @param GServer|null $server Corresponding GServer entity
* @return Entity\UserGServer
*/
public function createFromTableRow(array $row, GServer $server = null): Entity\UserGServer
{
return new Entity\UserGServer(
$row['uid'],
$row['gsid'],
$row['ignored'],
$server,
);
}
/**
* @param int $uid
* @param int $gsid
* @param GServer|null $gserver Corresponding GServer entity
* @return Entity\UserGServer
*/
public function createFromUserAndServer(int $uid, int $gsid, GServer $gserver = null): Entity\UserGServer
{
return new Entity\UserGServer(
$uid,
$gsid,
false,
$gserver,
);
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\User\Settings\Repository;
use Exception;
use Friendica\BaseCollection;
use Friendica\BaseEntity;
use Friendica\Content\Pager;
use Friendica\Database\Database;
use Friendica\Federation\Repository\GServer;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\User\Settings\Collection;
use Friendica\User\Settings\Entity;
use Friendica\User\Settings\Factory;
use Psr\Log\LoggerInterface;
class UserGServer extends \Friendica\BaseRepository
{
protected static $table_name = 'user-gserver';
/** @var Factory\UserGServer */
protected $factory;
/** @var GServer */
protected $gserverRepository;
public function __construct(GServer $gserverRepository, Database $database, LoggerInterface $logger, Factory\UserGServer $factory)
{
parent::__construct($database, $logger, $factory);
$this->gserverRepository = $gserverRepository;
}
/**
* Returns an existing UserGServer entity or create one on the fly
*
* @param int $uid
* @param int $gsid
* @param bool $hydrate Populate the related GServer entity
* @return Entity\UserGServer
*/
public function getOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): Entity\UserGServer
{
try {
return $this->selectOneByUserAndServer($uid, $gsid, $hydrate);
} catch (NotFoundException $e) {
return $this->factory->createFromUserAndServer($uid, $gsid, $hydrate ? $this->gserverRepository->selectOneById($gsid) : null);
}
}
/**
* @param int $uid
* @param int $gsid
* @param bool $hydrate Populate the related GServer entity
* @return Entity\UserGServer
* @throws NotFoundException
*/
public function selectOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): Entity\UserGServer
{
return $this->_selectOne(['uid' => $uid, 'gsid' => $gsid], [], $hydrate);
}
public function save(Entity\UserGServer $userGServer): Entity\UserGServer
{
$fields = [
'uid' => $userGServer->uid,
'gsid' => $userGServer->gsid,
'ignored' => $userGServer->ignored,
];
$this->db->insert(static::$table_name, $fields, Database::INSERT_UPDATE);
return $userGServer;
}
public function selectByUserWithPagination(int $uid, Pager $pager): Collection\UserGServers
{
return $this->_select(['uid' => $uid], ['limit' => [$pager->getStart(), $pager->getItemsPerPage()]]);
}
public function countByUser(int $uid): int
{
return $this->count(['uid' => $uid]);
}
public function isIgnoredByUser(int $uid, int $gsid): bool
{
return $this->exists(['uid' => $uid, 'gsid' => $gsid, 'ignored' => 1]);
}
/**
* @param Entity\UserGServer $userGServer
* @return bool
* @throws InternalServerErrorException in case the underlying storage cannot delete the record
*/
public function delete(Entity\UserGServer $userGServer): bool
{
try {
return $this->db->delete(self::$table_name, ['uid' => $userGServer->uid, 'gsid' => $userGServer->gsid]);
} catch (\Exception $exception) {
throw new InternalServerErrorException('Cannot delete the UserGServer', $exception);
}
}
protected function _selectOne(array $condition, array $params = [], bool $hydrate = true): BaseEntity
{
$fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
if (!$this->db->isResult($fields)) {
throw new NotFoundException();
}
return $this->factory->createFromTableRow($fields, $hydrate ? $this->gserverRepository->selectOneById($fields['gsid']) : null);
}
/**
* @param array $condition
* @param array $params
* @return Collection\UserGServers
* @throws Exception
*/
protected function _select(array $condition, array $params = [], bool $hydrate = true): BaseCollection
{
$rows = $this->db->selectToArray(static::$table_name, [], $condition, $params);
$Entities = new Collection\UserGServers();
foreach ($rows as $fields) {
$Entities[] = $this->factory->createFromTableRow($fields, $hydrate ? $this->gserverRepository->selectOneById($fields['gsid']) : null);
}
return $Entities;
}
public function listIgnoredByUser(int $uid): Collection\UserGServers
{
return $this->_select(['uid' => $uid, 'ignored' => 1], [], false);
}
}

View File

@ -56,7 +56,7 @@ use Friendica\Database\DBA;
// This file is required several times during the test in DbaDefinition which justifies this condition // This file is required several times during the test in DbaDefinition which justifies this condition
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1525); define('DB_UPDATE_VERSION', 1527);
} }
return [ return [
@ -159,6 +159,17 @@ return [
"email" => ["email(64)"], "email" => ["email(64)"],
] ]
], ],
"user-gserver" => [
"comment" => "User settings about remote servers",
"fields" => [
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "foreign" => ["user" => "uid"], "comment" => "Owner User id"],
"gsid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["gserver" => "id"], "comment" => "Gserver id"],
"ignored" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "server accounts are ignored for the user"],
],
"indexes" => [
"PRIMARY" => ["uid", "gsid"],
],
],
"item-uri" => [ "item-uri" => [
"comment" => "URI and GUID for items", "comment" => "URI and GUID for items",
"fields" => [ "fields" => [

View File

@ -197,6 +197,7 @@
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
"owner-hidden" => ["owner", "hidden"], "owner-hidden" => ["owner", "hidden"],
"owner-updated" => ["owner", "updated"], "owner-updated" => ["owner", "updated"],
"owner-gsid" => ["owner", "gsid"],
"owner-contact-type" => ["owner", "contact-type"], "owner-contact-type" => ["owner", "contact-type"],
"causer-id" => ["post-user", "causer-id"], "causer-id" => ["post-user", "causer-id"],
"causer-uri-id" => ["causer", "uri-id"], "causer-uri-id" => ["causer", "uri-id"],
@ -209,6 +210,7 @@
"causer-network" => ["causer", "network"], "causer-network" => ["causer", "network"],
"causer-blocked" => ["causer", "blocked"], "causer-blocked" => ["causer", "blocked"],
"causer-hidden" => ["causer", "hidden"], "causer-hidden" => ["causer", "hidden"],
"causer-gsid" => ["causer", "gsid"],
"causer-contact-type" => ["causer", "contact-type"], "causer-contact-type" => ["causer", "contact-type"],
"postopts" => ["post-delivery-data", "postopts"], "postopts" => ["post-delivery-data", "postopts"],
"inform" => ["post-delivery-data", "inform"], "inform" => ["post-delivery-data", "inform"],
@ -340,6 +342,7 @@
"contact-pending" => ["contact", "pending"], "contact-pending" => ["contact", "pending"],
"contact-rel" => ["contact", "rel"], "contact-rel" => ["contact", "rel"],
"contact-uid" => ["contact", "uid"], "contact-uid" => ["contact", "uid"],
"contact-gsid" => ["contact", "gsid"],
"contact-contact-type" => ["contact", "contact-type"], "contact-contact-type" => ["contact", "contact-type"],
"writable" => "IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`)", "writable" => "IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`)",
"self" => ["contact", "self"], "self" => ["contact", "self"],
@ -375,6 +378,7 @@
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
"owner-hidden" => ["owner", "hidden"], "owner-hidden" => ["owner", "hidden"],
"owner-updated" => ["owner", "updated"], "owner-updated" => ["owner", "updated"],
"owner-gsid" => ["owner", "gsid"],
"owner-contact-type" => ["owner", "contact-type"], "owner-contact-type" => ["owner", "contact-type"],
"causer-id" => ["post-thread-user", "causer-id"], "causer-id" => ["post-thread-user", "causer-id"],
"causer-uri-id" => ["causer", "uri-id"], "causer-uri-id" => ["causer", "uri-id"],
@ -387,6 +391,7 @@
"causer-network" => ["causer", "network"], "causer-network" => ["causer", "network"],
"causer-blocked" => ["causer", "blocked"], "causer-blocked" => ["causer", "blocked"],
"causer-hidden" => ["causer", "hidden"], "causer-hidden" => ["causer", "hidden"],
"causer-gsid" => ["causer", "gsid"],
"causer-contact-type" => ["causer", "contact-type"], "causer-contact-type" => ["causer", "contact-type"],
"postopts" => ["post-delivery-data", "postopts"], "postopts" => ["post-delivery-data", "postopts"],
"inform" => ["post-delivery-data", "inform"], "inform" => ["post-delivery-data", "inform"],
@ -540,6 +545,7 @@
"owner-hidden" => ["owner", "hidden"], "owner-hidden" => ["owner", "hidden"],
"owner-updated" => ["owner", "updated"], "owner-updated" => ["owner", "updated"],
"owner-contact-type" => ["owner", "contact-type"], "owner-contact-type" => ["owner", "contact-type"],
"owner-gsid" => ["owner", "gsid"],
"causer-id" => ["post", "causer-id"], "causer-id" => ["post", "causer-id"],
"causer-uri-id" => ["causer", "uri-id"], "causer-uri-id" => ["causer", "uri-id"],
"causer-link" => ["causer", "url"], "causer-link" => ["causer", "url"],
@ -552,6 +558,7 @@
"causer-blocked" => ["causer", "blocked"], "causer-blocked" => ["causer", "blocked"],
"causer-hidden" => ["causer", "hidden"], "causer-hidden" => ["causer", "hidden"],
"causer-contact-type" => ["causer", "contact-type"], "causer-contact-type" => ["causer", "contact-type"],
"causer-gsid" => ["causer", "gsid"],
"question-id" => ["post-question", "id"], "question-id" => ["post-question", "id"],
"question-multiple" => ["post-question", "multiple"], "question-multiple" => ["post-question", "multiple"],
"question-voters" => ["post-question", "voters"], "question-voters" => ["post-question", "voters"],
@ -680,6 +687,7 @@
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
"owner-hidden" => ["owner", "hidden"], "owner-hidden" => ["owner", "hidden"],
"owner-updated" => ["owner", "updated"], "owner-updated" => ["owner", "updated"],
"owner-gsid" => ["owner", "gsid"],
"owner-contact-type" => ["owner", "contact-type"], "owner-contact-type" => ["owner", "contact-type"],
"causer-id" => ["post-thread", "causer-id"], "causer-id" => ["post-thread", "causer-id"],
"causer-uri-id" => ["causer", "uri-id"], "causer-uri-id" => ["causer", "uri-id"],
@ -692,6 +700,7 @@
"causer-network" => ["causer", "network"], "causer-network" => ["causer", "network"],
"causer-blocked" => ["causer", "blocked"], "causer-blocked" => ["causer", "blocked"],
"causer-hidden" => ["causer", "hidden"], "causer-hidden" => ["causer", "hidden"],
"causer-gsid" => ["causer", "gsid"],
"causer-contact-type" => ["causer", "contact-type"], "causer-contact-type" => ["causer", "contact-type"],
"question-id" => ["post-question", "id"], "question-id" => ["post-question", "id"],
"question-multiple" => ["post-question", "multiple"], "question-multiple" => ["post-question", "multiple"],
@ -804,7 +813,7 @@
"contact-type" => ["ownercontact", "contact-type"], "contact-type" => ["ownercontact", "contact-type"],
], ],
"query" => "FROM `post-user` "query" => "FROM `post-user`
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid` INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id` STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id` STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id` STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
@ -843,7 +852,8 @@
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`) AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked` AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`) AND (`author`.`blocked` IS NULL OR NOT `author`.`blocked`)
AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`)" AND (`owner`.`blocked` IS NULL OR NOT `owner`.`blocked`)
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`)"
], ],
"owner-view" => [ "owner-view" => [
"fields" => [ "fields" => [

View File

@ -639,6 +639,10 @@ return [
], ],
'/settings' => [ '/settings' => [
'/server' => [
'[/]' => [Module\Settings\Server\Index::class, [R::GET, R::POST]],
'/{gsid:\d+}/{action}' => [Module\Settings\Server\Action::class, [R::GET, R::POST]],
],
'[/]' => [Module\Settings\Account::class, [R::GET, R::POST]], '[/]' => [Module\Settings\Account::class, [R::GET, R::POST]],
'/account' => [ '/account' => [
'[/]' => [Module\Settings\Account::class, [R::GET, R::POST]], '[/]' => [Module\Settings\Account::class, [R::GET, R::POST]],

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@
{{if $c[0]['total'] > 0}} {{if $c[0]['total'] > 0}}
<tr> <tr>
<th>{{$c[0]['platform']}}</th> <th>{{$c[0]['platform']}}</th>
<th><strong>{{$c[0]['total']}}</strong></td> <th><strong>{{$c[0]['total']}}</strong></th>
<td>{{$c[0]['network']}}</td> <td>{{$c[0]['network']}}</td>
</tr> </tr>
<tr> <tr>

View File

@ -0,0 +1,27 @@
<div id="settings-server" class="generic-page-wrapper">
<h1>{{$l10n.title}}</h1>
<form action="{{$action}}" method="POST">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<input type="hidden" name="redirect_url" value="settings/server">
<p>{{$l10n.action}}</p>
{{if $l10n.desc}}
<p>{{$l10n.desc}}</p>
{{/if}}
<table>
<tr>
<th>{{$l10n.siteName}}</th>
<td>{{$GServer->siteName}}</td>
</tr>
<tr>
<th>{{$l10n.siteUrl}}</th>
<td><a href="{{$GServer->url}}">{{$GServer->url}}</a></td>
</tr>
</table>
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
</div>

View File

@ -0,0 +1,45 @@
<div id="settings-server" class="generic-page-wrapper">
<h1>{{$l10n.title}} ({{$count}})</h1>
<p>{{$l10n.desc nofilter}}</p>
{{$paginate nofilter}}
<form action="" method="POST">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
<table class="table table-striped table-condensed table-bordered">
<tr>
<th>{{$l10n.siteName}}</th>
<th><span title="{{$l10n.ignored_title}}">{{$l10n.ignored}} <i class="fa fa-question-circle icon-question-sign"></i></span></th>
<th>
<span title="{{$l10n.delete_title}}">
<i class="fa fa-trash icon-trash" aria-hidden="true" title="{{$l10n.delete}}"></i>
<span class="sr-only">{{$l10n.delete}}</span>
<i class="fa fa-question-circle icon-question-sign"></i>
</span>
</th>
</tr>
{{foreach $servers as $index => $server}}
<tr>
<td>
<a href="{{$server->gserver->url}}">{{($server->gserver->siteName) ? $server->gserver->siteName : $server->gserver->url}} <i class="fa fa-external-link"></i></a>
</td>
<td>
{{include file="field_checkbox.tpl" field=$ignoredCheckboxes[$index]}}
</td>
<td>
{{include file="field_checkbox.tpl" field=$deleteCheckboxes[$index]}}
</td>
</tr>
{{/foreach}}
</table>
<p><button type="submit" class="btn btn-primary">{{$l10n.submit}}</button></p>
</form>
{{$paginate nofilter}}
</div>

View File

@ -1,5 +1,11 @@
<div id="exception" class="generic-page-wrapper"> <div id="exception" class="generic-page-wrapper">
<img class="hare" src="images/friendica-404_svg_flexy-o-hare.png"/> <img class="hare" src="images/friendica-404_svg_flexy-o-hare.png"/>
<h1>{{$title}}</h1> <h1>{{$l10n.title}}</h1>
{{$message nofilter}} <p>{{$l10n.desc1}}</p>
<p>{{$l10n.desc2}}</p>
<ul>
{{foreach $l10n.reasons as $reason}}
<li>{{$reason}}</li>
{{/foreach}}
</ul>
</div> </div>

View File

@ -210,6 +210,10 @@ function confirmCollapse() {
return confirm(aStr.collapseAuthor); return confirm(aStr.collapseAuthor);
} }
function confirmIgnoreServer() {
return confirm(aStr.ignoreServer + "\n" + aStr.ignoreServerDesc);
}
/** /**
* Hide and removes an item element from the DOM after the deletion url is * Hide and removes an item element from the DOM after the deletion url is
* successful, restore it else. * successful, restore it else.
@ -325,4 +329,34 @@ function collapseAuthor(url, elementId) {
}); });
} }
} }
/**
* Ignore author server
*
* @param {string} url The server ignore URL
* @param {string} elementId The DOM id of the item element
* @returns {undefined}
*/
function ignoreServer(url, elementId) {
if (confirmIgnoreServer()) {
$("body").css("cursor", "wait");
var $el = $(document.getElementById(elementId));
$el.fadeTo("fast", 0.33, function () {
$.post(url)
.then(function () {
$el.remove();
})
.fail(function () {
// @todo Show related error message
$el.fadeTo("fast", 1);
})
.always(function () {
$("body").css("cursor", "auto");
});
});
}
}
// @license-end // @license-end

View File

@ -61,6 +61,7 @@
{{if $ignored}}<li><div id="ignore-message">{{$ignored}}</div></li>{{/if}} {{if $ignored}}<li><div id="ignore-message">{{$ignored}}</div></li>{{/if}}
{{if $collapsed}}<li><div id="collapse-message">{{$collapsed}}</div></li>{{/if}} {{if $collapsed}}<li><div id="collapse-message">{{$collapsed}}</div></li>{{/if}}
{{if $archived}}<li><div id="archive-message">{{$archived}}</div></li>{{/if}} {{if $archived}}<li><div id="archive-message">{{$archived}}</div></li>{{/if}}
{{if $serverIgnored}}<li><div id="serverIgnored-message">{{$serverIgnored}} <a href="settings/server">{{$manageServers}}</a></div></li>{{/if}}
</ul> </ul>
</div> {{* End of contact-edit-status-wrapper *}} </div> {{* End of contact-edit-status-wrapper *}}

View File

@ -3,10 +3,12 @@
They are loaded into the html <head> so that js functions can use them *}} They are loaded into the html <head> so that js functions can use them *}}
<script type="text/javascript"> <script type="text/javascript">
const aStr = { const aStr = {
delitem : "{{$l10n.delitem|escape:'javascript' nofilter}}", delitem : "{{$l10n.delitem|escape:'javascript' nofilter}}",
blockAuthor : "{{$l10n.blockAuthor|escape:'javascript' nofilter}}", blockAuthor : "{{$l10n.blockAuthor|escape:'javascript' nofilter}}",
ignoreAuthor : "{{$l10n.ignoreAuthor|escape:'javascript' nofilter}}", ignoreAuthor : "{{$l10n.ignoreAuthor|escape:'javascript' nofilter}}",
collapseAuthor : "{{$l10n.collapseAuthor|escape:'javascript' nofilter}}", collapseAuthor : "{{$l10n.collapseAuthor|escape:'javascript' nofilter}}",
ignoreServer : "{{$l10n.ignoreServer|escape:'javascript' nofilter}}",
ignoreServerDesc : "{{$l10n.ignoreServerDesc|escape:'javascript' nofilter}}",
}; };
const aActErr = { const aActErr = {
like : "{{$l10n.likeError|escape:'javascript' nofilter}}", like : "{{$l10n.likeError|escape:'javascript' nofilter}}",

View File

@ -401,13 +401,17 @@ as the value of $top_child_total (this is done at the end of this file)
{{/if}} {{/if}}
{{if $item.ignore_author}} {{if $item.ignore_author}}
<li role="menuitem"> <li role="menuitem">
<a class="btn-link navicon ignore" href="javascript:ignoreAuthor('item/ignore/{{$item.id}}', 'item-{{$item.guid}}');" title="{{$item.ignore_author.label}}"><i class="fa fa-ban" aria-hidden="true"></i> {{$item.ignore_author.label}}</a> <a class="btn-link navicon ignore" href="javascript:ignoreAuthor('item/ignore/{{$item.id}}', 'item-{{$item.guid}}');" title="{{$item.ignore_author.label}}"><i class="fa fa-eye-slash" aria-hidden="true"></i> {{$item.ignore_author.label}}</a>
</li> </li>
{{/if}} {{/if}}
{{if $item.collapse}} {{if $item.collapse}}
<li role="menuitem"> <li role="menuitem">
<a class="btn-link navicon collapse" href="javascript:collapseAuthor('item/collapse/{{$item.id}}', 'item-{{$item.guid}}');" title="{{$item.collapse.label}}"><i class="fa fa-ban" aria-hidden="true"></i> {{$item.collapse.label}}</a> <a class="btn-link navicon collapse" href="javascript:collapseAuthor('item/collapse/{{$item.id}}', 'item-{{$item.guid}}');" title="{{$item.collapse.label}}"><i class="fa fa-minus-square" aria-hidden="true"></i> {{$item.collapse.label}}</a>
</li> </li>
{{/if}}
{{if $item.ignore_server}}
<li role="menuitem">
<a class="btn-link navicon ignoreServer" href="javascript:ignoreServer('settings/server/{{$item.author_gsid}}/ignore', 'item-{{$item.guid}}');" title="{{$item.ignore_server.label}}"><i class="fa fa-eye-slash" aria-hidden="true"></i> {{$item.ignore_server.label}}</a>
</li> </li>
{{/if}} {{/if}}
{{if $item.report}} {{if $item.report}}

View File

@ -3281,3 +3281,7 @@ fbrowser.photo .photo-album-image-wrapper { margin-left: 10px; }
#colorbox img { #colorbox img {
max-width: 100%; max-width: 100%;
} }
#settings-server td + td {
text-align: center;
}