From 013aee01f5d2af35bf141e2c389b9708c184306b Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 09:14:36 +0000 Subject: [PATCH 01/20] Network, Channels and Community are children of timeline --- src/Content/Conversation.php | 6 +- .../{Channels.php => Timelines.php} | 2 +- .../Entity/{Channel.php => Timeline.php} | 15 +- src/Content/Conversation/Factory/Channel.php | 65 --- src/Content/Conversation/Factory/Timeline.php | 119 +++++ src/Content/Nav.php | 10 +- src/Module/Conversation/Channel.php | 335 ++----------- src/Module/Conversation/Community.php | 314 +++--------- src/Module/Conversation/Network.php | 278 +++++----- src/Module/Conversation/Timeline.php | 474 ++++++++++++++++++ src/Module/Update/Channel.php | 8 +- src/Module/Update/Community.php | 4 +- src/Module/Update/Network.php | 5 +- static/routes.config.php | 2 +- 14 files changed, 875 insertions(+), 762 deletions(-) rename src/Content/Conversation/Collection/{Channels.php => Timelines.php} (95%) rename src/Content/Conversation/Entity/{Channel.php => Timeline.php} (78%) delete mode 100644 src/Content/Conversation/Factory/Channel.php create mode 100644 src/Content/Conversation/Factory/Timeline.php create mode 100644 src/Module/Conversation/Timeline.php diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 9e7348469..289b7851c 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -495,7 +495,8 @@ class Conversation . (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '') . (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '') . (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : '') - + . (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '') + . (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '') . "'; \r\n"; } } elseif ($mode === self::MODE_PROFILE) { @@ -930,7 +931,8 @@ class Conversation continue; } - if (in_array($row['author-gsid'], $ignoredGsids) + if ( + in_array($row['author-gsid'], $ignoredGsids) || in_array($row['owner-gsid'], $ignoredGsids) || in_array($row['causer-gsid'], $ignoredGsids) ) { diff --git a/src/Content/Conversation/Collection/Channels.php b/src/Content/Conversation/Collection/Timelines.php similarity index 95% rename from src/Content/Conversation/Collection/Channels.php rename to src/Content/Conversation/Collection/Timelines.php index a523cc7b2..da9c7c9e6 100644 --- a/src/Content/Conversation/Collection/Channels.php +++ b/src/Content/Conversation/Collection/Timelines.php @@ -23,6 +23,6 @@ namespace Friendica\Content\Conversation\Collection; use Friendica\BaseCollection; -class Channels extends BaseCollection +class Timelines extends BaseCollection { } diff --git a/src/Content/Conversation/Entity/Channel.php b/src/Content/Conversation/Entity/Timeline.php similarity index 78% rename from src/Content/Conversation/Entity/Channel.php rename to src/Content/Conversation/Entity/Timeline.php index b8e0e2bb2..b9ab1e1a0 100644 --- a/src/Content/Conversation/Entity/Channel.php +++ b/src/Content/Conversation/Entity/Timeline.php @@ -26,8 +26,9 @@ namespace Friendica\Content\Conversation\Entity; * @property-read string $label Channel label * @property-read string $description Channel description * @property-read string $accessKey Access key + * @property-read string $path Path */ -final class Channel extends \Friendica\BaseEntity +final class Timeline extends \Friendica\BaseEntity { const WHATSHOT = 'whatshot'; const FORYOU = 'foryou'; @@ -37,6 +38,13 @@ final class Channel extends \Friendica\BaseEntity const VIDEO = 'video'; const AUDIO = 'audio'; const LANGUAGE = 'language'; + const LOCAL = 'local'; + const GLOBAL = 'global'; + const STAR = 'star'; + const MENTION = 'mention'; + const RECEIVED = 'received'; + const COMMENTED = 'commented'; + const CREATED = 'created'; /** @var string */ protected $code; @@ -46,12 +54,15 @@ final class Channel extends \Friendica\BaseEntity protected $description; /** @var string */ protected $accessKey; + /** @var string */ + protected $path; - public function __construct(string $code, string $label, string $description, string $accessKey) + public function __construct(string $code, string $label, string $description, string $accessKey, string $path = null) { $this->code = $code; $this->label = $label; $this->description = $description; $this->accessKey = $accessKey; + $this->path = $path; } } diff --git a/src/Content/Conversation/Factory/Channel.php b/src/Content/Conversation/Factory/Channel.php deleted file mode 100644 index c5d172225..000000000 --- a/src/Content/Conversation/Factory/Channel.php +++ /dev/null @@ -1,65 +0,0 @@ -. - * - */ - -namespace Friendica\Content\Conversation\Factory; - -use Friendica\Content\Conversation\Collection\Channels; -use Friendica\Model\User; -use Friendica\Content\Conversation\Entity\Channel as ChannelEntity; -use Friendica\Core\L10n; -use Psr\Log\LoggerInterface; - -final class Channel extends \Friendica\BaseFactory -{ - /** @var L10n */ - protected $l10n; - - public function __construct(L10n $l10n, LoggerInterface $logger) - { - parent::__construct($logger); - - $this->l10n = $l10n; - } - - /** - * List of available channels - * - * @param integer $uid - * @return array - */ - public function getForUser(int $uid): Channels - { - $language = User::getLanguageCode($uid); - $languages = $this->l10n->getAvailableLanguages(true); - - $tabs = [ - new ChannelEntity(ChannelEntity::FORYOU, $this->l10n->t('For you'), $this->l10n->t('Posts from contacts you interact with and who interact with you'), 'y'), - new ChannelEntity(ChannelEntity::WHATSHOT, $this->l10n->t('What\'s Hot'), $this->l10n->t('Posts with a lot of interactions'), 'h'), - new ChannelEntity(ChannelEntity::LANGUAGE, $languages[$language], $this->l10n->t('Posts in %s', $languages[$language]), 'g'), - new ChannelEntity(ChannelEntity::FOLLOWERS, $this->l10n->t('Followers'), $this->l10n->t('Posts from your followers that you don\'t follow'), 'f'), - new ChannelEntity(ChannelEntity::SHARERSOFSHARERS, $this->l10n->t('Sharers of sharers'), $this->l10n->t('Posts from accounts that are followed by accounts that you follow'), 'r'), - new ChannelEntity(ChannelEntity::IMAGE, $this->l10n->t('Images'), $this->l10n->t('Posts with images'), 'i'), - new ChannelEntity(ChannelEntity::AUDIO, $this->l10n->t('Audio'), $this->l10n->t('Posts with audio'), 'd'), - new ChannelEntity(ChannelEntity::VIDEO, $this->l10n->t('Videos'), $this->l10n->t('Posts with videos'), 'v'), - ]; - return new Channels($tabs); - } -} diff --git a/src/Content/Conversation/Factory/Timeline.php b/src/Content/Conversation/Factory/Timeline.php new file mode 100644 index 000000000..160e55d1c --- /dev/null +++ b/src/Content/Conversation/Factory/Timeline.php @@ -0,0 +1,119 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Factory; + +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Model\User; +use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Module\Conversation\Community; +use Psr\Log\LoggerInterface; + +final class Timeline extends \Friendica\BaseFactory +{ + /** @var L10n */ + protected $l10n; + /** @var IManageConfigValues The config */ + protected $config; + + public function __construct(L10n $l10n, LoggerInterface $logger, IManageConfigValues $config) + { + parent::__construct($logger); + + $this->l10n = $l10n; + $this->config = $config; + } + + /** + * List of available channels + * + * @param integer $uid + * @return Timelines + */ + public function getChannelsForUser(int $uid): Timelines + { + $language = User::getLanguageCode($uid); + $languages = $this->l10n->getAvailableLanguages(true); + + $tabs = [ + new TimelineEntity(TimelineEntity::FORYOU, $this->l10n->t('For you'), $this->l10n->t('Posts from contacts you interact with and who interact with you'), 'y'), + new TimelineEntity(TimelineEntity::WHATSHOT, $this->l10n->t('What\'s Hot'), $this->l10n->t('Posts with a lot of interactions'), 'h'), + new TimelineEntity(TimelineEntity::LANGUAGE, $languages[$language], $this->l10n->t('Posts in %s', $languages[$language]), 'g'), + new TimelineEntity(TimelineEntity::FOLLOWERS, $this->l10n->t('Followers'), $this->l10n->t('Posts from your followers that you don\'t follow'), 'f'), + new TimelineEntity(TimelineEntity::SHARERSOFSHARERS, $this->l10n->t('Sharers of sharers'), $this->l10n->t('Posts from accounts that are followed by accounts that you follow'), 'r'), + new TimelineEntity(TimelineEntity::IMAGE, $this->l10n->t('Images'), $this->l10n->t('Posts with images'), 'i'), + new TimelineEntity(TimelineEntity::AUDIO, $this->l10n->t('Audio'), $this->l10n->t('Posts with audio'), 'd'), + new TimelineEntity(TimelineEntity::VIDEO, $this->l10n->t('Videos'), $this->l10n->t('Posts with videos'), 'v'), + ]; + return new Timelines($tabs); + } + + /** + * List of available communities + * + * @param boolean $authenticated + * @return Timelines + */ + public function getCommunities(bool $authenticated): Timelines + { + $page_style = $this->config->get('system', 'community_page_style'); + $tabs = []; + + if (($authenticated || in_array($page_style, [Community::LOCAL_AND_GLOBAL, Community::LOCAL])) && empty($this->config->get('system', 'singleuser'))) { + $tabs[] = new TimelineEntity(TimelineEntity::LOCAL, $this->l10n->t('Local Community'), $this->l10n->t('Posts from local users on this server'), 'l'); + } + + if ($authenticated || in_array($page_style, [Community::LOCAL_AND_GLOBAL, Community::GLOBAL])) { + $tabs[] = new TimelineEntity(TimelineEntity::GLOBAL, $this->l10n->t('Global Community'), $this->l10n->t('Posts from users of the whole federated network'), 'g'); + } + return new Timelines($tabs); + } + + /** + * List of available network feeds + * + * @param string $command + * @return Timelines + */ + public function getNetworkFeeds(string $command): Timelines + { + $tabs = [ + new TimelineEntity(TimelineEntity::COMMENTED, $this->l10n->t('Latest Activity'), $this->l10n->t('Sort by latest activity'), 'e', $command . '?' . http_build_query(['order' => 'commented'])), + new TimelineEntity(TimelineEntity::RECEIVED, $this->l10n->t('Latest Posts'), $this->l10n->t('Sort by post received date'), 't', $command . '?' . http_build_query(['order' => 'received'])), + new TimelineEntity(TimelineEntity::CREATED, $this->l10n->t('Latest Creation'), $this->l10n->t('Sort by post creation date'), 'q', $command . '?' . http_build_query(['order' => 'created'])), + new TimelineEntity(TimelineEntity::MENTION, $this->l10n->t('Personal'), $this->l10n->t('Posts that mention or involve you'), 'r', $command . '?' . http_build_query(['mention' => true])), + new TimelineEntity(TimelineEntity::STAR, $this->l10n->t('Starred'), $this->l10n->t('Favourite Posts'), 'm', $command . '?' . http_build_query(['star' => true])), + ]; + return new Timelines($tabs); + } + + public function isCommunity(string $selectedTab): bool + { + return in_array($selectedTab, [TimelineEntity::LOCAL, TimelineEntity::GLOBAL]); + } + + public function isChannel(string $selectedTab): bool + { + return in_array($selectedTab, [TimelineEntity::WHATSHOT, TimelineEntity::FORYOU, TimelineEntity::FOLLOWERS, TimelineEntity::SHARERSOFSHARERS, TimelineEntity::IMAGE, TimelineEntity::VIDEO, TimelineEntity::AUDIO, TimelineEntity::LANGUAGE]); + } +} diff --git a/src/Content/Nav.php b/src/Content/Nav.php index cfebc08f3..6cc799eb6 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -52,7 +52,7 @@ class Nav 'directory' => null, 'settings' => null, 'contacts' => null, - 'delegation'=> null, + 'delegation' => null, 'calendar' => null, 'register' => null ]; @@ -284,14 +284,14 @@ class Nav $gdirpath = Profile::zrl($this->config->get('system', 'directory'), true); } - if (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) && - !($this->config->get('system', 'community_page_style') == Community::DISABLED)) { + if ((!$this->session->isAuthenticated() && $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) && + !($this->config->get('system', 'community_page_style') == Community::DISABLED) + ) { $nav['community'] = ['community', $this->l10n->t('Community'), '', $this->l10n->t('Conversations on this and other servers')]; } - $nav['channel'] = ['channel', $this->l10n->t('Channels'), '', $this->l10n->t('Current posts, filtered by several rules')]; - if ($this->session->getLocalUserId()) { + $nav['channel'] = ['channel', $this->l10n->t('Channels'), '', $this->l10n->t('Current posts, filtered by several rules')]; $nav['calendar'] = ['calendar', $this->l10n->t('Calendar'), '', $this->l10n->t('Calendar')]; } diff --git a/src/Module/Conversation/Channel.php b/src/Module/Conversation/Channel.php index 3717a4d25..12d8ae035 100644 --- a/src/Module/Conversation/Channel.php +++ b/src/Module/Conversation/Channel.php @@ -23,83 +23,49 @@ namespace Friendica\Module\Conversation; use Friendica\App; use Friendica\App\Mode; -use Friendica\BaseModule; use Friendica\Content\BoundariesPager; use Friendica\Content\Conversation; -use Friendica\Content\Conversation\Entity\Channel as ChannelEntity; -use Friendica\Content\Conversation\Factory\Channel as ChannelFactory; +use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; use Friendica\Content\Feature; use Friendica\Content\Nav; use Friendica\Content\Text\HTML; use Friendica\Content\Widget; use Friendica\Content\Widget\TrendingTags; use Friendica\Core\Cache\Capability\ICanCache; -use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; -use Friendica\Model\Contact; use Friendica\Model\Post; -use Friendica\Model\User; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; -use Friendica\Core\Session\Model\UserSession; use Friendica\Database\Database; -use Friendica\Model\Item; use Friendica\Module\Response; use Friendica\Navigation\SystemMessages; -use Friendica\Util\DateTimeFormat; use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; -class Channel extends BaseModule +class Channel extends Timeline { - protected static $content; - protected static $accountTypeString; - protected static $accountType; - protected static $itemsPerPage; - protected static $min_id; - protected static $max_id; - protected static $item_id; - - /** @var UserSession */ - protected $session; - /** @var ICanCache */ - protected $cache; - /** @var IManageConfigValues The config */ - protected $config; - /** @var SystemMessages */ - protected $systemMessages; - /** @var App\Page */ - protected $page; + /** @var TimelineFactory */ + protected $timeline; /** @var Conversation */ protected $conversation; - /** @var App\Mode $mode */ - protected $mode; - /** @var IManagePersonalConfigValues */ - protected $pConfig; - /** @var Database */ - protected $database; - /** @var ChannelFactory */ - protected $channel; + /** @var App\Page */ + protected $page; + /** @var SystemMessages */ + protected $systemMessages; - - public function __construct(ChannelFactory $channel, SystemMessages $systemMessages, Database $database, IManagePersonalConfigValues $pConfig, Mode $mode, Conversation $conversation, App\Page $page, IManageConfigValues $config, ICanCache $cache, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + public function __construct(TimelineFactory $timeline, Conversation $conversation, App\Page $page, SystemMessages $systemMessages, Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, 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); + parent::__construct($mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->channel = $channel; - $this->systemMessages = $systemMessages; - $this->database = $database; - $this->pConfig = $pConfig; - $this->mode = $mode; + $this->timeline = $timeline; $this->conversation = $conversation; $this->page = $page; - $this->config = $config; - $this->cache = $cache; - $this->session = $session; + $this->systemMessages = $systemMessages; } protected function content(array $request = []): string @@ -122,78 +88,48 @@ class Channel extends BaseModule } if (empty($request['mode']) || ($request['mode'] != 'raw')) { - $tabs = []; - - foreach ($this->channel->getForUser($this->session->getLocalUserId()) as $tab) { - $tabs[] = [ - 'label' => $tab->label, - 'url' => 'channel/' . $tab->code, - 'sel' => self::$content == $tab->code ? 'active' : '', - 'title' => $tab->description, - 'id' => 'channel-' . $tab->code . '-tab', - 'accesskey' => $tab->accessKey, - ]; - } + $tabs = $this->getTabArray($this->timeline->getChannelsForUser($this->session->getLocalUserId()), 'channel'); + $tabs = array_merge($tabs, $this->getTabArray($this->timeline->getCommunities(true), 'channel')); $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); $o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]); Nav::setSelected('channel'); - $this->page['aside'] .= Widget::accountTypes('channel/' . self::$content, self::$accountTypeString); + $this->page['aside'] .= Widget::accountTypes('channel/' . self::$selectedTab, self::$accountTypeString); - if (!in_array(self::$content, [ChannelEntity::FOLLOWERS, ChannelEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { - $path = self::$content; - if (!empty($this->parameters['accounttype'])) { - $path .= '/' . $this->parameters['accounttype']; - } - $query_parameters = []; - - if (!empty($request['min_id'])) { - $query_parameters['min_id'] = $request['min_id']; - } - if (!empty($request['max_id'])) { - $query_parameters['max_id'] = $request['max_id']; - } - if (!empty($request['last_created'])) { - $query_parameters['max_id'] = $request['last_created']; - } - - $path_all = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : ''); - $path_no_sharer = $path . '?' . http_build_query(array_merge($query_parameters, ['no_sharer' => true])); - $this->page['aside'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/community_sharer.tpl'), [ - '$title' => $this->l10n->t('Own Contacts'), - '$path_all' => $path_all, - '$path_no_sharer' => $path_no_sharer, - '$no_sharer' => !empty($request['no_sharer']), - '$all' => $this->l10n->t('Include'), - '$no_sharer_label' => $this->l10n->t('Hide'), - '$base' => 'channel', - ]); + if (!in_array(self::$selectedTab, [TimelineEntity::FOLLOWERS, TimelineEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { + $this->page['aside'] .= $this->getNoSharerWidget('channel'); } if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) { - $this->page['aside'] .= TrendingTags::getHTML(self::$content); + $this->page['aside'] .= TrendingTags::getHTML(self::$selectedTab); } // We need the editor here to be able to reshare an item. $o .= $this->conversation->statusEditor([], 0, true); } - $items = $this->getItems($request); + if ($this->timeline->isChannel(self::$selectedTab)) { + $items = $this->getChannelItems(); + $order = 'created'; + } else { + $items = $this->getCommunityItems(); + $order = 'commented'; + } if (!$this->database->isResult($items)) { $this->systemMessages->addNotice($this->l10n->t('No results.')); return $o; } - $o .= $this->conversation->render($items, Conversation::MODE_CHANNEL, false, false, 'created', $this->session->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_CHANNEL, false, false, $order, $this->session->getLocalUserId()); $pager = new BoundariesPager( $this->l10n, $this->args->getQueryString(), - $items[0]['created'], - $items[count($items) - 1]['created'], + $items[0][$order], + $items[count($items) - 1][$order], self::$itemsPerPage ); @@ -214,34 +150,16 @@ class Channel extends BaseModule */ protected function parseRequest(array $request) { - self::$accountTypeString = $request['accounttype'] ?? $this->parameters['accounttype'] ?? ''; - self::$accountType = User::getAccountTypeByString(self::$accountTypeString); + parent::parseRequest($request); - self::$content = $this->parameters['content'] ?? ''; - if (!self::$content) { - self::$content = ChannelEntity::FORYOU; + if (!self::$selectedTab) { + self::$selectedTab = TimelineEntity::FORYOU; } - if (!in_array(self::$content, [ChannelEntity::WHATSHOT, ChannelEntity::FORYOU, ChannelEntity::FOLLOWERS, ChannelEntity::SHARERSOFSHARERS, ChannelEntity::IMAGE, ChannelEntity::VIDEO, ChannelEntity::AUDIO, ChannelEntity::LANGUAGE])) { + if (!$this->timeline->isChannel(self::$selectedTab) && !$this->timeline->isCommunity(self::$selectedTab)) { throw new HTTPException\BadRequestException($this->l10n->t('Channel not available.')); } - if ($this->mode->isMobile()) { - self::$itemsPerPage = $this->pConfig->get( - $this->session->getLocalUserId(), - 'system', - 'itemspage_mobile_network', - $this->config->get('system', 'itemspage_network_mobile') - ); - } else { - self::$itemsPerPage = $this->pConfig->get( - $this->session->getLocalUserId(), - 'system', - 'itemspage_network', - $this->config->get('system', 'itemspage_network') - ); - } - if (!empty($request['item'])) { $item = Post::selectFirst(['parent-uri-id'], ['id' => $request['item']]); self::$item_id = $item['parent-uri-id'] ?? 0; @@ -249,191 +167,6 @@ class Channel extends BaseModule self::$item_id = 0; } - self::$min_id = $request['min_id'] ?? null; - self::$max_id = $request['last_created'] ?? $request['max_id'] ?? null; - } - - /** - * Database query for the channel page - * - * @return array - * @throws \Exception - */ - protected function getItems(array $request) - { - if (self::$content == ChannelEntity::WHATSHOT) { - if (!is_null(self::$accountType)) { - $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` = ?", $this->getMedianComments(4), $this->getMedianActivities(4), self::$accountType]; - } else { - $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` != ?", $this->getMedianComments(4), $this->getMedianActivities(4), Contact::TYPE_COMMUNITY]; - } - } elseif (self::$content == ChannelEntity::FORYOU) { - $cid = Contact::getPublicIdByUserId($this->session->getLocalUserId()); - $condition = [ - "(`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR - ((`comments` >= ? OR `activities` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR - (`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?) AND `notify_new_posts`)))", - $cid, $this->getMedianRelationThreadScore($cid, 4), $this->getMedianComments(4), $this->getMedianActivities(4), $cid, - $this->session->getLocalUserId(), Contact::FRIEND, Contact::SHARING - ]; - } elseif (self::$content == ChannelEntity::FOLLOWERS) { - $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $this->session->getLocalUserId(), Contact::FOLLOWER]; - } elseif (self::$content == ChannelEntity::SHARERSOFSHARERS) { - $cid = Contact::getPublicIdByUserId($this->session->getLocalUserId()); - - // @todo Suggest posts from contacts that are followed most by our followers - $condition = [ - "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `last-interaction` > ? - AND `relation-cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `relation-thread-score` >= ?) - AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))", - DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') .' day'), $cid, $this->getMedianRelationThreadScore($cid, 4), $cid - ]; - } elseif (self::$content == ChannelEntity::IMAGE) { - $condition = ["`media-type` & ?", 1]; - } elseif (self::$content == ChannelEntity::VIDEO) { - $condition = ["`media-type` & ?", 2]; - } elseif (self::$content == ChannelEntity::AUDIO) { - $condition = ["`media-type` & ?", 4]; - } elseif (self::$content == ChannelEntity::LANGUAGE) { - $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", $this->l10n->convertCodeForLanguageDetection(User::getLanguageCode($this->session->getLocalUserId()))]; - } - - if (self::$content != ChannelEntity::LANGUAGE) { - $condition = $this->addLanguageCondition($condition); - } - - $condition[0] .= " AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))"; - $condition[] = $this->session->getLocalUserId(); - - if ((self::$content != ChannelEntity::WHATSHOT) && !is_null(self::$accountType)) { - $condition[0] .= " AND `contact-type` = ?"; - $condition[] = self::$accountType; - } - - $params = ['order' => ['created' => true], 'limit' => self::$itemsPerPage]; - - if (!empty(self::$item_id)) { - $condition[0] .= " AND `uri-id` = ?"; - $condition[] = self::$item_id; - } else { - if (!empty($request['no_sharer'])) { - $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)"; - $condition[] = $this->session->getLocalUserId(); - } - - if (isset(self::$max_id)) { - $condition[0] .= " AND `created` < ?"; - $condition[] = self::$max_id; - } - - if (isset(self::$min_id)) { - $condition[0] .= " AND `created` > ?"; - $condition[] = self::$min_id; - - // Previous page case: we want the items closest to min_id but for that we need to reverse the query order - if (!isset(self::$max_id)) { - $params['order']['created'] = false; - } - } - } - - $items = $this->database->selectToArray('post-engagement', ['uri-id', 'created'], $condition, $params); - if (empty($items)) { - return []; - } - - // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order - if (empty(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) { - $items = array_reverse($items); - } - - Item::update(['unseen' => false], ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'uri-id' => array_column($items, 'uri-id')]); - - return $items; - } - - private function addLanguageCondition(array $condition): array - { - $conditions = []; - $languages = $this->pConfig->get($this->session->getLocalUserId(), 'channel', 'languages', [User::getLanguageCode($this->session->getLocalUserId())]); - $languages = $this->l10n->convertForLanguageDetection($languages); - foreach ($languages as $language) { - $conditions[] = "JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?"; - $condition[] = $language; - } - if (!empty($conditions)) { - $condition[0] .= " AND (`language` IS NULL OR " . implode(' OR ', $conditions) . ")"; - } - return $condition; - } - - private function getMedianComments(int $divider): int - { - $languages = $this->pConfig->get($this->session->getLocalUserId(), 'channel', 'languages', [User::getLanguageCode($this->session->getLocalUserId())]); - $cache_key = 'Channel:getMedianComments:' . $divider . ':' . implode(':', $languages); - $comments = $this->cache->get($cache_key); - if (!empty($comments)) { - return $comments; - } - - $condition = ["`contact-type` != ? AND `comments` > ?", Contact::TYPE_COMMUNITY, 0]; - $condition = $this->addLanguageCondition($condition); - - $limit = $this->database->count('post-engagement', $condition) / $divider; - $post = $this->database->selectToArray('post-engagement', ['comments'], $condition, ['order' => ['comments' => true], 'limit' => [$limit, 1]]); - $comments = $post[0]['comments'] ?? 0; - if (empty($comments)) { - return 0; - } - - $this->cache->set($cache_key, $comments, Duration::HALF_HOUR); - $this->logger->debug('Calculated median comments', ['divider' => $divider, 'languages' => $languages, 'median' => $comments]); - return $comments; - } - - private function getMedianActivities(int $divider): int - { - $languages = $this->pConfig->get($this->session->getLocalUserId(), 'channel', 'languages', [User::getLanguageCode($this->session->getLocalUserId())]); - $cache_key = 'Channel:getMedianActivities:' . $divider . ':' . implode(':', $languages); - $activities = $this->cache->get($cache_key); - if (!empty($activities)) { - return $activities; - } - - $condition = ["`contact-type` != ? AND `activities` > ?", Contact::TYPE_COMMUNITY, 0]; - $condition = $this->addLanguageCondition($condition); - - $limit = $this->database->count('post-engagement', $condition) / $divider; - $post = $this->database->selectToArray('post-engagement', ['activities'], $condition, ['order' => ['activities' => true], 'limit' => [$limit, 1]]); - $activities = $post[0]['activities'] ?? 0; - if (empty($activities)) { - return 0; - } - - $this->cache->set($cache_key, $activities, Duration::HALF_HOUR); - $this->logger->debug('Calculated median activities', ['divider' => $divider, 'languages' => $languages, 'median' => $activities]); - return $activities; - } - - private function getMedianRelationThreadScore(int $cid, int $divider): int - { - $cache_key = 'Channel:getThreadScore:' . $cid . ':' . $divider; - $score = $this->cache->get($cache_key); - if (!empty($score)) { - return $score; - } - - $condition = ["`relation-cid` = ? AND `relation-thread-score` > ?", $cid, 0]; - - $limit = $this->database->count('contact-relation', $condition) / $divider; - $relation = $this->database->selectToArray('contact-relation', ['relation-thread-score'], $condition, ['order' => ['relation-thread-score' => true], 'limit' => [$limit, 1]]); - $score = $relation[0]['relation-thread-score'] ?? 0; - if (empty($score)) { - return 0; - } - - $this->cache->set($cache_key, $score, Duration::HALF_HOUR); - $this->logger->debug('Calculated median score', ['cid' => $cid, 'divider' => $divider, 'median' => $score]); - return $score; + self::$max_id = $request['last_created'] ?? self::$max_id; } } diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index 08fcf52cc..9ab5f731e 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -22,23 +22,32 @@ namespace Friendica\Module\Conversation; -use Friendica\BaseModule; +use Friendica\App; +use Friendica\App\Mode; use Friendica\Content\BoundariesPager; use Friendica\Content\Conversation; +use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; use Friendica\Content\Feature; use Friendica\Content\Nav; use Friendica\Content\Text\HTML; use Friendica\Content\Widget; use Friendica\Content\Widget\TrendingTags; +use Friendica\Core\Cache\Capability\ICanCache; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; -use Friendica\Database\DBA; -use Friendica\DI; -use Friendica\Model\Item; +use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Model\Post; -use Friendica\Model\User; use Friendica\Network\HTTPException; +use Friendica\Database\Database; +use Friendica\Module\Response; +use Friendica\Navigation\SystemMessages; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; -class Community extends BaseModule +class Community extends Timeline { /** * Type of the community page @@ -49,126 +58,86 @@ class Community extends BaseModule const LOCAL = 0; const GLOBAL = 1; const LOCAL_AND_GLOBAL = 2; - /** - * @} - */ protected static $page_style; - protected static $content; - protected static $accountTypeString; - protected static $accountType; - protected static $itemsPerPage; - protected static $min_id; - protected static $max_id; - protected static $item_id; + + /** @var TimelineFactory */ + protected $timeline; + /** @var Conversation */ + protected $conversation; + /** @var App\Page */ + protected $page; + /** @var SystemMessages */ + protected $systemMessages; + + public function __construct(TimelineFactory $timeline, Conversation $conversation, App\Page $page, SystemMessages $systemMessages, Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->timeline = $timeline; + $this->conversation = $conversation; + $this->page = $page; + $this->systemMessages = $systemMessages; + } protected function content(array $request = []): string { - $this->parseRequest(); + $this->parseRequest($request); $t = Renderer::getMarkupTemplate("community.tpl"); $o = Renderer::replaceMacros($t, [ '$content' => '', '$header' => '', - '$show_global_community_hint' => (self::$content == 'global') && DI::config()->get('system', 'show_global_community_hint'), - '$global_community_hint' => DI::l10n()->t("This community stream shows all public posts received by this node. They may not reflect the opinions of this node’s users.") + '$show_global_community_hint' => (self::$selectedTab == TimelineEntity::GLOBAL) && $this->config->get('system', 'show_global_community_hint'), + '$global_community_hint' => $this->l10n->t("This community stream shows all public posts received by this node. They may not reflect the opinions of this node’s users.") ]); - if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) { + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll')) { $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); - $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]); } - if (empty($_GET['mode']) || ($_GET['mode'] != 'raw')) { - $tabs = []; - - if ((DI::userSession()->isAuthenticated() || in_array(self::$page_style, [self::LOCAL_AND_GLOBAL, self::LOCAL])) && empty(DI::config()->get('system', 'singleuser'))) { - $tabs[] = [ - 'label' => DI::l10n()->t('Local Community'), - 'url' => 'community/local', - 'sel' => self::$content == 'local' ? 'active' : '', - 'title' => DI::l10n()->t('Posts from local users on this server'), - 'id' => 'community-local-tab', - 'accesskey' => 'l' - ]; - } - - if (DI::userSession()->isAuthenticated() || in_array(self::$page_style, [self::LOCAL_AND_GLOBAL, self::GLOBAL])) { - $tabs[] = [ - 'label' => DI::l10n()->t('Global Community'), - 'url' => 'community/global', - 'sel' => self::$content == 'global' ? 'active' : '', - 'title' => DI::l10n()->t('Posts from users of the whole federated network'), - 'id' => 'community-global-tab', - 'accesskey' => 'g' - ]; - } - + if (empty($request['mode']) || ($request['mode'] != 'raw')) { + $tabs = $this->getTabArray($this->timeline->getCommunities($this->session->isAuthenticated()), 'community'); $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); $o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]); Nav::setSelected('community'); - DI::page()['aside'] .= Widget::accountTypes('community/' . self::$content, self::$accountTypeString); + $this->page['aside'] .= Widget::accountTypes('community/' . self::$selectedTab, self::$accountTypeString); - if (DI::userSession()->getLocalUserId() && DI::config()->get('system', 'community_no_sharer')) { - $path = self::$content; - if (!empty($this->parameters['accounttype'])) { - $path .= '/' . $this->parameters['accounttype']; - } - $query_parameters = []; - - if (!empty($_GET['min_id'])) { - $query_parameters['min_id'] = $_GET['min_id']; - } - if (!empty($_GET['max_id'])) { - $query_parameters['max_id'] = $_GET['max_id']; - } - if (!empty($_GET['last_commented'])) { - $query_parameters['max_id'] = $_GET['last_commented']; - } - - $path_all = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : ''); - $path_no_sharer = $path . '?' . http_build_query(array_merge($query_parameters, ['no_sharer' => true])); - DI::page()['aside'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/community_sharer.tpl'), [ - '$title' => DI::l10n()->t('Own Contacts'), - '$path_all' => $path_all, - '$path_no_sharer' => $path_no_sharer, - '$no_sharer' => !empty($_REQUEST['no_sharer']), - '$all' => DI::l10n()->t('Include'), - '$no_sharer_label' => DI::l10n()->t('Hide'), - '$base' => 'community', - ]); + if ($this->session->getLocalUserId() && $this->config->get('system', 'community_no_sharer')) { + $this->page['aside'] .= $this->getNoSharerWidget('community'); } - if (Feature::isEnabled(DI::userSession()->getLocalUserId(), 'trending_tags')) { - DI::page()['aside'] .= TrendingTags::getHTML(self::$content); + if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) { + $this->page['aside'] .= TrendingTags::getHTML(self::$selectedTab); } // We need the editor here to be able to reshare an item. - if (DI::userSession()->isAuthenticated()) { - $o .= DI::conversation()->statusEditor([], 0, true); + if ($this->session->isAuthenticated()) { + $o .= $this->conversation->statusEditor([], 0, true); } } - $items = self::getItems(); + $items = $this->getCommunityItems(); - if (!DBA::isResult($items)) { - DI::sysmsg()->addNotice(DI::l10n()->t('No results.')); + if (!$this->database->isResult($items)) { + $this->systemMessages->addNotice($this->l10n->t('No results.')); return $o; } - $o .= DI::conversation()->render($items, Conversation::MODE_COMMUNITY, false, false, 'commented', DI::userSession()->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_COMMUNITY, false, false, 'commented', $this->session->getLocalUserId()); $pager = new BoundariesPager( - DI::l10n(), - DI::args()->getQueryString(), + $this->l10n, + $this->args->getQueryString(), $items[0]['commented'], $items[count($items) - 1]['commented'], self::$itemsPerPage ); - if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) { + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll')) { $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal(count($items)); @@ -183,191 +152,58 @@ class Community extends BaseModule * @throws HTTPException\BadRequestException * @throws HTTPException\ForbiddenException */ - protected function parseRequest() + protected function parseRequest($request) { - if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) { - throw new HTTPException\ForbiddenException(DI::l10n()->t('Public access denied.')); + parent::parseRequest($request); + + if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) { + throw new HTTPException\ForbiddenException($this->l10n->t('Public access denied.')); } - self::$page_style = DI::config()->get('system', 'community_page_style'); + self::$page_style = $this->config->get('system', 'community_page_style'); if (self::$page_style == self::DISABLED) { - throw new HTTPException\ForbiddenException(DI::l10n()->t('Access denied.')); + throw new HTTPException\ForbiddenException($this->l10n->t('Access denied.')); } - self::$accountTypeString = $_GET['accounttype'] ?? $this->parameters['accounttype'] ?? ''; - self::$accountType = User::getAccountTypeByString(self::$accountTypeString); - - self::$content = $this->parameters['content'] ?? ''; - if (!self::$content) { - if (!empty(DI::config()->get('system', 'singleuser'))) { + if (!self::$selectedTab) { + if (!empty($this->config->get('system', 'singleuser'))) { // On single user systems only the global page does make sense - self::$content = 'global'; + self::$selectedTab = TimelineEntity::GLOBAL; } else { // When only the global community is allowed, we use this as default - self::$content = self::$page_style == self::GLOBAL ? 'global' : 'local'; + self::$selectedTab = self::$page_style == self::GLOBAL ? TimelineEntity::GLOBAL : TimelineEntity::LOCAL; } } - if (!in_array(self::$content, ['local', 'global'])) { - throw new HTTPException\BadRequestException(DI::l10n()->t('Community option not available.')); + if (!$this->timeline->isCommunity(self::$selectedTab)) { + throw new HTTPException\BadRequestException($this->l10n->t('Community option not available.')); } // Check if we are allowed to display the content to visitors - if (!DI::userSession()->isAuthenticated()) { + if (!$this->session->isAuthenticated()) { $available = self::$page_style == self::LOCAL_AND_GLOBAL; if (!$available) { - $available = (self::$page_style == self::LOCAL) && (self::$content == 'local'); + $available = (self::$page_style == self::LOCAL) && (self::$selectedTab == TimelineEntity::LOCAL); } if (!$available) { - $available = (self::$page_style == self::GLOBAL) && (self::$content == 'global'); + $available = (self::$page_style == self::GLOBAL) && (self::$selectedTab == TimelineEntity::GLOBAL); } if (!$available) { - throw new HTTPException\ForbiddenException(DI::l10n()->t('Not available.')); + throw new HTTPException\ForbiddenException($this->l10n->t('Not available.')); } } - if (DI::mode()->isMobile()) { - self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile')); - } else { - self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network')); - } - - if (!empty($_GET['item'])) { - $item = Post::selectFirst(['parent'], ['id' => $_GET['item']]); + if (!empty($request['item'])) { + $item = Post::selectFirst(['parent'], ['id' => $request['item']]); self::$item_id = $item['parent'] ?? 0; } else { self::$item_id = 0; } - self::$min_id = $_GET['min_id'] ?? null; - self::$max_id = $_GET['last_commented'] ?? $_GET['max_id'] ?? null; - } - - /** - * Computes the displayed items. - * - * Community pages have a restriction on how many successive posts by the same author can show on any given page, - * so we may have to retrieve more content beyond the first query - * - * @return array - * @throws \Exception - */ - protected static function getItems() - { - $items = self::selectItems(self::$min_id, self::$max_id, self::$item_id, self::$itemsPerPage); - - $maxpostperauthor = (int) DI::config()->get('system', 'max_author_posts_community_page'); - if ($maxpostperauthor != 0 && self::$content == 'local') { - $count = 1; - $previousauthor = ''; - $numposts = 0; - $selected_items = []; - - while (count($selected_items) < self::$itemsPerPage && ++$count < 50 && count($items) > 0) { - foreach ($items as $item) { - if ($previousauthor == $item["author-link"]) { - ++$numposts; - } else { - $numposts = 0; - } - $previousauthor = $item["author-link"]; - - if (($numposts < $maxpostperauthor) && (count($selected_items) < self::$itemsPerPage)) { - $selected_items[] = $item; - } - } - - // If we're looking at a "previous page", the lookup continues forward in time because the list is - // sorted in chronologically decreasing order - if (isset(self::$min_id)) { - self::$min_id = $items[0]['commented']; - } else { - // In any other case, the lookup continues backwards in time - self::$max_id = $items[count($items) - 1]['commented']; - } - - $items = self::selectItems(self::$min_id, self::$max_id, self::$item_id, self::$itemsPerPage); - } - } else { - $selected_items = $items; - } - - return $selected_items; - } - - /** - * Database query for the community page - * - * @param $min_id - * @param $max_id - * @param $itemspage - * @return array - * @throws \Exception - * @TODO Move to repository/factory - */ - private static function selectItems($min_id, $max_id, $item_id, $itemspage) - { - if (self::$content == 'local') { - if (!is_null(self::$accountType)) { - $condition = ["`wall` AND `origin` AND `private` = ? AND `owner-contact-type` = ?", Item::PUBLIC, self::$accountType]; - } else { - $condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC]; - } - } elseif (self::$content == 'global') { - if (!is_null(self::$accountType)) { - $condition = ["`uid` = ? AND `private` = ? AND `owner-contact-type` = ?", 0, Item::PUBLIC, self::$accountType]; - } else { - $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC]; - } - } else { - return []; - } - - $params = ['order' => ['commented' => true], 'limit' => $itemspage]; - - if (!empty($item_id)) { - $condition[0] .= " AND `id` = ?"; - $condition[] = $item_id; - } else { - if (DI::userSession()->getLocalUserId() && !empty($_REQUEST['no_sharer'])) { - $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`)"; - $condition[] = DI::userSession()->getLocalUserId(); - } - - if (isset($max_id)) { - $condition[0] .= " AND `commented` < ?"; - $condition[] = $max_id; - } - - if (isset($min_id)) { - $condition[0] .= " AND `commented` > ?"; - $condition[] = $min_id; - - // Previous page case: we want the items closest to min_id but for that we need to reverse the query order - if (!isset($max_id)) { - $params['order']['commented'] = false; - } - } - } - - $r = Post::selectThreadForUser(DI::userSession()->getLocalUserId() ?: 0, ['uri-id', 'commented', 'author-link'], $condition, $params); - - $items = Post::toArray($r); - if (empty($items)) { - return []; - } - - // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order - if (empty($item_id) && isset($min_id) && !isset($max_id)) { - $items = array_reverse($items); - } - - return $items; + self::$max_id = $request['last_commented'] ?? self::$max_id; } } diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index ed53f7d23..b5b8a3d94 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -21,52 +21,53 @@ namespace Friendica\Module\Conversation; -use Friendica\BaseModule; +use Friendica\App; +use Friendica\App\Mode; use Friendica\Content\BoundariesPager; use Friendica\Content\Conversation; +use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; +use Friendica\Content\Feature; use Friendica\Content\GroupManager; use Friendica\Content\Nav; use Friendica\Content\Widget; use Friendica\Content\Text\HTML; +use Friendica\Content\Widget\TrendingTags; use Friendica\Core\ACL; +use Friendica\Core\Cache\Capability\ICanCache; +use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; +use Friendica\Core\L10n; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Database\DBA; +use Friendica\Database\Database; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Circle; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\Profile; -use Friendica\Model\User; use Friendica\Model\Verb; use Friendica\Module\Contact as ModuleContact; +use Friendica\Module\Response; use Friendica\Module\Security\Login; -use Friendica\Protocol\Activity; +use Friendica\Network\HTTPException; +use Friendica\Navigation\SystemMessages; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Profiler; +use Friendica\Protocol\Activity; +use Psr\Log\LoggerInterface; -class Network extends BaseModule +class Network extends Timeline { /** @var int */ private static $circleId; /** @var int */ private static $groupContactId; /** @var string */ - private static $selectedTab; - /** @var mixed */ - private static $min_id; - /** @var mixed */ - private static $max_id; - /** @var string */ - private static $accountTypeString; - /** @var int */ - private static $accountType; - /** @var string */ private static $network; - /** @var int */ - private static $itemsPerPage; /** @var string */ private static $dateFrom; /** @var string */ @@ -78,44 +79,94 @@ class Network extends BaseModule /** @var string */ protected static $order; + /** @var ICanCache */ + protected $cache; + /** @var IManageConfigValues The config */ + protected $config; + /** @var SystemMessages */ + protected $systemMessages; + /** @var App\Page */ + protected $page; + /** @var Conversation */ + protected $conversation; + /** @var IManagePersonalConfigValues */ + protected $pConfig; + /** @var Database */ + protected $database; + /** @var TimelineFactory */ + protected $timeline; + + public function __construct(TimelineFactory $timeline, SystemMessages $systemMessages, Mode $mode, Conversation $conversation, App\Page $page, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->timeline = $timeline; + $this->systemMessages = $systemMessages; + $this->conversation = $conversation; + $this->page = $page; + } + protected function content(array $request = []): string { - if (!DI::userSession()->getLocalUserId()) { + if (!$this->session->getLocalUserId()) { return Login::form(); } - $this->parseRequest($_GET); + $this->parseRequest($request); $module = 'network'; - DI::page()['aside'] .= Widget::accountTypes($module, self::$accountTypeString); - DI::page()['aside'] .= Circle::sidebarWidget($module, $module . '/circle', 'standard', self::$circleId); - DI::page()['aside'] .= GroupManager::widget($module . '/group', DI::userSession()->getLocalUserId(), self::$groupContactId); - DI::page()['aside'] .= Widget::postedByYear($module . '/archive', DI::userSession()->getLocalUserId(), false); - DI::page()['aside'] .= Widget::networks($module, !self::$groupContactId ? self::$network : ''); - DI::page()['aside'] .= Widget\SavedSearches::getHTML(DI::args()->getQueryString()); - DI::page()['aside'] .= Widget::fileAs('filed', ''); + $this->page['aside'] .= Widget::accountTypes($module, self::$accountTypeString); - $arr = ['query' => DI::args()->getQueryString()]; + $arr = ['query' => $this->args->getQueryString()]; Hook::callAll('network_content_init', $arr); $o = ''; - // Fetch a page full of parent items for this page - $params = ['limit' => self::$itemsPerPage]; - $table = 'network-thread-view'; + if ($this->timeline->isChannel(self::$selectedTab)) { + if (!in_array(self::$selectedTab, [TimelineEntity::FOLLOWERS, TimelineEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { + $this->page['aside'] .= $this->getNoSharerWidget($module); + } - $items = self::getItems($table, $params); + if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) { + $this->page['aside'] .= TrendingTags::getHTML(self::$selectedTab); + } - if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll') && ($_GET['mode'] ?? '') != 'minimal') { - $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); - $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + $items = $this->getChannelItems(); + } elseif ($this->timeline->isCommunity(self::$selectedTab)) { + if ($this->session->getLocalUserId() && $this->config->get('system', 'community_no_sharer')) { + $this->page['aside'] .= $this->getNoSharerWidget($module); + } + + if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) { + $this->page['aside'] .= TrendingTags::getHTML(self::$selectedTab); + } + + $items = $this->getCommunityItems(); + } else { + $this->page['aside'] .= Circle::sidebarWidget($module, $module . '/circle', 'standard', self::$circleId); + $this->page['aside'] .= GroupManager::widget($module . '/group', $this->session->getLocalUserId(), self::$groupContactId); + $this->page['aside'] .= Widget::postedByYear($module . '/archive', $this->session->getLocalUserId(), false); + $this->page['aside'] .= Widget::networks($module, !self::$groupContactId ? self::$network : ''); + $this->page['aside'] .= Widget\SavedSearches::getHTML($this->args->getQueryString()); + $this->page['aside'] .= Widget::fileAs('filed', ''); + + // Fetch a page full of parent items for this page + $params = ['limit' => self::$itemsPerPage]; + $table = 'network-thread-view'; + + $items = $this->getItems($table, $params); } - if (!(isset($_GET['mode']) AND ($_GET['mode'] == 'raw'))) { - $o .= self::getTabsHTML(self::$selectedTab); + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll') && ($_GET['mode'] ?? '') != 'minimal') { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]); + } - Nav::setSelected(DI::args()->get(0)); + if (!(isset($_GET['mode']) and ($_GET['mode'] == 'raw'))) { + $o .= $this->getTabsHTML(); + + Nav::setSelected($this->args->get(0)); $content = ''; @@ -128,8 +179,6 @@ class Network extends BaseModule } } - $a = DI::app(); - $default_permissions = []; if (self::$circleId) { $default_permissions['allow_gid'] = [self::$circleId]; @@ -140,7 +189,7 @@ class Network extends BaseModule $allowedCids[] = (int) self::$groupContactId; } elseif (self::$network) { $condition = [ - 'uid' => DI::userSession()->getLocalUserId(), + 'uid' => $this->session->getLocalUserId(), 'network' => self::$network, 'self' => false, 'blocked' => false, @@ -160,35 +209,35 @@ class Network extends BaseModule } $x = [ - 'lockstate' => self::$circleId || self::$groupContactId || self::$network || ACL::getLockstateForUserId($a->getLoggedInUserId()) ? 'lock' : 'unlock', - 'acl' => ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId(), true, $default_permissions), + 'lockstate' => self::$circleId || self::$groupContactId || self::$network || ACL::getLockstateForUserId($this->session->getLocalUserId()) ? 'lock' : 'unlock', + 'acl' => ACL::getFullSelectorHTML($this->page, $this->session->getLocalUserId(), true, $default_permissions), 'bang' => ((self::$circleId || self::$groupContactId || self::$network) ? '!' : ''), 'content' => $content, ]; - $o .= DI::conversation()->statusEditor($x); + $o .= $this->conversation->statusEditor($x); } if (self::$circleId) { - $circle = DBA::selectFirst('group', ['name'], ['id' => self::$circleId, 'uid' => DI::userSession()->getLocalUserId()]); + $circle = DBA::selectFirst('group', ['name'], ['id' => self::$circleId, 'uid' => $this->session->getLocalUserId()]); if (!DBA::isResult($circle)) { - DI::sysmsg()->addNotice(DI::l10n()->t('No such circle')); + $this->systemMessages->addNotice($this->l10n->t('No such circle')); } $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => DI::l10n()->t('Circle: %s', $circle['name']) + '$title' => $this->l10n->t('Circle: %s', $circle['name']) ]) . $o; } elseif (self::$groupContactId) { $contact = Contact::getById(self::$groupContactId); if (DBA::isResult($contact)) { $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('contact/list.tpl'), [ 'contacts' => [ModuleContact::getContactTemplateVars($contact)], - 'id' => DI::args()->get(0), + 'id' => $this->args->get(0), ]) . $o; } else { - DI::sysmsg()->addNotice(DI::l10n()->t('Invalid contact.')); + $this->systemMessages->addNotice($this->l10n->t('Invalid contact.')); } - } elseif (!DI::config()->get('theme', 'hide_eventlist')) { + } elseif (!$this->config->get('theme', 'hide_eventlist')) { $o .= Profile::getBirthdays(); $o .= Profile::getEventsReminderHTML(); } @@ -201,14 +250,14 @@ class Network extends BaseModule $ordering = '`commented`'; } - $o .= DI::conversation()->render($items, Conversation::MODE_NETWORK, false, false, $ordering, DI::userSession()->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $ordering, $this->session->getLocalUserId()); - if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) { + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll')) { $o .= HTML::scrollLoader(); } else { $pager = new BoundariesPager( - DI::l10n(), - DI::args()->getQueryString(), + $this->l10n, + $this->args->getQueryString(), $items[0][self::$order] ?? null, $items[count($items) - 1][self::$order] ?? null, self::$itemsPerPage @@ -243,57 +292,13 @@ class Network extends BaseModule /** * Get the network tabs menu * - * @param string $selectedTab * @return string Html of the network tabs * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - private static function getTabsHTML(string $selectedTab) + private function getTabsHTML() { - $cmd = DI::args()->getCommand(); - - // tabs - $tabs = [ - [ - 'label' => DI::l10n()->t('Latest Activity'), - 'url' => $cmd . '?' . http_build_query(['order' => 'commented']), - 'sel' => !$selectedTab || $selectedTab == 'commented' ? 'active' : '', - 'title' => DI::l10n()->t('Sort by latest activity'), - 'id' => 'activity-order-tab', - 'accesskey' => 'e', - ], - [ - 'label' => DI::l10n()->t('Latest Posts'), - 'url' => $cmd . '?' . http_build_query(['order' => 'received']), - 'sel' => $selectedTab == 'received' ? 'active' : '', - 'title' => DI::l10n()->t('Sort by post received date'), - 'id' => 'post-order-tab', - 'accesskey' => 't', - ], - [ - 'label' => DI::l10n()->t('Latest Creation'), - 'url' => $cmd . '?' . http_build_query(['order' => 'created']), - 'sel' => $selectedTab == 'created' ? 'active' : '', - 'title' => DI::l10n()->t('Sort by post creation date'), - 'id' => 'creation-order-tab', - 'accesskey' => 'q', - ], - [ - 'label' => DI::l10n()->t('Personal'), - 'url' => $cmd . '?' . http_build_query(['mention' => true]), - 'sel' => $selectedTab == 'mention' ? 'active' : '', - 'title' => DI::l10n()->t('Posts that mention or involve you'), - 'id' => 'personal-tab', - 'accesskey' => 'r', - ], - [ - 'label' => DI::l10n()->t('Starred'), - 'url' => $cmd . '?' . http_build_query(['star' => true]), - 'sel' => $selectedTab == 'star' ? 'active' : '', - 'title' => DI::l10n()->t('Favourite Posts'), - 'id' => 'starred-posts-tab', - 'accesskey' => 'm', - ], - ]; + // @todo user confgurable selection of tabs + $tabs = $this->getTabArray($this->timeline->getNetworkFeeds($this->args->getCommand()), 'network'); $arr = ['tabs' => $tabs]; Hook::callAll('network_tabs', $arr); @@ -303,36 +308,43 @@ class Network extends BaseModule return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs']]); } - protected function parseRequest(array $get) + protected function parseRequest(array $request) { + parent::parseRequest($request); + self::$circleId = (int)($this->parameters['circle_id'] ?? 0); self::$groupContactId = (int)($this->parameters['contact_id'] ?? 0); - self::$selectedTab = self::getTimelineOrderBySession(DI::userSession(), DI::pConfig()); + if (!self::$selectedTab) { + self::$selectedTab = self::getTimelineOrderBySession(DI::userSession(), $this->pConfig); + } elseif (!$this->timeline->isChannel(self::$selectedTab) && !$this->timeline->isCommunity(self::$selectedTab)) { + throw new HTTPException\BadRequestException($this->l10n->t('Network feed not available.')); + } - if (!empty($get['star'])) { - self::$selectedTab = 'star'; + + if (!empty($request['star'])) { + self::$selectedTab = TimelineEntity::STAR; self::$star = true; } else { - self::$star = self::$selectedTab == 'star'; + self::$star = self::$selectedTab == TimelineEntity::STAR; } - if (!empty($get['mention'])) { - self::$selectedTab = 'mention'; + if (!empty($request['mention'])) { + self::$selectedTab = TimelineEntity::MENTION; self::$mention = true; } else { - self::$mention = self::$selectedTab == 'mention'; + self::$mention = self::$selectedTab == TimelineEntity::MENTION; } - if (!empty($get['order'])) { - self::$selectedTab = $get['order']; - self::$order = $get['order']; + if (!empty($request['order'])) { + self::$selectedTab = $request['order']; + self::$order = $request['order']; self::$star = false; self::$mention = false; - } elseif (in_array(self::$selectedTab, ['received', 'star'])) { + } elseif (in_array(self::$selectedTab, [TimelineEntity::RECEIVED, TimelineEntity::STAR])) { self::$order = 'received'; - } elseif (self::$selectedTab == 'created') { + } elseif ((self::$selectedTab == TimelineEntity::CREATED) || $this->timeline->isChannel(self::$selectedTab)) { self::$order = 'created'; } else { self::$order = 'commented'; @@ -341,53 +353,39 @@ class Network extends BaseModule self::$selectedTab = self::$selectedTab ?? self::$order; // Prohibit combined usage of "star" and "mention" - if (self::$selectedTab == 'star') { + if (self::$selectedTab == TimelineEntity::STAR) { self::$mention = false; - } elseif (self::$selectedTab == 'mention') { + } elseif (self::$selectedTab == TimelineEntity::MENTION) { self::$star = false; } - DI::session()->set('network-tab', self::$selectedTab); - DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'network.view', 'selected_tab', self::$selectedTab); + $this->session->set('network-tab', self::$selectedTab); + $this->pConfig->set($this->session->getLocalUserId(), 'network.view', 'selected_tab', self::$selectedTab); - self::$accountTypeString = $get['accounttype'] ?? $this->parameters['accounttype'] ?? ''; - self::$accountType = User::getAccountTypeByString(self::$accountTypeString); - - self::$network = $get['nets'] ?? ''; + self::$network = $request['nets'] ?? ''; self::$dateFrom = $this->parameters['from'] ?? ''; self::$dateTo = $this->parameters['to'] ?? ''; - if (DI::mode()->isMobile()) { - self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile')); - } else { - self::$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network')); - } - - self::$min_id = $get['min_id'] ?? null; - self::$max_id = $get['max_id'] ?? null; - switch (self::$order) { case 'received': - self::$max_id = $get['last_received'] ?? self::$max_id; + self::$max_id = $request['last_received'] ?? self::$max_id; break; case 'created': - self::$max_id = $get['last_created'] ?? self::$max_id; + self::$max_id = $request['last_created'] ?? self::$max_id; break; case 'uriid': - self::$max_id = $get['last_uriid'] ?? self::$max_id; + self::$max_id = $request['last_uriid'] ?? self::$max_id; break; default: self::$order = 'commented'; - self::$max_id = $get['last_commented'] ?? self::$max_id; + self::$max_id = $request['last_commented'] ?? self::$max_id; } } - protected static function getItems(string $table, array $params, array $conditionFields = []) + protected function getItems(string $table, array $params, array $conditionFields = []) { - $conditionFields['uid'] = DI::userSession()->getLocalUserId(); + $conditionFields['uid'] = $this->session->getLocalUserId(); $conditionStrings = []; if (!is_null(self::$accountType)) { @@ -416,7 +414,7 @@ class Network extends BaseModule } elseif (self::$groupContactId) { $conditionStrings = DBA::mergeConditions($conditionStrings, ["((`contact-id` = ?) OR `uri-id` IN (SELECT `parent-uri-id` FROM `post-user-view` WHERE (`contact-id` = ? AND `gravity` = ? AND `vid` = ? AND `uid` = ?)))", - self::$groupContactId, self::$groupContactId, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), DI::userSession()->getLocalUserId()]); + self::$groupContactId, self::$groupContactId, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), $this->session->getLocalUserId()]); } // Currently only the order modes "received" and "commented" are in use @@ -480,10 +478,10 @@ class Network extends BaseModule // level which items you've seen and which you haven't. If you're looking // at the top level network page just mark everything seen. if (!self::$circleId && !self::$groupContactId && !self::$star && !self::$mention) { - $condition = ['unseen' => true, 'uid' => DI::userSession()->getLocalUserId()]; + $condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId()]; self::setItemsSeenByCondition($condition); } elseif (!empty($parents)) { - $condition = ['unseen' => true, 'uid' => DI::userSession()->getLocalUserId(), 'parent-uri-id' => $parents]; + $condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => $parents]; self::setItemsSeenByCondition($condition); } diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php new file mode 100644 index 000000000..8faa1c48a --- /dev/null +++ b/src/Module/Conversation/Timeline.php @@ -0,0 +1,474 @@ +. + * + */ + +namespace Friendica\Module\Conversation; + +use Friendica\App; +use Friendica\App\Mode; +use Friendica\BaseModule; +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Core\Cache\Capability\ICanCache; +use Friendica\Core\Cache\Enum\Duration; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; +use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Model\Contact; +use Friendica\Model\User; +use Friendica\Database\Database; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Module\Response; +use Friendica\Util\DateTimeFormat; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; + +class Timeline extends BaseModule +{ + /** @var string */ + protected static $selectedTab; + /** @var mixed */ + protected static $min_id; + /** @var mixed */ + protected static $max_id; + /** @var string */ + protected static $accountTypeString; + /** @var int */ + protected static $accountType; + /** @var int */ + protected static $item_id; + /** @var int */ + protected static $itemsPerPage; + /** @var bool */ + protected static $no_sharer; + + /** @var App\Mode $mode */ + protected $mode; + /** @var UserSession */ + protected $session; + /** @var Database */ + protected $database; + /** @var IManagePersonalConfigValues */ + protected $pConfig; + /** @var IManageConfigValues The config */ + protected $config; + /** @var ICanCache */ + protected $cache; + + public function __construct(Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, 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->mode = $mode; + $this->session = $session; + $this->database = $database; + $this->pConfig = $pConfig; + $this->config = $config; + $this->cache = $cache; + } + + /** + * Computes module parameters from the request and local configuration + * + * @throws HTTPException\BadRequestException + * @throws HTTPException\ForbiddenException + */ + protected function parseRequest(array $request) + { + self::$selectedTab = $this->parameters['content'] ?? ''; + + self::$accountTypeString = $request['accounttype'] ?? $this->parameters['accounttype'] ?? ''; + self::$accountType = User::getAccountTypeByString(self::$accountTypeString); + + if ($this->mode->isMobile()) { + self::$itemsPerPage = $this->pConfig->get( + $this->session->getLocalUserId(), + 'system', + 'itemspage_mobile_network', + $this->config->get('system', 'itemspage_network_mobile') + ); + } else { + self::$itemsPerPage = $this->pConfig->get( + $this->session->getLocalUserId(), + 'system', + 'itemspage_network', + $this->config->get('system', 'itemspage_network') + ); + } + + self::$min_id = $request['min_id'] ?? null; + self::$max_id = $request['max_id'] ?? null; + + self::$no_sharer = !empty($request['no_sharer']); + } + + protected function getNoSharerWidget(string $base): string + { + $path = self::$selectedTab; + if (!empty(self::$accountTypeString)) { + $path .= '/' . self::$accountTypeString; + } + $query_parameters = []; + + if (!empty(self::$min_id)) { + $query_parameters['min_id'] = self::$min_id; + } + if (!empty(self::$max_id)) { + $query_parameters['max_id'] = self::$max_id; + } + + $path_all = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : ''); + $path_no_sharer = $path . '?' . http_build_query(array_merge($query_parameters, ['no_sharer' => true])); + return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/community_sharer.tpl'), [ + '$title' => $this->l10n->t('Own Contacts'), + '$path_all' => $path_all, + '$path_no_sharer' => $path_no_sharer, + '$no_sharer' => self::$no_sharer, + '$all' => $this->l10n->t('Include'), + '$no_sharer_label' => $this->l10n->t('Hide'), + '$base' => $base, + ]); + } + + protected function getTabArray(Timelines $timelines, string $prefix): array + { + $tabs = []; + + foreach ($timelines as $tab) { + $tabs[] = [ + 'label' => $tab->label, + 'url' => $tab->path ?? $prefix . '/' . $tab->code, + 'sel' => self::$selectedTab == $tab->code ? 'active' : '', + 'title' => $tab->description, + 'id' => $prefix . '-' . $tab->code . '-tab', + 'accesskey' => $tab->accessKey, + ]; + } + return $tabs; + } + + /** + * Database query for the channel page + * + * @return array + * @throws \Exception + */ + protected function getChannelItems() + { + $uid = $this->session->getLocalUserId(); + + if (self::$selectedTab == TimelineEntity::WHATSHOT) { + if (!is_null(self::$accountType)) { + $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` = ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), self::$accountType]; + } else { + $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` != ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), Contact::TYPE_COMMUNITY]; + } + } elseif (self::$selectedTab == TimelineEntity::FORYOU) { + $cid = Contact::getPublicIdByUserId($uid); + $condition = [ + "(`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR + ((`comments` >= ? OR `activities` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR + (`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?) AND `notify_new_posts`)))", + $cid, $this->getMedianRelationThreadScore($cid, 4), $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $cid, + $uid, Contact::FRIEND, Contact::SHARING + ]; + } elseif (self::$selectedTab == TimelineEntity::FOLLOWERS) { + $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER]; + } elseif (self::$selectedTab == TimelineEntity::SHARERSOFSHARERS) { + $cid = Contact::getPublicIdByUserId($uid); + + // @todo Suggest posts from contacts that are followed most by our followers + $condition = [ + "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `last-interaction` > ? + AND `relation-cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `relation-thread-score` >= ?) + AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))", + DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') . ' day'), $cid, $this->getMedianRelationThreadScore($cid, 4), $cid + ]; + } elseif (self::$selectedTab == TimelineEntity::IMAGE) { + $condition = ["`media-type` & ?", 1]; + } elseif (self::$selectedTab == TimelineEntity::VIDEO) { + $condition = ["`media-type` & ?", 2]; + } elseif (self::$selectedTab == TimelineEntity::AUDIO) { + $condition = ["`media-type` & ?", 4]; + } elseif (self::$selectedTab == TimelineEntity::LANGUAGE) { + $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", $this->l10n->convertCodeForLanguageDetection(User::getLanguageCode($uid))]; + } + + if (self::$selectedTab != TimelineEntity::LANGUAGE) { + $condition = $this->addLanguageCondition($uid, $condition); + } + + $condition[0] .= " AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))"; + $condition[] = $uid; + + if ((self::$selectedTab != TimelineEntity::WHATSHOT) && !is_null(self::$accountType)) { + $condition[0] .= " AND `contact-type` = ?"; + $condition[] = self::$accountType; + } + + $params = ['order' => ['created' => true], 'limit' => self::$itemsPerPage]; + + if (!empty(self::$item_id)) { + $condition[0] .= " AND `uri-id` = ?"; + $condition[] = self::$item_id; + } else { + if (self::$no_sharer) { + $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)"; + $condition[] = $uid; + } + + if (isset(self::$max_id)) { + $condition[0] .= " AND `created` < ?"; + $condition[] = self::$max_id; + } + + if (isset(self::$min_id)) { + $condition[0] .= " AND `created` > ?"; + $condition[] = self::$min_id; + + // Previous page case: we want the items closest to min_id but for that we need to reverse the query order + if (!isset(self::$max_id)) { + $params['order']['created'] = false; + } + } + } + + $items = $this->database->selectToArray('post-engagement', ['uri-id', 'created'], $condition, $params); + if (empty($items)) { + return []; + } + + // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order + if (empty(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) { + $items = array_reverse($items); + } + + Item::update(['unseen' => false], ['unseen' => true, 'uid' => $uid, 'uri-id' => array_column($items, 'uri-id')]); + + return $items; + } + + private function addLanguageCondition(int $uid, array $condition): array + { + $conditions = []; + $languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]); + $languages = $this->l10n->convertForLanguageDetection($languages); + foreach ($languages as $language) { + $conditions[] = "JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?"; + $condition[] = $language; + } + if (!empty($conditions)) { + $condition[0] .= " AND (`language` IS NULL OR " . implode(' OR ', $conditions) . ")"; + } + return $condition; + } + + private function getMedianComments(int $uid, int $divider): int + { + $languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]); + $cache_key = 'Channel:getMedianComments:' . $divider . ':' . implode(':', $languages); + $comments = $this->cache->get($cache_key); + if (!empty($comments)) { + return $comments; + } + + $condition = ["`contact-type` != ? AND `comments` > ?", Contact::TYPE_COMMUNITY, 0]; + $condition = $this->addLanguageCondition($uid, $condition); + + $limit = $this->database->count('post-engagement', $condition) / $divider; + $post = $this->database->selectToArray('post-engagement', ['comments'], $condition, ['order' => ['comments' => true], 'limit' => [$limit, 1]]); + $comments = $post[0]['comments'] ?? 0; + if (empty($comments)) { + return 0; + } + + $this->cache->set($cache_key, $comments, Duration::HALF_HOUR); + $this->logger->debug('Calculated median comments', ['divider' => $divider, 'languages' => $languages, 'median' => $comments]); + return $comments; + } + + private function getMedianActivities(int $uid, int $divider): int + { + $languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]); + $cache_key = 'Channel:getMedianActivities:' . $divider . ':' . implode(':', $languages); + $activities = $this->cache->get($cache_key); + if (!empty($activities)) { + return $activities; + } + + $condition = ["`contact-type` != ? AND `activities` > ?", Contact::TYPE_COMMUNITY, 0]; + $condition = $this->addLanguageCondition($uid, $condition); + + $limit = $this->database->count('post-engagement', $condition) / $divider; + $post = $this->database->selectToArray('post-engagement', ['activities'], $condition, ['order' => ['activities' => true], 'limit' => [$limit, 1]]); + $activities = $post[0]['activities'] ?? 0; + if (empty($activities)) { + return 0; + } + + $this->cache->set($cache_key, $activities, Duration::HALF_HOUR); + $this->logger->debug('Calculated median activities', ['divider' => $divider, 'languages' => $languages, 'median' => $activities]); + return $activities; + } + + private function getMedianRelationThreadScore(int $cid, int $divider): int + { + $cache_key = 'Channel:getThreadScore:' . $cid . ':' . $divider; + $score = $this->cache->get($cache_key); + if (!empty($score)) { + return $score; + } + + $condition = ["`relation-cid` = ? AND `relation-thread-score` > ?", $cid, 0]; + + $limit = $this->database->count('contact-relation', $condition) / $divider; + $relation = $this->database->selectToArray('contact-relation', ['relation-thread-score'], $condition, ['order' => ['relation-thread-score' => true], 'limit' => [$limit, 1]]); + $score = $relation[0]['relation-thread-score'] ?? 0; + if (empty($score)) { + return 0; + } + + $this->cache->set($cache_key, $score, Duration::HALF_HOUR); + $this->logger->debug('Calculated median score', ['cid' => $cid, 'divider' => $divider, 'median' => $score]); + return $score; + } + + /** + * Computes the displayed items. + * + * Community pages have a restriction on how many successive posts by the same author can show on any given page, + * so we may have to retrieve more content beyond the first query + * + * @return array + * @throws \Exception + */ + protected function getCommunityItems() + { + $items = $this->selectItems(); + + $maxpostperauthor = (int) $this->config->get('system', 'max_author_posts_community_page'); + if ($maxpostperauthor != 0 && self::$selectedTab == 'local') { + $count = 1; + $previousauthor = ''; + $numposts = 0; + $selected_items = []; + + while (count($selected_items) < self::$itemsPerPage && ++$count < 50 && count($items) > 0) { + foreach ($items as $item) { + if ($previousauthor == $item["author-link"]) { + ++$numposts; + } else { + $numposts = 0; + } + $previousauthor = $item["author-link"]; + + if (($numposts < $maxpostperauthor) && (count($selected_items) < self::$itemsPerPage)) { + $selected_items[] = $item; + } + } + + // If we're looking at a "previous page", the lookup continues forward in time because the list is + // sorted in chronologically decreasing order + if (isset(self::$min_id)) { + self::$min_id = $items[0]['commented']; + } else { + // In any other case, the lookup continues backwards in time + self::$max_id = $items[count($items) - 1]['commented']; + } + + $items = $this->selectItems(); + } + } else { + $selected_items = $items; + } + + return $selected_items; + } + + /** + * Database query for the community page + * + * @return array + * @throws \Exception + * @TODO Move to repository/factory + */ + private function selectItems() + { + if (self::$selectedTab == 'local') { + if (!is_null(self::$accountType)) { + $condition = ["`wall` AND `origin` AND `private` = ? AND `owner-contact-type` = ?", Item::PUBLIC, self::$accountType]; + } else { + $condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC]; + } + } elseif (self::$selectedTab == 'global') { + if (!is_null(self::$accountType)) { + $condition = ["`uid` = ? AND `private` = ? AND `owner-contact-type` = ?", 0, Item::PUBLIC, self::$accountType]; + } else { + $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC]; + } + } else { + return []; + } + + $params = ['order' => ['commented' => true], 'limit' => self::$itemsPerPage]; + + if (!empty(self::$item_id)) { + $condition[0] .= " AND `id` = ?"; + $condition[] = self::$item_id; + } else { + if ($this->session->getLocalUserId() && self::$no_sharer) { + $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`)"; + $condition[] = $this->session->getLocalUserId(); + } + + if (isset(self::$max_id)) { + $condition[0] .= " AND `commented` < ?"; + $condition[] = self::$max_id; + } + + if (isset(self::$min_id)) { + $condition[0] .= " AND `commented` > ?"; + $condition[] = self::$min_id; + + // Previous page case: we want the items closest to min_id but for that we need to reverse the query order + if (!isset(self::$max_id)) { + $params['order']['commented'] = false; + } + } + } + + $r = Post::selectThreadForUser($this->session->getLocalUserId() ?: 0, ['uri-id', 'commented', 'author-link'], $condition, $params); + + $items = Post::toArray($r); + if (empty($items)) { + return []; + } + + // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order + if (empty(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) { + $items = array_reverse($items); + } + + return $items; + } +} diff --git a/src/Module/Update/Channel.php b/src/Module/Update/Channel.php index 099611195..333c63b2d 100644 --- a/src/Module/Update/Channel.php +++ b/src/Module/Update/Channel.php @@ -38,7 +38,13 @@ class Channel extends ChannelModule $o = ''; if (!empty($request['force'])) { - $o = $this->conversation->render($this->getItems($request), Conversation::MODE_CHANNEL, true, false, 'created', $this->session->getLocalUserId()); + if ($this->timeline->isChannel(self::$selectedTab)) { + $items = $this->getChannelItems(); + } else { + $items = $this->getCommunityItems(); + } + + $o = $this->conversation->render($items, Conversation::MODE_CHANNEL, true, false, 'created', $this->session->getLocalUserId()); } System::htmlUpdateExit($o); diff --git a/src/Module/Update/Community.php b/src/Module/Update/Community.php index 7edb2949b..88467f252 100644 --- a/src/Module/Update/Community.php +++ b/src/Module/Update/Community.php @@ -36,11 +36,11 @@ class Community extends CommunityModule { protected function rawContent(array $request = []) { - $this->parseRequest(); + $this->parseRequest($request); $o = ''; if (!empty($request['force'])) { - $o = DI::conversation()->render(self::getItems(), Conversation::MODE_COMMUNITY, true, false, 'commented', DI::userSession()->getLocalUserId()); + $o = DI::conversation()->render($this->getCommunityItems(), Conversation::MODE_COMMUNITY, true, false, 'commented', DI::userSession()->getLocalUserId()); } System::htmlUpdateExit($o); diff --git a/src/Module/Update/Network.php b/src/Module/Update/Network.php index 7c9a07f36..bf33c6c05 100644 --- a/src/Module/Update/Network.php +++ b/src/Module/Update/Network.php @@ -23,7 +23,6 @@ namespace Friendica\Module\Update; use Friendica\Content\Conversation; use Friendica\Core\System; -use Friendica\DI; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\Conversation\Network as NetworkModule; @@ -69,7 +68,7 @@ class Network extends NetworkModule $params = ['limit' => 100]; $table = 'network-item-view'; - $items = self::getItems($table, $params, $conditionFields); + $items = $this->getItems($table, $params, $conditionFields); if (self::$order === 'received') { $ordering = '`received`'; @@ -79,7 +78,7 @@ class Network extends NetworkModule $ordering = '`commented`'; } - $o = DI::conversation()->render($items, Conversation::MODE_NETWORK, $profile_uid, false, $ordering, DI::userSession()->getLocalUserId()); + $o = $this->conversation->render($items, Conversation::MODE_NETWORK, $profile_uid, false, $ordering, $this->session->getLocalUserId()); System::htmlUpdateExit($o); } diff --git a/static/routes.config.php b/static/routes.config.php index 55b40bcf3..6cd979d8e 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -672,7 +672,7 @@ return [ ], '/network' => [ - '[/]' => [Module\Conversation\Network::class, [R::GET]], + '[/{content}]' => [Module\Conversation\Network::class, [R::GET]], '/archive/{from:\d\d\d\d-\d\d-\d\d}[/{to:\d\d\d\d-\d\d-\d\d}]' => [Module\Conversation\Network::class, [R::GET]], '/group/{contact_id:\d+}' => [Module\Conversation\Network::class, [R::GET]], '/circle/{circle_id:\d+}' => [Module\Conversation\Network::class, [R::GET]], From 9f027df9090b9ece1e10b3cdc9fa50b9476c1365 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 09:30:55 +0000 Subject: [PATCH 02/20] Code standards + messages.po --- src/Content/Conversation/Factory/Timeline.php | 1 + src/Module/Conversation/Timeline.php | 7 +- view/lang/C/messages.po | 335 +++++++++--------- 3 files changed, 173 insertions(+), 170 deletions(-) diff --git a/src/Content/Conversation/Factory/Timeline.php b/src/Content/Conversation/Factory/Timeline.php index 160e55d1c..817f61713 100644 --- a/src/Content/Conversation/Factory/Timeline.php +++ b/src/Content/Conversation/Factory/Timeline.php @@ -77,6 +77,7 @@ final class Timeline extends \Friendica\BaseFactory public function getCommunities(bool $authenticated): Timelines { $page_style = $this->config->get('system', 'community_page_style'); + $tabs = []; if (($authenticated || in_array($page_style, [Community::LOCAL_AND_GLOBAL, Community::LOCAL])) && empty($this->config->get('system', 'singleuser'))) { diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php index 8faa1c48a..49fcc0623 100644 --- a/src/Module/Conversation/Timeline.php +++ b/src/Module/Conversation/Timeline.php @@ -137,7 +137,7 @@ class Timeline extends BaseModule $query_parameters['max_id'] = self::$max_id; } - $path_all = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : ''); + $path_all = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : ''); $path_no_sharer = $path . '?' . http_build_query(array_merge($query_parameters, ['no_sharer' => true])); return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/community_sharer.tpl'), [ '$title' => $this->l10n->t('Own Contacts'), @@ -185,6 +185,7 @@ class Timeline extends BaseModule } } elseif (self::$selectedTab == TimelineEntity::FORYOU) { $cid = Contact::getPublicIdByUserId($uid); + $condition = [ "(`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR ((`comments` >= ? OR `activities` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR @@ -368,9 +369,9 @@ class Timeline extends BaseModule $maxpostperauthor = (int) $this->config->get('system', 'max_author_posts_community_page'); if ($maxpostperauthor != 0 && self::$selectedTab == 'local') { - $count = 1; + $count = 1; $previousauthor = ''; - $numposts = 0; + $numposts = 0; $selected_items = []; while (count($selected_items) < self::$itemsPerPage && ++$count < 50 && count($items) > 0) { diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 247a025e8..8dbb35595 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-07 17:41+0000\n" +"POT-Creation-Date: 2023-09-09 09:30+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -292,7 +292,7 @@ msgid "Insert web link" msgstr "" #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 -#: src/Content/Conversation.php:399 src/Content/Conversation.php:1546 +#: src/Content/Conversation.php:399 src/Content/Conversation.php:1548 #: src/Module/Item/Compose.php:206 src/Module/Post/Edit.php:145 #: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:578 msgid "Please wait" @@ -449,7 +449,7 @@ msgstr "" msgid "%1$s was tagged in %2$s by %3$s" msgstr "" -#: mod/photos.php:582 src/Module/Conversation/Community.php:189 +#: mod/photos.php:582 src/Module/Conversation/Community.php:160 #: src/Module/Directory.php:48 src/Module/Profile/Photos.php:295 #: src/Module/Search/Index.php:65 msgid "Public access denied." @@ -622,12 +622,12 @@ msgstr "" msgid "Loading..." msgstr "" -#: mod/photos.php:1236 src/Content/Conversation.php:1461 +#: mod/photos.php:1236 src/Content/Conversation.php:1463 #: src/Object/Post.php:260 msgid "Select" msgstr "" -#: mod/photos.php:1237 src/Content/Conversation.php:1462 +#: mod/photos.php:1237 src/Content/Conversation.php:1464 #: src/Module/Moderation/Users/Active.php:136 #: src/Module/Moderation/Users/Blocked.php:136 #: src/Module/Moderation/Users/Index.php:151 @@ -792,7 +792,7 @@ msgstr "" msgid "All contacts" msgstr "" -#: src/BaseModule.php:433 src/Content/Conversation/Factory/Channel.php:56 +#: src/BaseModule.php:433 src/Content/Conversation/Factory/Timeline.php:62 #: src/Content/Widget.php:239 src/Core/ACL.php:195 src/Module/Contact.php:415 #: src/Module/PermissionTooltip.php:127 src/Module/PermissionTooltip.php:149 msgid "Followers" @@ -956,7 +956,7 @@ msgstr "" msgid "Enter user nickname: " msgstr "" -#: src/Console/User.php:182 src/Model/User.php:712 +#: src/Console/User.php:182 src/Model/User.php:711 #: src/Module/Api/Twitter/ContactEndpoint.php:74 #: src/Module/Moderation/Users/Active.php:71 #: src/Module/Moderation/Users/Blocked.php:71 @@ -1391,184 +1391,241 @@ msgstr "" msgid "Open Compose page" msgstr "" -#: src/Content/Conversation.php:593 +#: src/Content/Conversation.php:594 msgid "remove" msgstr "" -#: src/Content/Conversation.php:597 +#: src/Content/Conversation.php:598 msgid "Delete Selected Items" msgstr "" -#: src/Content/Conversation.php:752 src/Content/Conversation.php:755 -#: src/Content/Conversation.php:758 src/Content/Conversation.php:761 -#: src/Content/Conversation.php:764 +#: src/Content/Conversation.php:753 src/Content/Conversation.php:756 +#: src/Content/Conversation.php:759 src/Content/Conversation.php:762 +#: src/Content/Conversation.php:765 #, php-format msgid "You had been addressed (%s)." msgstr "" -#: src/Content/Conversation.php:767 +#: src/Content/Conversation.php:768 #, php-format msgid "You are following %s." msgstr "" -#: src/Content/Conversation.php:772 +#: src/Content/Conversation.php:773 #, php-format msgid "You subscribed to %s." msgstr "" -#: src/Content/Conversation.php:774 +#: src/Content/Conversation.php:775 msgid "You subscribed to one or more tags in this post." msgstr "" -#: src/Content/Conversation.php:794 +#: src/Content/Conversation.php:795 #, php-format msgid "%s reshared this." msgstr "" -#: src/Content/Conversation.php:796 +#: src/Content/Conversation.php:797 msgid "Reshared" msgstr "" -#: src/Content/Conversation.php:796 +#: src/Content/Conversation.php:797 #, php-format msgid "Reshared by %s <%s>" msgstr "" -#: src/Content/Conversation.php:799 +#: src/Content/Conversation.php:800 #, php-format msgid "%s is participating in this thread." msgstr "" -#: src/Content/Conversation.php:802 +#: src/Content/Conversation.php:803 msgid "Stored for general reasons" msgstr "" -#: src/Content/Conversation.php:805 +#: src/Content/Conversation.php:806 msgid "Global post" msgstr "" -#: src/Content/Conversation.php:808 +#: src/Content/Conversation.php:809 msgid "Sent via an relay server" msgstr "" -#: src/Content/Conversation.php:808 +#: src/Content/Conversation.php:809 #, php-format msgid "Sent via the relay server %s <%s>" msgstr "" -#: src/Content/Conversation.php:811 +#: src/Content/Conversation.php:812 msgid "Fetched" msgstr "" -#: src/Content/Conversation.php:811 +#: src/Content/Conversation.php:812 #, php-format msgid "Fetched because of %s <%s>" msgstr "" -#: src/Content/Conversation.php:814 +#: src/Content/Conversation.php:815 msgid "Stored because of a child post to complete this thread." msgstr "" -#: src/Content/Conversation.php:817 +#: src/Content/Conversation.php:818 msgid "Local delivery" msgstr "" -#: src/Content/Conversation.php:820 +#: src/Content/Conversation.php:821 msgid "Stored because of your activity (like, comment, star, ...)" msgstr "" -#: src/Content/Conversation.php:823 +#: src/Content/Conversation.php:824 msgid "Distributed" msgstr "" -#: src/Content/Conversation.php:826 +#: src/Content/Conversation.php:827 msgid "Pushed to us" msgstr "" -#: src/Content/Conversation.php:1489 src/Object/Post.php:247 +#: src/Content/Conversation.php:1491 src/Object/Post.php:247 msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1506 src/Object/Post.php:521 +#: src/Content/Conversation.php:1508 src/Object/Post.php:521 #: src/Object/Post.php:522 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1519 src/Object/Post.php:509 +#: src/Content/Conversation.php:1521 src/Object/Post.php:509 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1520 src/Object/Post.php:510 +#: src/Content/Conversation.php:1522 src/Object/Post.php:510 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1528 src/Object/Post.php:535 +#: src/Content/Conversation.php:1530 src/Object/Post.php:535 #, php-format msgid "%s from %s" msgstr "" -#: src/Content/Conversation.php:1544 +#: src/Content/Conversation.php:1546 msgid "View in context" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:53 +#: src/Content/Conversation/Factory/Timeline.php:59 msgid "For you" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:53 +#: src/Content/Conversation/Factory/Timeline.php:59 msgid "Posts from contacts you interact with and who interact with you" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:54 +#: src/Content/Conversation/Factory/Timeline.php:60 msgid "What's Hot" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:54 +#: src/Content/Conversation/Factory/Timeline.php:60 msgid "Posts with a lot of interactions" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:55 +#: src/Content/Conversation/Factory/Timeline.php:61 #, php-format msgid "Posts in %s" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:56 +#: src/Content/Conversation/Factory/Timeline.php:62 msgid "Posts from your followers that you don't follow" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:57 +#: src/Content/Conversation/Factory/Timeline.php:63 msgid "Sharers of sharers" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:57 +#: src/Content/Conversation/Factory/Timeline.php:63 msgid "Posts from accounts that are followed by accounts that you follow" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:58 +#: src/Content/Conversation/Factory/Timeline.php:64 msgid "Images" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:58 +#: src/Content/Conversation/Factory/Timeline.php:64 msgid "Posts with images" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:59 +#: src/Content/Conversation/Factory/Timeline.php:65 msgid "Audio" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:59 +#: src/Content/Conversation/Factory/Timeline.php:65 msgid "Posts with audio" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:60 +#: src/Content/Conversation/Factory/Timeline.php:66 msgid "Videos" msgstr "" -#: src/Content/Conversation/Factory/Channel.php:60 +#: src/Content/Conversation/Factory/Timeline.php:66 msgid "Posts with videos" msgstr "" +#: src/Content/Conversation/Factory/Timeline.php:83 +msgid "Local Community" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:83 +msgid "Posts from local users on this server" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:87 +msgid "Global Community" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:87 +msgid "Posts from users of the whole federated network" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:101 +msgid "Latest Activity" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:101 +msgid "Sort by latest activity" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:102 +msgid "Latest Posts" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:102 +msgid "Sort by post received date" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:103 +msgid "Latest Creation" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:103 +msgid "Sort by post creation date" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:104 +#: src/Module/Settings/Profile/Index.php:260 +msgid "Personal" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:104 +msgid "Posts that mention or involve you" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:105 src/Object/Post.php:380 +msgid "Starred" +msgstr "" + +#: src/Content/Conversation/Factory/Timeline.php:105 +msgid "Favourite Posts" +msgstr "" + #: src/Content/Feature.php:96 msgid "General Features" msgstr "" @@ -1683,7 +1740,7 @@ msgstr "" #: src/Content/GroupManager.php:152 src/Content/Nav.php:278 #: src/Content/Text/HTML.php:880 src/Content/Widget.php:537 -#: src/Model/User.php:1274 +#: src/Model/User.php:1273 msgid "Groups" msgstr "" @@ -1948,19 +2005,19 @@ msgstr "" msgid "Contacts" msgstr "" -#: src/Content/Nav.php:289 +#: src/Content/Nav.php:290 msgid "Community" msgstr "" -#: src/Content/Nav.php:289 +#: src/Content/Nav.php:290 msgid "Conversations on this and other servers" msgstr "" -#: src/Content/Nav.php:292 src/Module/Settings/Display.php:257 +#: src/Content/Nav.php:294 src/Module/Settings/Display.php:257 msgid "Channels" msgstr "" -#: src/Content/Nav.php:292 +#: src/Content/Nav.php:294 msgid "Current posts, filtered by several rules" msgstr "" @@ -3547,145 +3604,145 @@ msgstr "" msgid "Contact information and Social Networks" msgstr "" -#: src/Model/User.php:227 src/Model/User.php:1187 +#: src/Model/User.php:227 src/Model/User.php:1186 msgid "SERIOUS ERROR: Generation of security keys failed." msgstr "" -#: src/Model/User.php:621 src/Model/User.php:654 +#: src/Model/User.php:620 src/Model/User.php:653 msgid "Login failed" msgstr "" -#: src/Model/User.php:686 +#: src/Model/User.php:685 msgid "Not enough information to authenticate" msgstr "" -#: src/Model/User.php:807 +#: src/Model/User.php:806 msgid "Password can't be empty" msgstr "" -#: src/Model/User.php:849 +#: src/Model/User.php:848 msgid "Empty passwords are not allowed." msgstr "" -#: src/Model/User.php:853 +#: src/Model/User.php:852 msgid "" "The new password has been exposed in a public data dump, please choose " "another." msgstr "" -#: src/Model/User.php:857 +#: src/Model/User.php:856 msgid "The password length is limited to 72 characters." msgstr "" -#: src/Model/User.php:861 +#: src/Model/User.php:860 msgid "The password can't contain white spaces nor accentuated letters" msgstr "" -#: src/Model/User.php:1070 +#: src/Model/User.php:1069 msgid "Passwords do not match. Password unchanged." msgstr "" -#: src/Model/User.php:1077 +#: src/Model/User.php:1076 msgid "An invitation is required." msgstr "" -#: src/Model/User.php:1081 +#: src/Model/User.php:1080 msgid "Invitation could not be verified." msgstr "" -#: src/Model/User.php:1089 +#: src/Model/User.php:1088 msgid "Invalid OpenID url" msgstr "" -#: src/Model/User.php:1102 src/Security/Authentication.php:241 +#: src/Model/User.php:1101 src/Security/Authentication.php:241 msgid "" "We encountered a problem while logging in with the OpenID you provided. " "Please check the correct spelling of the ID." msgstr "" -#: src/Model/User.php:1102 src/Security/Authentication.php:241 +#: src/Model/User.php:1101 src/Security/Authentication.php:241 msgid "The error message was:" msgstr "" -#: src/Model/User.php:1108 +#: src/Model/User.php:1107 msgid "Please enter the required information." msgstr "" -#: src/Model/User.php:1122 +#: src/Model/User.php:1121 #, php-format msgid "" "system.username_min_length (%s) and system.username_max_length (%s) are " "excluding each other, swapping values." msgstr "" -#: src/Model/User.php:1129 +#: src/Model/User.php:1128 #, php-format msgid "Username should be at least %s character." msgid_plural "Username should be at least %s characters." msgstr[0] "" msgstr[1] "" -#: src/Model/User.php:1133 +#: src/Model/User.php:1132 #, php-format msgid "Username should be at most %s character." msgid_plural "Username should be at most %s characters." msgstr[0] "" msgstr[1] "" -#: src/Model/User.php:1141 +#: src/Model/User.php:1140 msgid "That doesn't appear to be your full (First Last) name." msgstr "" -#: src/Model/User.php:1146 +#: src/Model/User.php:1145 msgid "Your email domain is not among those allowed on this site." msgstr "" -#: src/Model/User.php:1150 +#: src/Model/User.php:1149 msgid "Not a valid email address." msgstr "" -#: src/Model/User.php:1153 +#: src/Model/User.php:1152 msgid "The nickname was blocked from registration by the nodes admin." msgstr "" -#: src/Model/User.php:1157 src/Model/User.php:1163 +#: src/Model/User.php:1156 src/Model/User.php:1162 msgid "Cannot use that email." msgstr "" -#: src/Model/User.php:1169 +#: src/Model/User.php:1168 msgid "Your nickname can only contain a-z, 0-9 and _." msgstr "" -#: src/Model/User.php:1177 src/Model/User.php:1234 +#: src/Model/User.php:1176 src/Model/User.php:1233 msgid "Nickname is already registered. Please choose another." msgstr "" -#: src/Model/User.php:1221 src/Model/User.php:1225 +#: src/Model/User.php:1220 src/Model/User.php:1224 msgid "An error occurred during registration. Please try again." msgstr "" -#: src/Model/User.php:1248 +#: src/Model/User.php:1247 msgid "An error occurred creating your default profile. Please try again." msgstr "" -#: src/Model/User.php:1255 +#: src/Model/User.php:1254 msgid "An error occurred creating your self contact. Please try again." msgstr "" -#: src/Model/User.php:1260 +#: src/Model/User.php:1259 msgid "Friends" msgstr "" -#: src/Model/User.php:1264 +#: src/Model/User.php:1263 msgid "" "An error occurred creating your default contact circle. Please try again." msgstr "" -#: src/Model/User.php:1308 +#: src/Model/User.php:1307 msgid "Profile Photos" msgstr "" -#: src/Model/User.php:1488 +#: src/Model/User.php:1487 #, php-format msgid "" "\n" @@ -3693,7 +3750,7 @@ msgid "" "\t\t\tthe administrator of %2$s has set up an account for you." msgstr "" -#: src/Model/User.php:1491 +#: src/Model/User.php:1490 #, php-format msgid "" "\n" @@ -3731,12 +3788,12 @@ msgid "" "\t\tThank you and welcome to %4$s." msgstr "" -#: src/Model/User.php:1524 src/Model/User.php:1631 +#: src/Model/User.php:1523 src/Model/User.php:1630 #, php-format msgid "Registration details for %s" msgstr "" -#: src/Model/User.php:1544 +#: src/Model/User.php:1543 #, php-format msgid "" "\n" @@ -3752,12 +3809,12 @@ msgid "" "\t\t" msgstr "" -#: src/Model/User.php:1563 +#: src/Model/User.php:1562 #, php-format msgid "Registration at %s" msgstr "" -#: src/Model/User.php:1587 +#: src/Model/User.php:1586 #, php-format msgid "" "\n" @@ -3766,7 +3823,7 @@ msgid "" "\t\t\t" msgstr "" -#: src/Model/User.php:1595 +#: src/Model/User.php:1594 #, php-format msgid "" "\n" @@ -5878,7 +5935,7 @@ msgid "Contact not found." msgstr "" #: src/Module/Circle.php:102 src/Module/Contact/Contacts.php:66 -#: src/Module/Conversation/Network.php:189 +#: src/Module/Conversation/Network.php:238 msgid "Invalid contact." msgstr "" @@ -6189,7 +6246,7 @@ msgstr[0] "" msgstr[1] "" #: src/Module/Contact/Follow.php:70 src/Module/Contact/Redir.php:62 -#: src/Module/Contact/Redir.php:222 src/Module/Conversation/Community.php:195 +#: src/Module/Contact/Redir.php:222 src/Module/Conversation/Community.php:166 #: src/Module/Debug/ItemBody.php:38 src/Module/Diaspora/Receive.php:57 #: src/Module/Item/Display.php:96 src/Module/Item/Feed.php:59 #: src/Module/Item/Follow.php:41 src/Module/Item/Ignore.php:41 @@ -6583,109 +6640,53 @@ msgstr "" msgid "Unable to unfollow this contact, please contact your administrator" msgstr "" -#: src/Module/Conversation/Channel.php:165 -#: src/Module/Conversation/Community.php:134 -msgid "Own Contacts" -msgstr "" - -#: src/Module/Conversation/Channel.php:169 -#: src/Module/Conversation/Community.php:138 -msgid "Include" -msgstr "" - -#: src/Module/Conversation/Channel.php:170 -#: src/Module/Conversation/Community.php:139 -msgid "Hide" -msgstr "" - -#: src/Module/Conversation/Channel.php:186 -#: src/Module/Conversation/Community.php:157 src/Module/Search/Index.php:152 +#: src/Module/Conversation/Channel.php:122 +#: src/Module/Conversation/Community.php:126 src/Module/Search/Index.php:152 #: src/Module/Search/Index.php:194 msgid "No results." msgstr "" -#: src/Module/Conversation/Channel.php:226 +#: src/Module/Conversation/Channel.php:160 msgid "Channel not available." msgstr "" -#: src/Module/Conversation/Community.php:74 +#: src/Module/Conversation/Community.php:92 msgid "" "This community stream shows all public posts received by this node. They may " "not reflect the opinions of this node’s users." msgstr "" -#: src/Module/Conversation/Community.php:87 -msgid "Local Community" -msgstr "" - -#: src/Module/Conversation/Community.php:90 -msgid "Posts from local users on this server" -msgstr "" - -#: src/Module/Conversation/Community.php:98 -msgid "Global Community" -msgstr "" - -#: src/Module/Conversation/Community.php:101 -msgid "Posts from users of the whole federated network" -msgstr "" - -#: src/Module/Conversation/Community.php:213 +#: src/Module/Conversation/Community.php:180 msgid "Community option not available." msgstr "" -#: src/Module/Conversation/Community.php:229 +#: src/Module/Conversation/Community.php:196 msgid "Not available." msgstr "" -#: src/Module/Conversation/Network.php:175 +#: src/Module/Conversation/Network.php:224 msgid "No such circle" msgstr "" -#: src/Module/Conversation/Network.php:179 +#: src/Module/Conversation/Network.php:228 #, php-format msgid "Circle: %s" msgstr "" -#: src/Module/Conversation/Network.php:257 -msgid "Latest Activity" +#: src/Module/Conversation/Network.php:322 +msgid "Network feed not available." msgstr "" -#: src/Module/Conversation/Network.php:260 -msgid "Sort by latest activity" +#: src/Module/Conversation/Timeline.php:143 +msgid "Own Contacts" msgstr "" -#: src/Module/Conversation/Network.php:265 -msgid "Latest Posts" +#: src/Module/Conversation/Timeline.php:147 +msgid "Include" msgstr "" -#: src/Module/Conversation/Network.php:268 -msgid "Sort by post received date" -msgstr "" - -#: src/Module/Conversation/Network.php:273 -msgid "Latest Creation" -msgstr "" - -#: src/Module/Conversation/Network.php:276 -msgid "Sort by post creation date" -msgstr "" - -#: src/Module/Conversation/Network.php:281 -#: src/Module/Settings/Profile/Index.php:260 -msgid "Personal" -msgstr "" - -#: src/Module/Conversation/Network.php:284 -msgid "Posts that mention or involve you" -msgstr "" - -#: src/Module/Conversation/Network.php:289 src/Object/Post.php:380 -msgid "Starred" -msgstr "" - -#: src/Module/Conversation/Network.php:292 -msgid "Favourite Posts" +#: src/Module/Conversation/Timeline.php:148 +msgid "Hide" msgstr "" #: src/Module/Credits.php:44 From 5cc1f87511dde954afa09fb42fad89827d8b3554 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 09:59:30 +0000 Subject: [PATCH 03/20] Fix tests? --- tests/src/Model/UserTest.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/src/Model/UserTest.php b/tests/src/Model/UserTest.php index 680482570..542288d71 100644 --- a/tests/src/Model/UserTest.php +++ b/tests/src/Model/UserTest.php @@ -106,7 +106,10 @@ class UserTest extends MockedTest ['uid', 'username', 'nickname'], [ 'parent-uid' => $this->parent['uid'], - 'account_removed' => false + 'verified' => true, + 'blocked' => false, + 'account_removed' => false, + 'account_expired' => false ], [])->andReturn('objectReturn')->once(); $this->dbMock->shouldReceive('isResult')->with('objectReturn')->andReturn(true)->once(); $this->dbMock->shouldReceive('toArray')->with('objectReturn', true, 0)->andReturn([$this->child])->once(); @@ -139,8 +142,11 @@ class UserTest extends MockedTest $this->dbMock->shouldReceive('select')->with('user', ['uid', 'username', 'nickname'], [ - 'uid' => $this->parent['uid'], - 'account_removed' => false + 'uid' => $this->parent['uid'], + 'verified' => true, + 'blocked' => false, + 'account_removed' => false, + 'account_expired' => false ], [])->andReturn('objectReturn')->once(); $this->dbMock->shouldReceive('isResult')->with('objectReturn')->andReturn(true)->once(); $this->dbMock->shouldReceive('toArray')->with('objectReturn', true, 0)->andReturn([$this->parent])->once(); @@ -150,7 +156,10 @@ class UserTest extends MockedTest ['uid', 'username', 'nickname'], [ 'parent-uid' => $this->parent['uid'], - 'account_removed' => false + 'verified' => true, + 'blocked' => false, + 'account_removed' => false, + 'account_expired' => false ], [])->andReturn('objectReturn')->once(); $this->dbMock->shouldReceive('isResult')->with('objectReturn')->andReturn(true)->once(); $this->dbMock->shouldReceive('toArray')->with('objectReturn', true, 0)->andReturn([$this->child, $this->manage])->once(); From dbf863fbd20ed163f94363f7b2638329aca4f45e Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 10:37:26 +0000 Subject: [PATCH 04/20] Test test --- tests/src/Model/UserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/Model/UserTest.php b/tests/src/Model/UserTest.php index 542288d71..2cab353a0 100644 --- a/tests/src/Model/UserTest.php +++ b/tests/src/Model/UserTest.php @@ -125,7 +125,7 @@ class UserTest extends MockedTest $this->parent, $this->child, $this->manage - ], $record); + ], $record, 'testIdentitiesAsParent: ' . print_r($record, true)); } public function testIdentitiesAsChild() From 2c308d300a838e128ac1a8f3b2c9f9a346432c9f Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 10:44:51 +0000 Subject: [PATCH 05/20] Test tests again --- tests/src/Model/UserTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/src/Model/UserTest.php b/tests/src/Model/UserTest.php index 2cab353a0..c80198f6e 100644 --- a/tests/src/Model/UserTest.php +++ b/tests/src/Model/UserTest.php @@ -53,10 +53,6 @@ class UserTest extends MockedTest 'uid' => 1, 'username' => 'maxmuster', 'nickname' => 'Max Muster', - 'verified' => true, - 'blocked' => false, - 'account_removed' => false, - 'account_expired' => false, ]; $this->child = [ From e9e5bb12b958a47a1af79a50a09267c734bbf469 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 10:48:43 +0000 Subject: [PATCH 06/20] Tests ... --- tests/src/Model/UserTest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/src/Model/UserTest.php b/tests/src/Model/UserTest.php index c80198f6e..89c3b9d23 100644 --- a/tests/src/Model/UserTest.php +++ b/tests/src/Model/UserTest.php @@ -59,20 +59,12 @@ class UserTest extends MockedTest 'uid' => 2, 'username' => 'johndoe', 'nickname' => 'John Doe', - 'verified' => true, - 'blocked' => false, - 'account_removed' => false, - 'account_expired' => false, ]; $this->manage = [ 'uid' => 3, 'username' => 'janesmith', 'nickname' => 'Jane Smith', - 'verified' => true, - 'blocked' => false, - 'account_removed' => false, - 'account_expired' => false, ]; } From 9d6166d7d645a9fc680341182527fee7a405ead2 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 12:00:22 +0000 Subject: [PATCH 07/20] Simplify query merge, changed update behaviour --- src/Module/Conversation/Channel.php | 8 ---- src/Module/Conversation/Community.php | 8 ---- src/Module/Conversation/Network.php | 62 ++++++++++++++------------- src/Module/Conversation/Timeline.php | 51 +++++++++++----------- src/Module/Update/Network.php | 4 +- 5 files changed, 62 insertions(+), 71 deletions(-) diff --git a/src/Module/Conversation/Channel.php b/src/Module/Conversation/Channel.php index 12d8ae035..d32d9708c 100644 --- a/src/Module/Conversation/Channel.php +++ b/src/Module/Conversation/Channel.php @@ -38,7 +38,6 @@ use Friendica\Core\L10n; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; -use Friendica\Model\Post; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; use Friendica\Database\Database; @@ -160,13 +159,6 @@ class Channel extends Timeline throw new HTTPException\BadRequestException($this->l10n->t('Channel not available.')); } - if (!empty($request['item'])) { - $item = Post::selectFirst(['parent-uri-id'], ['id' => $request['item']]); - self::$item_id = $item['parent-uri-id'] ?? 0; - } else { - self::$item_id = 0; - } - self::$max_id = $request['last_created'] ?? self::$max_id; } } diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index 9ab5f731e..e5ec2bd65 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -39,7 +39,6 @@ use Friendica\Core\L10n; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; -use Friendica\Model\Post; use Friendica\Network\HTTPException; use Friendica\Database\Database; use Friendica\Module\Response; @@ -197,13 +196,6 @@ class Community extends Timeline } } - if (!empty($request['item'])) { - $item = Post::selectFirst(['parent'], ['id' => $request['item']]); - self::$item_id = $item['parent'] ?? 0; - } else { - self::$item_id = 0; - } - self::$max_id = $request['last_commented'] ?? self::$max_id; } } diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index b5b8a3d94..cd24a7d77 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -418,37 +418,41 @@ class Network extends Timeline } // Currently only the order modes "received" and "commented" are in use - if (isset(self::$max_id)) { - switch (self::$order) { - case 'received': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` < ?", self::$max_id]); - break; - case 'commented': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` < ?", self::$max_id]); - break; - case 'created': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` < ?", self::$max_id]); - break; - case 'uriid': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` < ?", self::$max_id]); - break; + if (!empty(self::$item_uri_id)) { + $conditionStrings = DBA::mergeConditions($conditionStrings, ['uri-id' => self::$item_uri_id]); + } else { + if (isset(self::$max_id)) { + switch (self::$order) { + case 'received': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` < ?", self::$max_id]); + break; + case 'commented': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` < ?", self::$max_id]); + break; + case 'created': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` < ?", self::$max_id]); + break; + case 'uriid': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` < ?", self::$max_id]); + break; + } } - } - if (isset(self::$min_id)) { - switch (self::$order) { - case 'received': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` > ?", self::$min_id]); - break; - case 'commented': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` > ?", self::$min_id]); - break; - case 'created': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` > ?", self::$min_id]); - break; - case 'uriid': - $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` > ?", self::$min_id]); - break; + if (isset(self::$min_id)) { + switch (self::$order) { + case 'received': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` > ?", self::$min_id]); + break; + case 'commented': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` > ?", self::$min_id]); + break; + case 'created': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` > ?", self::$min_id]); + break; + case 'uriid': + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` > ?", self::$min_id]); + break; + } } } diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php index 49fcc0623..881e974bd 100644 --- a/src/Module/Conversation/Timeline.php +++ b/src/Module/Conversation/Timeline.php @@ -36,6 +36,7 @@ use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Database\Database; +use Friendica\Database\DBA; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\Response; @@ -58,6 +59,8 @@ class Timeline extends BaseModule /** @var int */ protected static $item_id; /** @var int */ + protected static $item_uri_id; + /** @var int */ protected static $itemsPerPage; /** @var bool */ protected static $no_sharer; @@ -95,6 +98,7 @@ class Timeline extends BaseModule */ protected function parseRequest(array $request) { + $this->logger->debug('Got request', $request); self::$selectedTab = $this->parameters['content'] ?? ''; self::$accountTypeString = $request['accounttype'] ?? $this->parameters['accounttype'] ?? ''; @@ -116,6 +120,15 @@ class Timeline extends BaseModule ); } + if (!empty($request['item'])) { + $item = Post::selectFirst(['parent', 'parent-uri-id'], ['id' => $request['item']]); + self::$item_id = $item['parent'] ?? 0; + self::$item_uri_id = $item['parent-uri-id'] ?? 0; + } else { + self::$item_id = 0; + self::$item_uri_id = 0; + } + self::$min_id = $request['min_id'] ?? null; self::$max_id = $request['max_id'] ?? null; @@ -219,33 +232,27 @@ class Timeline extends BaseModule $condition = $this->addLanguageCondition($uid, $condition); } - $condition[0] .= " AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))"; - $condition[] = $uid; + $condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))", $uid]); if ((self::$selectedTab != TimelineEntity::WHATSHOT) && !is_null(self::$accountType)) { - $condition[0] .= " AND `contact-type` = ?"; - $condition[] = self::$accountType; + $condition = DBA::mergeConditions($condition, ['contact-type' => self::$accountType]); } $params = ['order' => ['created' => true], 'limit' => self::$itemsPerPage]; - if (!empty(self::$item_id)) { - $condition[0] .= " AND `uri-id` = ?"; - $condition[] = self::$item_id; + if (!empty(self::$item_uri_id)) { + $condition = DBA::mergeConditions($condition, ['uri-id' => self::$item_uri_id]); } else { if (self::$no_sharer) { - $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)"; - $condition[] = $uid; + $condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)", $this->session->getLocalUserId()]); } if (isset(self::$max_id)) { - $condition[0] .= " AND `created` < ?"; - $condition[] = self::$max_id; + $condition = DBA::mergeConditions($condition, ["`created` < ?", self::$max_id]); } if (isset(self::$min_id)) { - $condition[0] .= " AND `created` > ?"; - $condition[] = self::$min_id; + $condition = DBA::mergeConditions($condition, ["`created` > ?", self::$min_id]); // Previous page case: we want the items closest to min_id but for that we need to reverse the query order if (!isset(self::$max_id)) { @@ -260,7 +267,7 @@ class Timeline extends BaseModule } // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order - if (empty(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) { + if (empty(self::$item_uri_id) && isset(self::$min_id) && !isset(self::$max_id)) { $items = array_reverse($items); } @@ -433,23 +440,19 @@ class Timeline extends BaseModule $params = ['order' => ['commented' => true], 'limit' => self::$itemsPerPage]; - if (!empty(self::$item_id)) { - $condition[0] .= " AND `id` = ?"; - $condition[] = self::$item_id; + if (!empty(self::$item_uri_id)) { + $condition = DBA::mergeConditions($condition, ['uri-id' => self::$item_uri_id]); } else { if ($this->session->getLocalUserId() && self::$no_sharer) { - $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`)"; - $condition[] = $this->session->getLocalUserId(); + $condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`)", $this->session->getLocalUserId()]); } if (isset(self::$max_id)) { - $condition[0] .= " AND `commented` < ?"; - $condition[] = self::$max_id; + $condition = DBA::mergeConditions($condition, ["`commented` < ?", self::$max_id]); } if (isset(self::$min_id)) { - $condition[0] .= " AND `commented` > ?"; - $condition[] = self::$min_id; + $condition = DBA::mergeConditions($condition, ["`commented` > ?", self::$min_id]); // Previous page case: we want the items closest to min_id but for that we need to reverse the query order if (!isset(self::$max_id)) { @@ -466,7 +469,7 @@ class Timeline extends BaseModule } // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order - if (empty(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) { + if (empty(self::$item_uri_id) && isset(self::$min_id) && !isset(self::$max_id)) { $items = array_reverse($items); } diff --git a/src/Module/Update/Network.php b/src/Module/Update/Network.php index bf33c6c05..1a2ad17ba 100644 --- a/src/Module/Update/Network.php +++ b/src/Module/Update/Network.php @@ -65,8 +65,8 @@ class Network extends NetworkModule $conditionFields['unseen'] = true; } - $params = ['limit' => 100]; - $table = 'network-item-view'; + $params = ['limit' => self::$itemsPerPage]; + $table = 'network-thread-view'; $items = $this->getItems($table, $params, $conditionFields); From 72b68abe7f5958b1790ec301d56864a2eae19c4c Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 12:01:35 +0000 Subject: [PATCH 08/20] Align array assignment --- src/Content/Nav.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Content/Nav.php b/src/Content/Nav.php index 6cc799eb6..cac03ccaa 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -40,21 +40,21 @@ use Friendica\Network\HTTPException; class Nav { private static $selected = [ - 'global' => null, - 'community' => null, - 'channel' => null, - 'network' => null, - 'home' => null, - 'profiles' => null, + 'global' => null, + 'community' => null, + 'channel' => null, + 'network' => null, + 'home' => null, + 'profiles' => null, 'introductions' => null, 'notifications' => null, - 'messages' => null, - 'directory' => null, - 'settings' => null, - 'contacts' => null, - 'delegation' => null, - 'calendar' => null, - 'register' => null + 'messages' => null, + 'directory' => null, + 'settings' => null, + 'contacts' => null, + 'delegation' => null, + 'calendar' => null, + 'register' => null ]; /** From b5f184388a5a87b5f7352b66a9691788ec0b244d Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 9 Sep 2023 12:22:40 +0000 Subject: [PATCH 09/20] Fix update after activity --- src/Content/Conversation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 289b7851c..34f3b923c 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -537,7 +537,7 @@ class Conversation if (!$update) { $live_update_div = '
' . "\r\n" - . "\r\n"; diff --git a/src/Module/BaseSettings.php b/src/Module/BaseSettings.php index 4b26a0f26..f3acb19a2 100644 --- a/src/Module/BaseSettings.php +++ b/src/Module/BaseSettings.php @@ -28,8 +28,6 @@ use Friendica\Content\Nav; use Friendica\Core\L10n; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; -use Friendica\Core\System; -use Friendica\Module\Security\Login; use Friendica\Network\HTTPException\ForbiddenException; use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index 88c7e5ed7..c2ab24297 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -280,12 +280,30 @@ class Network extends Timeline // @todo user confgurable selection of tabs $tabs = $this->getTabArray($this->timeline->getNetworkFeeds($this->args->getCommand()), 'network'); + $network_timelines = $this->pConfig->get($this->session->getLocalUserId(), 'system', 'network_timelines', []); + if (!empty($network_timelines)) { + $tabs = array_merge($tabs, $this->getTabArray($this->timeline->getChannelsForUser($this->session->getLocalUserId()), 'network', 'channel')); + $tabs = array_merge($tabs, $this->getTabArray($this->timeline->getCommunities(true), 'network', 'channel')); + } + $arr = ['tabs' => $tabs]; Hook::callAll('network_tabs', $arr); + if (!empty($network_timelines)) { + $tabs = []; + + foreach (array_keys($arr['tabs']) as $tab) { + if (in_array($tab, $network_timelines)) { + $tabs[] = $arr['tabs'][$tab]; + } + } + } else { + $tabs = $arr['tabs']; + } + $tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); - return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs']]); + return Renderer::replaceMacros($tpl, ['$tabs' => $tabs]); } protected function parseRequest(array $request) @@ -302,7 +320,6 @@ class Network extends Timeline throw new HTTPException\BadRequestException($this->l10n->t('Network feed not available.')); } - if (!empty($request['star'])) { $this->selectedTab = TimelineEntity::STAR; $this->star = true; diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php index 029e5c9a7..372f8c80a 100644 --- a/src/Module/Conversation/Timeline.php +++ b/src/Module/Conversation/Timeline.php @@ -97,7 +97,7 @@ class Timeline extends BaseModule protected function parseRequest(array $request) { $this->logger->debug('Got request', $request); - $this->selectedTab = $this->parameters['content'] ?? ''; + $this->selectedTab = $this->parameters['content'] ?? $request['channel'] ?? ''; $this->accountTypeString = $request['accounttype'] ?? $this->parameters['accounttype'] ?? ''; $this->accountType = User::getAccountTypeByString($this->accountTypeString); @@ -159,14 +159,19 @@ class Timeline extends BaseModule ]); } - protected function getTabArray(Timelines $timelines, string $prefix): array + protected function getTabArray(Timelines $timelines, string $prefix, string $parameter = ''): array { $tabs = []; foreach ($timelines as $tab) { - $tabs[] = [ + if (is_null($tab->path) && !empty($parameter)) { + $path = $prefix . '?' . http_build_query([$parameter => $tab->code]); + } else { + $path = $tab->path ?? $prefix . '/' . $tab->code; + } + $tabs[$tab->code] = [ 'label' => $tab->label, - 'url' => $tab->path ?? $prefix . '/' . $tab->code, + 'url' => $path, 'sel' => $this->selectedTab == $tab->code ? 'active' : '', 'title' => $tab->description, 'id' => $prefix . '-' . $tab->code . '-tab', diff --git a/src/Module/Settings/Display.php b/src/Module/Settings/Display.php index fa1496da6..71c4caed5 100644 --- a/src/Module/Settings/Display.php +++ b/src/Module/Settings/Display.php @@ -23,6 +23,7 @@ namespace Friendica\Module\Settings; use Friendica\App; use Friendica\Content\Text\BBCode; +use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; use Friendica\Core\L10n; @@ -51,8 +52,10 @@ class Display extends BaseSettings private $app; /** @var SystemMessages */ private $systemMessages; + /** @var TimelineFactory */ + protected $timeline; - public function __construct(SystemMessages $systemMessages, App $app, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + public function __construct(TimelineFactory $timeline, SystemMessages $systemMessages, App $app, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, 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); @@ -60,6 +63,7 @@ class Display extends BaseSettings $this->pConfig = $pConfig; $this->app = $app; $this->systemMessages = $systemMessages; + $this->timeline = $timeline; } protected function post(array $request = []) @@ -76,6 +80,7 @@ class Display extends BaseSettings $theme = !empty($request['theme']) ? trim($request['theme']) : $user['theme']; $mobile_theme = !empty($request['mobile_theme']) ? trim($request['mobile_theme']) : ''; $enable_smile = !empty($request['enable_smile']) ? intval($request['enable_smile']) : 0; + $network_timelines = !empty($request['network_timelines']) ? $request['network_timelines'] : []; $channel_languages = !empty($request['channel_languages']) ? $request['channel_languages'] : []; $first_day_of_week = !empty($request['first_day_of_week']) ? intval($request['first_day_of_week']) : 0; $calendar_default_view = !empty($request['calendar_default_view']) ? trim($request['calendar_default_view']) : 'month'; @@ -121,6 +126,7 @@ class Display extends BaseSettings $this->pConfig->set($uid, 'system', 'stay_local' , $stay_local); $this->pConfig->set($uid, 'system', 'preview_mode' , $preview_mode); + $this->pConfig->set($uid, 'system', 'network_timelines' , $network_timelines); $this->pConfig->set($uid, 'channel', 'languages' , $channel_languages); $this->pConfig->set($uid, 'calendar', 'first_day_of_week' , $first_day_of_week); @@ -218,8 +224,10 @@ class Display extends BaseSettings BBCode::PREVIEW_LARGE => $this->t('Large Image'), ]; + $network_timelines = $this->pConfig->get($uid, 'system', 'network_timelines', array_keys($this->getAvailableTimelines($uid, true))); $channel_languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]); $languages = $this->l10n->getAvailableLanguages(true); + $timelines = $this->getAvailableTimelines($uid); $first_day_of_week = $this->pConfig->get($uid, 'calendar', 'first_day_of_week', 0); $weekdays = [ @@ -254,6 +262,7 @@ class Display extends BaseSettings '$d_ctset' => $this->t('Custom Theme Settings'), '$d_cset' => $this->t('Content Settings'), '$stitle' => $this->t('Theme settings'), + '$timeline_title' => $this->t('Timelines'), '$channel_title' => $this->t('Channels'), '$calendar_title' => $this->t('Calendar'), @@ -275,10 +284,34 @@ class Display extends BaseSettings '$stay_local' => ['stay_local' , $this->t('Stay local'), $stay_local, $this->t("Don't go to a remote system when following a contact link.")], '$preview_mode' => ['preview_mode' , $this->t('Link preview mode'), $preview_mode, $this->t('Appearance of the link preview that is added to each post with a link.'), $preview_modes, false], + '$network_timelines' => ['network_timelines[]', $this->t('Timelines for the network page:'), $network_timelines, $this->t('Select all the timelines that you want to see on your network page.'), $timelines, 'multiple'], '$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all languages that you want to see in your channels.'), $languages, 'multiple'], '$first_day_of_week' => ['first_day_of_week' , $this->t('Beginning of week:') , $first_day_of_week , '', $weekdays , false], '$calendar_default_view' => ['calendar_default_view', $this->t('Default calendar view:'), $calendar_default_view, '', $calendarViews, false], ]); } + + private function getAvailableTimelines(int $uid, bool $only_network = false): array + { + $timelines = []; + + foreach ($this->timeline->getNetworkFeeds('') as $channel) { + $timelines[$channel->code] = $this->t('%s: %s', $channel->label, $channel->description); + } + + if ($only_network) { + return $timelines; + } + + foreach ($this->timeline->getChannelsForUser($uid) as $channel) { + $timelines[$channel->code] = $this->t('%s: %s', $channel->label, $channel->description); + } + + foreach ($this->timeline->getCommunities(true) as $community) { + $timelines[$community->code] = $this->t('%s: %s', $community->label, $community->description); + } + + return $timelines; + } } diff --git a/src/Module/Update/Network.php b/src/Module/Update/Network.php index 59ab0e2eb..ebd7211af 100644 --- a/src/Module/Update/Network.php +++ b/src/Module/Update/Network.php @@ -43,7 +43,15 @@ class Network extends NetworkModule System::htmlUpdateExit($o); } - $o = $this->conversation->render($this->getItems(), Conversation::MODE_NETWORK, $profile_uid, false, $this->getOrder(), $this->session->getLocalUserId()); + if ($this->timeline->isChannel($this->selectedTab)) { + $items = $this->getChannelItems(); + } elseif ($this->timeline->isCommunity($this->selectedTab)) { + $items = $this->getCommunityItems(); + } else { + $items = $this->getItems(); + } + + $o = $this->conversation->render($items, Conversation::MODE_NETWORK, $profile_uid, false, $this->getOrder(), $this->session->getLocalUserId()); System::htmlUpdateExit($o); } diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 8dbb35595..a0db9f227 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-09 09:30+0000\n" +"POT-Creation-Date: 2023-09-09 17:26+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,7 +47,7 @@ msgstr "" #: mod/item.php:452 mod/message.php:67 mod/message.php:113 mod/notes.php:45 #: mod/photos.php:152 mod/photos.php:670 src/Model/Event.php:520 #: src/Module/Attach.php:55 src/Module/BaseApi.php:99 -#: src/Module/BaseNotifications.php:98 src/Module/BaseSettings.php:52 +#: src/Module/BaseNotifications.php:98 src/Module/BaseSettings.php:50 #: src/Module/Calendar/Event/API.php:88 src/Module/Calendar/Event/Form.php:84 #: src/Module/Calendar/Export.php:82 src/Module/Calendar/Show.php:82 #: src/Module/Circle.php:41 src/Module/Circle.php:84 @@ -69,7 +69,7 @@ msgstr "" #: src/Module/Register.php:245 src/Module/Search/Directory.php:37 #: src/Module/Settings/Account.php:50 src/Module/Settings/Account.php:408 #: src/Module/Settings/Delegation.php:41 src/Module/Settings/Delegation.php:71 -#: src/Module/Settings/Display.php:69 src/Module/Settings/Display.php:154 +#: src/Module/Settings/Display.php:74 src/Module/Settings/Display.php:161 #: src/Module/Settings/Profile/Photo/Crop.php:165 #: src/Module/Settings/Profile/Photo/Index.php:111 #: src/Module/Settings/RemoveMe.php:117 src/Module/Settings/UserExport.php:80 @@ -292,7 +292,7 @@ msgid "Insert web link" msgstr "" #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 -#: src/Content/Conversation.php:399 src/Content/Conversation.php:1548 +#: src/Content/Conversation.php:399 src/Content/Conversation.php:1549 #: src/Module/Item/Compose.php:206 src/Module/Post/Edit.php:145 #: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:578 msgid "Please wait" @@ -415,7 +415,7 @@ msgstr "" msgid "Upload New Photos" msgstr "" -#: mod/photos.php:121 src/Module/BaseSettings.php:74 +#: mod/photos.php:121 src/Module/BaseSettings.php:72 #: src/Module/Profile/Photos.php:363 msgid "everybody" msgstr "" @@ -449,7 +449,7 @@ msgstr "" msgid "%1$s was tagged in %2$s by %3$s" msgstr "" -#: mod/photos.php:582 src/Module/Conversation/Community.php:160 +#: mod/photos.php:582 src/Module/Conversation/Community.php:159 #: src/Module/Directory.php:48 src/Module/Profile/Photos.php:295 #: src/Module/Search/Index.php:65 msgid "Public access denied." @@ -622,12 +622,12 @@ msgstr "" msgid "Loading..." msgstr "" -#: mod/photos.php:1236 src/Content/Conversation.php:1463 +#: mod/photos.php:1236 src/Content/Conversation.php:1464 #: src/Object/Post.php:260 msgid "Select" msgstr "" -#: mod/photos.php:1237 src/Content/Conversation.php:1464 +#: mod/photos.php:1237 src/Content/Conversation.php:1465 #: src/Module/Moderation/Users/Active.php:136 #: src/Module/Moderation/Users/Blocked.php:136 #: src/Module/Moderation/Users/Index.php:151 @@ -1391,124 +1391,124 @@ msgstr "" msgid "Open Compose page" msgstr "" -#: src/Content/Conversation.php:594 +#: src/Content/Conversation.php:595 msgid "remove" msgstr "" -#: src/Content/Conversation.php:598 +#: src/Content/Conversation.php:599 msgid "Delete Selected Items" msgstr "" -#: src/Content/Conversation.php:753 src/Content/Conversation.php:756 -#: src/Content/Conversation.php:759 src/Content/Conversation.php:762 -#: src/Content/Conversation.php:765 +#: src/Content/Conversation.php:754 src/Content/Conversation.php:757 +#: src/Content/Conversation.php:760 src/Content/Conversation.php:763 +#: src/Content/Conversation.php:766 #, php-format msgid "You had been addressed (%s)." msgstr "" -#: src/Content/Conversation.php:768 +#: src/Content/Conversation.php:769 #, php-format msgid "You are following %s." msgstr "" -#: src/Content/Conversation.php:773 +#: src/Content/Conversation.php:774 #, php-format msgid "You subscribed to %s." msgstr "" -#: src/Content/Conversation.php:775 +#: src/Content/Conversation.php:776 msgid "You subscribed to one or more tags in this post." msgstr "" -#: src/Content/Conversation.php:795 +#: src/Content/Conversation.php:796 #, php-format msgid "%s reshared this." msgstr "" -#: src/Content/Conversation.php:797 +#: src/Content/Conversation.php:798 msgid "Reshared" msgstr "" -#: src/Content/Conversation.php:797 +#: src/Content/Conversation.php:798 #, php-format msgid "Reshared by %s <%s>" msgstr "" -#: src/Content/Conversation.php:800 +#: src/Content/Conversation.php:801 #, php-format msgid "%s is participating in this thread." msgstr "" -#: src/Content/Conversation.php:803 +#: src/Content/Conversation.php:804 msgid "Stored for general reasons" msgstr "" -#: src/Content/Conversation.php:806 +#: src/Content/Conversation.php:807 msgid "Global post" msgstr "" -#: src/Content/Conversation.php:809 +#: src/Content/Conversation.php:810 msgid "Sent via an relay server" msgstr "" -#: src/Content/Conversation.php:809 +#: src/Content/Conversation.php:810 #, php-format msgid "Sent via the relay server %s <%s>" msgstr "" -#: src/Content/Conversation.php:812 +#: src/Content/Conversation.php:813 msgid "Fetched" msgstr "" -#: src/Content/Conversation.php:812 +#: src/Content/Conversation.php:813 #, php-format msgid "Fetched because of %s <%s>" msgstr "" -#: src/Content/Conversation.php:815 +#: src/Content/Conversation.php:816 msgid "Stored because of a child post to complete this thread." msgstr "" -#: src/Content/Conversation.php:818 +#: src/Content/Conversation.php:819 msgid "Local delivery" msgstr "" -#: src/Content/Conversation.php:821 +#: src/Content/Conversation.php:822 msgid "Stored because of your activity (like, comment, star, ...)" msgstr "" -#: src/Content/Conversation.php:824 +#: src/Content/Conversation.php:825 msgid "Distributed" msgstr "" -#: src/Content/Conversation.php:827 +#: src/Content/Conversation.php:828 msgid "Pushed to us" msgstr "" -#: src/Content/Conversation.php:1491 src/Object/Post.php:247 +#: src/Content/Conversation.php:1492 src/Object/Post.php:247 msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1508 src/Object/Post.php:521 +#: src/Content/Conversation.php:1509 src/Object/Post.php:521 #: src/Object/Post.php:522 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1521 src/Object/Post.php:509 +#: src/Content/Conversation.php:1522 src/Object/Post.php:509 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1522 src/Object/Post.php:510 +#: src/Content/Conversation.php:1523 src/Object/Post.php:510 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1530 src/Object/Post.php:535 +#: src/Content/Conversation.php:1531 src/Object/Post.php:535 #, php-format msgid "%s from %s" msgstr "" -#: src/Content/Conversation.php:1546 +#: src/Content/Conversation.php:1547 msgid "View in context" msgstr "" @@ -1569,60 +1569,60 @@ msgstr "" msgid "Posts with videos" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:83 +#: src/Content/Conversation/Factory/Timeline.php:84 msgid "Local Community" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:83 +#: src/Content/Conversation/Factory/Timeline.php:84 msgid "Posts from local users on this server" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:87 +#: src/Content/Conversation/Factory/Timeline.php:88 msgid "Global Community" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:87 +#: src/Content/Conversation/Factory/Timeline.php:88 msgid "Posts from users of the whole federated network" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:101 +#: src/Content/Conversation/Factory/Timeline.php:102 msgid "Latest Activity" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:101 +#: src/Content/Conversation/Factory/Timeline.php:102 msgid "Sort by latest activity" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:102 +#: src/Content/Conversation/Factory/Timeline.php:103 msgid "Latest Posts" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:102 +#: src/Content/Conversation/Factory/Timeline.php:103 msgid "Sort by post received date" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:103 +#: src/Content/Conversation/Factory/Timeline.php:104 msgid "Latest Creation" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:103 +#: src/Content/Conversation/Factory/Timeline.php:104 msgid "Sort by post creation date" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:104 +#: src/Content/Conversation/Factory/Timeline.php:105 #: src/Module/Settings/Profile/Index.php:260 msgid "Personal" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:104 +#: src/Content/Conversation/Factory/Timeline.php:105 msgid "Posts that mention or involve you" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:105 src/Object/Post.php:380 +#: src/Content/Conversation/Factory/Timeline.php:106 src/Object/Post.php:380 msgid "Starred" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:105 +#: src/Content/Conversation/Factory/Timeline.php:106 msgid "Favourite Posts" msgstr "" @@ -1894,7 +1894,7 @@ msgid "Conversations you started" msgstr "" #: src/Content/Nav.php:230 src/Module/BaseProfile.php:49 -#: src/Module/BaseSettings.php:100 src/Module/Contact.php:504 +#: src/Module/BaseSettings.php:98 src/Module/Contact.php:504 #: src/Module/Contact/Profile.php:413 src/Module/Profile/Profile.php:268 #: src/Module/Welcome.php:57 view/theme/frio/theme.php:230 msgid "Profile" @@ -1926,7 +1926,7 @@ msgstr "" #: src/Content/Nav.php:233 src/Content/Nav.php:295 #: src/Module/BaseProfile.php:85 src/Module/BaseProfile.php:88 #: src/Module/BaseProfile.php:96 src/Module/BaseProfile.php:99 -#: src/Module/Settings/Display.php:258 view/theme/frio/theme.php:236 +#: src/Module/Settings/Display.php:268 view/theme/frio/theme.php:236 #: view/theme/frio/theme.php:240 msgid "Calendar" msgstr "" @@ -2013,7 +2013,7 @@ msgstr "" msgid "Conversations on this and other servers" msgstr "" -#: src/Content/Nav.php:294 src/Module/Settings/Display.php:257 +#: src/Content/Nav.php:294 src/Module/Settings/Display.php:267 msgid "Channels" msgstr "" @@ -2106,7 +2106,7 @@ msgid "Manage other pages" msgstr "" #: src/Content/Nav.php:329 src/Module/Admin/Addons/Details.php:114 -#: src/Module/Admin/Themes/Details.php:93 src/Module/BaseSettings.php:177 +#: src/Module/Admin/Themes/Details.php:93 src/Module/BaseSettings.php:175 #: src/Module/Welcome.php:52 view/theme/frio/theme.php:242 msgid "Settings" msgstr "" @@ -2824,37 +2824,37 @@ msgid "Could not connect to database." msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:430 -#: src/Module/Settings/Display.php:227 +#: src/Module/Settings/Display.php:236 msgid "Monday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:431 -#: src/Module/Settings/Display.php:228 +#: src/Module/Settings/Display.php:237 msgid "Tuesday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:432 -#: src/Module/Settings/Display.php:229 +#: src/Module/Settings/Display.php:238 msgid "Wednesday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:433 -#: src/Module/Settings/Display.php:230 +#: src/Module/Settings/Display.php:239 msgid "Thursday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:434 -#: src/Module/Settings/Display.php:231 +#: src/Module/Settings/Display.php:240 msgid "Friday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:435 -#: src/Module/Settings/Display.php:232 +#: src/Module/Settings/Display.php:241 msgid "Saturday" msgstr "" #: src/Core/L10n.php:476 src/Model/Event.php:429 -#: src/Module/Settings/Display.php:226 +#: src/Module/Settings/Display.php:235 msgid "Sunday" msgstr "" @@ -3299,17 +3299,17 @@ msgid "today" msgstr "" #: src/Model/Event.php:463 src/Module/Calendar/Show.php:129 -#: src/Module/Settings/Display.php:237 src/Util/Temporal.php:353 +#: src/Module/Settings/Display.php:246 src/Util/Temporal.php:353 msgid "month" msgstr "" #: src/Model/Event.php:464 src/Module/Calendar/Show.php:130 -#: src/Module/Settings/Display.php:238 src/Util/Temporal.php:354 +#: src/Module/Settings/Display.php:247 src/Util/Temporal.php:354 msgid "week" msgstr "" #: src/Model/Event.php:465 src/Module/Calendar/Show.php:131 -#: src/Module/Settings/Display.php:239 src/Util/Temporal.php:355 +#: src/Module/Settings/Display.php:248 src/Util/Temporal.php:355 msgid "day" msgstr "" @@ -3898,7 +3898,7 @@ msgid "Administration" msgstr "" #: src/Module/Admin/Addons/Details.php:112 src/Module/Admin/Addons/Index.php:68 -#: src/Module/BaseAdmin.php:92 src/Module/BaseSettings.php:134 +#: src/Module/BaseAdmin.php:92 src/Module/BaseSettings.php:132 msgid "Addons" msgstr "" @@ -3932,7 +3932,7 @@ msgstr "" #: src/Module/Settings/Account.php:561 src/Module/Settings/Addons.php:78 #: src/Module/Settings/Connectors.php:160 #: src/Module/Settings/Connectors.php:246 -#: src/Module/Settings/Delegation.php:171 src/Module/Settings/Display.php:252 +#: src/Module/Settings/Delegation.php:171 src/Module/Settings/Display.php:261 #: src/Module/Settings/Features.php:76 msgid "Save Settings" msgstr "" @@ -4292,11 +4292,11 @@ msgstr "" msgid "%s is no valid input for maximum image size" msgstr "" -#: src/Module/Admin/Site.php:313 src/Module/Settings/Display.php:172 +#: src/Module/Admin/Site.php:313 src/Module/Settings/Display.php:179 msgid "No special theme for mobile devices" msgstr "" -#: src/Module/Admin/Site.php:330 src/Module/Settings/Display.php:182 +#: src/Module/Admin/Site.php:330 src/Module/Settings/Display.php:189 #, php-format msgid "%s - (Experimental)" msgstr "" @@ -5583,7 +5583,7 @@ msgstr "" msgid "Configuration" msgstr "" -#: src/Module/BaseAdmin.php:94 src/Module/BaseSettings.php:112 +#: src/Module/BaseAdmin.php:94 src/Module/BaseSettings.php:110 msgid "Additional features" msgstr "" @@ -5748,40 +5748,40 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: src/Module/BaseSettings.php:80 +#: src/Module/BaseSettings.php:78 msgid "Account" msgstr "" -#: src/Module/BaseSettings.php:87 src/Module/Security/TwoFactor/Verify.php:96 +#: src/Module/BaseSettings.php:85 src/Module/Security/TwoFactor/Verify.php:96 #: src/Module/Settings/TwoFactor/Index.php:117 msgid "Two-factor authentication" msgstr "" -#: src/Module/BaseSettings.php:120 +#: src/Module/BaseSettings.php:118 msgid "Display" msgstr "" -#: src/Module/BaseSettings.php:127 src/Module/Settings/Connectors.php:204 +#: src/Module/BaseSettings.php:125 src/Module/Settings/Connectors.php:204 msgid "Social Networks" msgstr "" -#: src/Module/BaseSettings.php:141 src/Module/Settings/Delegation.php:172 +#: src/Module/BaseSettings.php:139 src/Module/Settings/Delegation.php:172 msgid "Manage Accounts" msgstr "" -#: src/Module/BaseSettings.php:148 +#: src/Module/BaseSettings.php:146 msgid "Connected apps" msgstr "" -#: src/Module/BaseSettings.php:155 +#: src/Module/BaseSettings.php:153 msgid "Remote servers" msgstr "" -#: src/Module/BaseSettings.php:162 src/Module/Settings/UserExport.php:98 +#: src/Module/BaseSettings.php:160 src/Module/Settings/UserExport.php:98 msgid "Export personal data" msgstr "" -#: src/Module/BaseSettings.php:169 +#: src/Module/BaseSettings.php:167 msgid "Remove account" msgstr "" @@ -5901,7 +5901,7 @@ msgstr "" msgid "Create New Event" msgstr "" -#: src/Module/Calendar/Show.php:132 src/Module/Settings/Display.php:240 +#: src/Module/Calendar/Show.php:132 src/Module/Settings/Display.php:249 msgid "list" msgstr "" @@ -5935,7 +5935,7 @@ msgid "Contact not found." msgstr "" #: src/Module/Circle.php:102 src/Module/Contact/Contacts.php:66 -#: src/Module/Conversation/Network.php:238 +#: src/Module/Conversation/Network.php:235 msgid "Invalid contact." msgstr "" @@ -6246,7 +6246,7 @@ msgstr[0] "" msgstr[1] "" #: src/Module/Contact/Follow.php:70 src/Module/Contact/Redir.php:62 -#: src/Module/Contact/Redir.php:222 src/Module/Conversation/Community.php:166 +#: src/Module/Contact/Redir.php:222 src/Module/Conversation/Community.php:165 #: src/Module/Debug/ItemBody.php:38 src/Module/Diaspora/Receive.php:57 #: src/Module/Item/Display.php:96 src/Module/Item/Feed.php:59 #: src/Module/Item/Follow.php:41 src/Module/Item/Ignore.php:41 @@ -6640,52 +6640,52 @@ msgstr "" msgid "Unable to unfollow this contact, please contact your administrator" msgstr "" -#: src/Module/Conversation/Channel.php:122 -#: src/Module/Conversation/Community.php:126 src/Module/Search/Index.php:152 +#: src/Module/Conversation/Channel.php:121 +#: src/Module/Conversation/Community.php:125 src/Module/Search/Index.php:152 #: src/Module/Search/Index.php:194 msgid "No results." msgstr "" -#: src/Module/Conversation/Channel.php:160 +#: src/Module/Conversation/Channel.php:159 msgid "Channel not available." msgstr "" -#: src/Module/Conversation/Community.php:92 +#: src/Module/Conversation/Community.php:91 msgid "" "This community stream shows all public posts received by this node. They may " "not reflect the opinions of this node’s users." msgstr "" -#: src/Module/Conversation/Community.php:180 +#: src/Module/Conversation/Community.php:179 msgid "Community option not available." msgstr "" -#: src/Module/Conversation/Community.php:196 +#: src/Module/Conversation/Community.php:195 msgid "Not available." msgstr "" -#: src/Module/Conversation/Network.php:224 +#: src/Module/Conversation/Network.php:221 msgid "No such circle" msgstr "" -#: src/Module/Conversation/Network.php:228 +#: src/Module/Conversation/Network.php:225 #, php-format msgid "Circle: %s" msgstr "" -#: src/Module/Conversation/Network.php:322 +#: src/Module/Conversation/Network.php:320 msgid "Network feed not available." msgstr "" -#: src/Module/Conversation/Timeline.php:143 +#: src/Module/Conversation/Timeline.php:152 msgid "Own Contacts" msgstr "" -#: src/Module/Conversation/Timeline.php:147 +#: src/Module/Conversation/Timeline.php:156 msgid "Include" msgstr "" -#: src/Module/Conversation/Timeline.php:148 +#: src/Module/Conversation/Timeline.php:157 msgid "Hide" msgstr "" @@ -10087,153 +10087,171 @@ msgstr "" msgid "No entries." msgstr "" -#: src/Module/Settings/Display.php:140 +#: src/Module/Settings/Display.php:147 msgid "The theme you chose isn't available." msgstr "" -#: src/Module/Settings/Display.php:180 +#: src/Module/Settings/Display.php:187 #, php-format msgid "%s - (Unsupported)" msgstr "" -#: src/Module/Settings/Display.php:215 +#: src/Module/Settings/Display.php:222 msgid "No preview" msgstr "" -#: src/Module/Settings/Display.php:216 +#: src/Module/Settings/Display.php:223 msgid "No image" msgstr "" -#: src/Module/Settings/Display.php:217 +#: src/Module/Settings/Display.php:224 msgid "Small Image" msgstr "" -#: src/Module/Settings/Display.php:218 +#: src/Module/Settings/Display.php:225 msgid "Large Image" msgstr "" -#: src/Module/Settings/Display.php:251 +#: src/Module/Settings/Display.php:260 msgid "Display Settings" msgstr "" -#: src/Module/Settings/Display.php:253 +#: src/Module/Settings/Display.php:262 msgid "General Theme Settings" msgstr "" -#: src/Module/Settings/Display.php:254 +#: src/Module/Settings/Display.php:263 msgid "Custom Theme Settings" msgstr "" -#: src/Module/Settings/Display.php:255 +#: src/Module/Settings/Display.php:264 msgid "Content Settings" msgstr "" -#: src/Module/Settings/Display.php:256 view/theme/duepuntozero/config.php:86 +#: src/Module/Settings/Display.php:265 view/theme/duepuntozero/config.php:86 #: view/theme/frio/config.php:172 view/theme/quattro/config.php:88 #: view/theme/vier/config.php:136 msgid "Theme settings" msgstr "" -#: src/Module/Settings/Display.php:263 +#: src/Module/Settings/Display.php:266 +msgid "Timelines" +msgstr "" + +#: src/Module/Settings/Display.php:273 msgid "Display Theme:" msgstr "" -#: src/Module/Settings/Display.php:264 +#: src/Module/Settings/Display.php:274 msgid "Mobile Theme:" msgstr "" -#: src/Module/Settings/Display.php:267 +#: src/Module/Settings/Display.php:277 msgid "Number of items to display per page:" msgstr "" -#: src/Module/Settings/Display.php:267 src/Module/Settings/Display.php:268 +#: src/Module/Settings/Display.php:277 src/Module/Settings/Display.php:278 msgid "Maximum of 100 items" msgstr "" -#: src/Module/Settings/Display.php:268 +#: src/Module/Settings/Display.php:278 msgid "Number of items to display per page when viewed from mobile device:" msgstr "" -#: src/Module/Settings/Display.php:269 +#: src/Module/Settings/Display.php:279 msgid "Update browser every xx seconds" msgstr "" -#: src/Module/Settings/Display.php:269 +#: src/Module/Settings/Display.php:279 msgid "Minimum of 10 seconds. Enter -1 to disable it." msgstr "" -#: src/Module/Settings/Display.php:270 +#: src/Module/Settings/Display.php:280 msgid "Display emoticons" msgstr "" -#: src/Module/Settings/Display.php:270 +#: src/Module/Settings/Display.php:280 msgid "When enabled, emoticons are replaced with matching symbols." msgstr "" -#: src/Module/Settings/Display.php:271 +#: src/Module/Settings/Display.php:281 msgid "Infinite scroll" msgstr "" -#: src/Module/Settings/Display.php:271 +#: src/Module/Settings/Display.php:281 msgid "Automatic fetch new items when reaching the page end." msgstr "" -#: src/Module/Settings/Display.php:272 +#: src/Module/Settings/Display.php:282 msgid "Enable Smart Threading" msgstr "" -#: src/Module/Settings/Display.php:272 +#: src/Module/Settings/Display.php:282 msgid "Enable the automatic suppression of extraneous thread indentation." msgstr "" -#: src/Module/Settings/Display.php:273 +#: src/Module/Settings/Display.php:283 msgid "Display the Dislike feature" msgstr "" -#: src/Module/Settings/Display.php:273 +#: src/Module/Settings/Display.php:283 msgid "Display the Dislike button and dislike reactions on posts and comments." msgstr "" -#: src/Module/Settings/Display.php:274 +#: src/Module/Settings/Display.php:284 msgid "Display the resharer" msgstr "" -#: src/Module/Settings/Display.php:274 +#: src/Module/Settings/Display.php:284 msgid "Display the first resharer as icon and text on a reshared item." msgstr "" -#: src/Module/Settings/Display.php:275 +#: src/Module/Settings/Display.php:285 msgid "Stay local" msgstr "" -#: src/Module/Settings/Display.php:275 +#: src/Module/Settings/Display.php:285 msgid "Don't go to a remote system when following a contact link." msgstr "" -#: src/Module/Settings/Display.php:276 +#: src/Module/Settings/Display.php:286 msgid "Link preview mode" msgstr "" -#: src/Module/Settings/Display.php:276 +#: src/Module/Settings/Display.php:286 msgid "Appearance of the link preview that is added to each post with a link." msgstr "" -#: src/Module/Settings/Display.php:278 +#: src/Module/Settings/Display.php:288 +msgid "Timelines for the network page:" +msgstr "" + +#: src/Module/Settings/Display.php:288 +msgid "Select all the timelines that you want to see on your network page." +msgstr "" + +#: src/Module/Settings/Display.php:289 msgid "Channel languages:" msgstr "" -#: src/Module/Settings/Display.php:278 +#: src/Module/Settings/Display.php:289 msgid "Select all languages that you want to see in your channels." msgstr "" -#: src/Module/Settings/Display.php:280 +#: src/Module/Settings/Display.php:291 msgid "Beginning of week:" msgstr "" -#: src/Module/Settings/Display.php:281 +#: src/Module/Settings/Display.php:292 msgid "Default calendar view:" msgstr "" +#: src/Module/Settings/Display.php:301 src/Module/Settings/Display.php:305 +#: src/Module/Settings/Display.php:309 +#, php-format +msgid "%s: %s" +msgstr "" + #: src/Module/Settings/Features.php:74 msgid "Additional Features" msgstr "" diff --git a/view/templates/settings/display.tpl b/view/templates/settings/display.tpl index bc3107e6a..868acb38a 100644 --- a/view/templates/settings/display.tpl +++ b/view/templates/settings/display.tpl @@ -21,6 +21,9 @@ {{include file="field_checkbox.tpl" field=$stay_local}} {{include file="field_select.tpl" field=$preview_mode}} +

{{$timeline_title}}

+ {{include file="field_select.tpl" field=$network_timelines}} +

{{$channel_title}}

{{include file="field_select.tpl" field=$channel_languages}} diff --git a/view/theme/frio/templates/settings/display.tpl b/view/theme/frio/templates/settings/display.tpl index f76dd7a0c..f361fe953 100644 --- a/view/theme/frio/templates/settings/display.tpl +++ b/view/theme/frio/templates/settings/display.tpl @@ -74,6 +74,24 @@ +
+ +
+
+ {{include file="field_select.tpl" field=$network_timelines}} +
+ +
+
+