diff --git a/database.sql b/database.sql index 93cc138967..ec7adc5c7a 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2023.09-rc (Giant Rhubarb) --- DB_UPDATE_VERSION 1535 +-- DB_UPDATE_VERSION 1536 -- ------------------------------------------ @@ -492,6 +492,25 @@ CREATE TABLE IF NOT EXISTS `cache` ( INDEX `k_expires` (`k`,`expires`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Stores temporary data'; +-- +-- TABLE channel +-- +CREATE TABLE IF NOT EXISTS `channel` ( + `id` int unsigned NOT NULL auto_increment COMMENT '', + `uid` mediumint unsigned NOT NULL COMMENT 'User id', + `label` varchar(64) NOT NULL COMMENT 'Channel label', + `description` varchar(64) COMMENT 'Channel description', + `circle` int COMMENT 'Circle or channel that this channel is based on', + `access-key` varchar(1) COMMENT 'Access key', + `include-tags` varchar(255) COMMENT 'Comma separated list of tags that will be included in the channel', + `exclude-tags` varchar(255) COMMENT 'Comma separated list of tags that aren\'t allowed in the channel', + `full-text-search` varchar(255) COMMENT 'Full text search pattern, see https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode', + `media-type` smallint unsigned COMMENT 'Filtered media types', + PRIMARY KEY(`id`), + INDEX `uid` (`uid`), + FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User defined Channels'; + -- -- TABLE config -- @@ -1309,6 +1328,7 @@ CREATE TABLE IF NOT EXISTS `post-engagement` ( `contact-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Person, organisation, news, community, relay', `media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio', `language` varbinary(128) COMMENT 'Language information about this post', + `searchtext` mediumtext COMMENT 'Simplified text for the full text search', `created` datetime COMMENT '', `restricted` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is either unlisted or not from a federated network', `comments` mediumint unsigned COMMENT 'Number of comments', @@ -1316,6 +1336,7 @@ CREATE TABLE IF NOT EXISTS `post-engagement` ( PRIMARY KEY(`uri-id`), INDEX `owner-id` (`owner-id`), INDEX `created` (`created`), + FULLTEXT INDEX `searchtext` (`searchtext`), FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Engagement data per post'; @@ -2070,6 +2091,7 @@ CREATE VIEW `post-user-view` AS SELECT `author`.`blocked` AS `author-blocked`, `author`.`hidden` AS `author-hidden`, `author`.`updated` AS `author-updated`, + `author`.`contact-type` AS `author-contact-type`, `author`.`gsid` AS `author-gsid`, `author`.`baseurl` AS `author-baseurl`, `post-user`.`owner-id` AS `owner-id`, @@ -2254,6 +2276,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT `author`.`blocked` AS `author-blocked`, `author`.`hidden` AS `author-hidden`, `author`.`updated` AS `author-updated`, + `author`.`contact-type` AS `author-contact-type`, `author`.`gsid` AS `author-gsid`, `post-thread-user`.`owner-id` AS `owner-id`, `owner`.`uri-id` AS `owner-uri-id`, @@ -2422,6 +2445,7 @@ CREATE VIEW `post-view` AS SELECT `author`.`blocked` AS `author-blocked`, `author`.`hidden` AS `author-hidden`, `author`.`updated` AS `author-updated`, + `author`.`contact-type` AS `author-contact-type`, `author`.`gsid` AS `author-gsid`, `post`.`owner-id` AS `owner-id`, `owner`.`uri-id` AS `owner-uri-id`, @@ -2567,6 +2591,7 @@ CREATE VIEW `post-thread-view` AS SELECT `author`.`blocked` AS `author-blocked`, `author`.`hidden` AS `author-hidden`, `author`.`updated` AS `author-updated`, + `author`.`contact-type` AS `author-contact-type`, `author`.`gsid` AS `author-gsid`, `post-thread`.`owner-id` AS `owner-id`, `owner`.`uri-id` AS `owner-uri-id`, diff --git a/doc/Channels.md b/doc/Channels.md new file mode 100644 index 0000000000..260b962942 --- /dev/null +++ b/doc/Channels.md @@ -0,0 +1,77 @@ +Channels +===== + +* [Home](help) + +Channels are a way to discover new content or to display content that you might have missed otherwise. +There are several predefined channels, additionally you can create your own channels, based on some rules. +Channels only display posts from the last 24 hours (this value can be changed by the admin). + +In the display settings in the section "Timelines" you can define which channels and other timelines you want to see in the "Channels" widget on the network page and which channels should appear in the menu bar at the top of the page. + +Also in the display settings in the section "Channels" you can define all the languages that you want to see in your channels. Here you can select more than one language. + +On the contact page you can define the channel frequency for every contact. The options are: + +* Default frequency: Posts by this contact are displayed in the "for you" channel if you interact often with this contact or if a post reached some level of interaction. +* Display all posts of this contact: All posts from this contact will appear on the "for you" channel. +* Display only few posts: When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel. +* Never display posts: Posts from this contact will never be displayed in any channel. + +Predefined Channels +--- + +* For you: Posts from contacts you interact with and who interact with you. In detail, it consists of: + * Posts from people you interact with on a more than average level. + * Posts from the accounts that you follow with a more than average number of interactions- + * Posts from accounts where you activated "notify on new posts" or where you have set the channel frequency accordingly. +* What's Hot: Posts with a more than average number of interactions. +* Language: Posts in your language. +* Followers: Posts from your followers that you don't follow. +* Sharers of sharers: Posts from accounts that are followed by accounts that you follow. +* Images: Posts with images. +* Audio: Posts with audio. +* Videos: Posts with videos. + +User defined Channels +--- + +In the "Channels" settings you can create your own channels. + +Each channel is defined by these values: + +* Label: This value is mandatory and is used for the menu label. +* Description: A short description of the content. This can help to keep the overview, when you have got a lot of channels. +* Access Key: When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one. +* Circle: This defines the data source for this channel. By default it is set to the public timeline. There are some predefined values, like the accounts that you follow or the accounts that follow you. Also all of your circles can be selected. +* Include Tags: Comma separated list of tags. A post will be used when it contains any of the listed tags. +* Exclude Tags: Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel. +* Full Text Search: This can be used to include or exclude content, based on the content and some additional keywords. It uses the "boolean mode" operators from MariaDB: https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode +* Images, Videos, Audio: When selected, you will see content with the selected media type. This can be combined. If none of these fields are checked, you will see any content, with or without attacked media. + +Additional keywords for the full text search +--- + +Additionally to the search for content, there are additional keywords that can be used in the full text search: + +* from - Use "from:nickname" or "from:nickname@domain.tld" to search for posts from a specific author. +* to - Use "from:nickname" or "from:nickname@domain.tld" to search for posts with the given contact as receiver. +* group - Use "from:nickname" or "from:nickname@domain.tld" to search for group post of the given group. +* tag - Use "tag:tagname" to search for a specific tag. +* network - Use this to include or exclude some networks from your channel. + * network:apub - ActivityPub (Used by the systems in the Fediverse) + * network:dfrn - Legacy Friendica protocol. Nowayday Friendica mostly uses ActivityPub. + * network:dspr - The Diaspora protocol is mainly used by Diaspora itself. Some other systems support the protocol as well like Hubzilla, Socialhome or Ganggo. + * network:feed - RSS/Atom feeds + * network:mail - Mails that had been imported via IMAP. + * network:stat - The OStatus protocol is mainly used by old GNU Social installations. + * network:dscs - Posts that are received by the Discourse connector. + * network:tmbl - Posts that are received by the Tumblr connector. + * network:bsky - Posts that are received by the Bluesky connector. +* visibility - You have the choice between different visibilities. You can only see unlisted or private posts that you have the access for. + * visibility:public + * visibility:unlisted + * visibility:private + +Remember that you can combine these kerywords. +So for example you can create a channel with all posts that talk about the Fediverse - that aren't posted in the Fediverse with the search terms: "fediverse -network:apub -network:dfrn" \ No newline at end of file diff --git a/doc/Home.md b/doc/Home.md index 9578e914a0..33ed640746 100644 --- a/doc/Home.md +++ b/doc/Home.md @@ -17,6 +17,7 @@ Friendica Documentation and Resources * [Circles and Privacy](help/Circles-and-Privacy) * [Tags and Mentions](help/Tags-and-Mentions) * [Community Groups](help/Groups) + * [Channels](help/Channels) * [Chats](help/Chats) * Further information * [Move your account](help/Move-Account) diff --git a/doc/database.md b/doc/database.md index 25b9baefb1..516be3bae1 100644 --- a/doc/database.md +++ b/doc/database.md @@ -17,6 +17,7 @@ Database Tables | [arrived-activity](help/database/db_arrived-activity) | Id of arrived activities | | [attach](help/database/db_attach) | file attachments | | [cache](help/database/db_cache) | Stores temporary data | +| [channel](help/database/db_channel) | User defined Channels | | [config](help/database/db_config) | main configuration storage | | [contact](help/database/db_contact) | contact table | | [contact-relation](help/database/db_contact-relation) | Contact relations | diff --git a/doc/database/db_channel.md b/doc/database/db_channel.md new file mode 100644 index 0000000000..e37aa93d11 --- /dev/null +++ b/doc/database/db_channel.md @@ -0,0 +1,37 @@ +Table channel +=========== + +User defined Channels + +Fields +------ + +| Field | Description | Type | Null | Key | Default | Extra | +| ---------------- | ------------------------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | -------------- | +| id | | int unsigned | NO | PRI | NULL | auto_increment | +| uid | User id | mediumint unsigned | NO | | NULL | | +| label | Channel label | varchar(64) | NO | | NULL | | +| description | Channel description | varchar(64) | YES | | NULL | | +| circle | Circle or channel that this channel is based on | int | YES | | NULL | | +| access-key | Access key | varchar(1) | YES | | NULL | | +| include-tags | Comma separated list of tags that will be included in the channel | varchar(255) | YES | | NULL | | +| exclude-tags | Comma separated list of tags that aren't allowed in the channel | varchar(255) | YES | | NULL | | +| full-text-search | Full text search pattern, see https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode | varchar(255) | YES | | NULL | | +| media-type | Filtered media types | smallint unsigned | YES | | NULL | | + +Indexes +------------ + +| Name | Fields | +| ------- | ------ | +| PRIMARY | id | +| uid | uid | + +Foreign Keys +------------ + +| Field | Target Table | Target Field | +|-------|--------------|--------------| +| uid | [user](help/database/db_user) | uid | + +Return to [database documentation](help/database) diff --git a/doc/database/db_post-engagement.md b/doc/database/db_post-engagement.md index 19cb64d54f..edca447f3d 100644 --- a/doc/database/db_post-engagement.md +++ b/doc/database/db_post-engagement.md @@ -13,6 +13,7 @@ Fields | contact-type | Person, organisation, news, community, relay | tinyint | NO | | 0 | | | media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | | | language | Language information about this post | varbinary(128) | YES | | NULL | | +| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | | | created | | datetime | YES | | NULL | | | restricted | If true, this post is either unlisted or not from a federated network | boolean | NO | | 0 | | | comments | Number of comments | mediumint unsigned | YES | | NULL | | @@ -21,11 +22,12 @@ Fields Indexes ------------ -| Name | Fields | -| -------- | -------- | -| PRIMARY | uri-id | -| owner-id | owner-id | -| created | created | +| Name | Fields | +| ---------- | -------------------- | +| PRIMARY | uri-id | +| owner-id | owner-id | +| created | created | +| searchtext | FULLTEXT, searchtext | Foreign Keys ------------ diff --git a/doc/de/Home.md b/doc/de/Home.md index 91976bb282..6cd6e5ea87 100644 --- a/doc/de/Home.md +++ b/doc/de/Home.md @@ -17,6 +17,7 @@ Friendica - Dokumentation und Ressourcen * [Circles und Privatsphäre](help/Circles-and-Privacy) * [Tags und Erwähnungen](help/Tags-and-Mentions) * [Community-Gruppen](help/Groups) + * [Channels](help/Channels) * [Chats](help/Chats) * Weiterführende Informationen * [Account umziehen](help/Move-Account) diff --git a/src/Content/Conversation/Collection/UserDefinedChannels.php b/src/Content/Conversation/Collection/UserDefinedChannels.php new file mode 100644 index 0000000000..5dc7326da6 --- /dev/null +++ b/src/Content/Conversation/Collection/UserDefinedChannels.php @@ -0,0 +1,26 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Collection; + +class UserDefinedChannels extends Timelines +{ +} diff --git a/src/Content/Conversation/Entity/Channel.php b/src/Content/Conversation/Entity/Channel.php new file mode 100644 index 0000000000..8166b7906b --- /dev/null +++ b/src/Content/Conversation/Entity/Channel.php @@ -0,0 +1,34 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Entity; + +class Channel extends Timeline +{ + const WHATSHOT = 'whatshot'; + const FORYOU = 'foryou'; + const FOLLOWERS = 'followers'; + const SHARERSOFSHARERS = 'sharersofsharers'; + const IMAGE = 'image'; + const VIDEO = 'video'; + const AUDIO = 'audio'; + const LANGUAGE = 'language'; +} diff --git a/src/Content/Conversation/Entity/Community.php b/src/Content/Conversation/Entity/Community.php new file mode 100644 index 0000000000..35c7add33e --- /dev/null +++ b/src/Content/Conversation/Entity/Community.php @@ -0,0 +1,28 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Entity; + +final class Community extends Timeline +{ + const LOCAL = 'local'; + const GLOBAL = 'global'; +} diff --git a/src/Content/Conversation/Entity/Network.php b/src/Content/Conversation/Entity/Network.php new file mode 100644 index 0000000000..75775b7b3c --- /dev/null +++ b/src/Content/Conversation/Entity/Network.php @@ -0,0 +1,31 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Entity; + +final class Network extends Timeline +{ + const STAR = 'star'; + const MENTION = 'mention'; + const RECEIVED = 'received'; + const COMMENTED = 'commented'; + const CREATED = 'created'; +} diff --git a/src/Content/Conversation/Entity/Timeline.php b/src/Content/Conversation/Entity/Timeline.php index b9ab1e1a01..20223d5182 100644 --- a/src/Content/Conversation/Entity/Timeline.php +++ b/src/Content/Conversation/Entity/Timeline.php @@ -22,30 +22,20 @@ namespace Friendica\Content\Conversation\Entity; /** - * @property-read string $code Channel code - * @property-read string $label Channel label - * @property-read string $description Channel description - * @property-read string $accessKey Access key - * @property-read string $path Path + * @property-read string $code Channel code + * @property-read string $label Channel label + * @property-read string $description Channel description + * @property-read string $accessKey Access key + * @property-read string $path Path + * @property-read int $uid User of the channel + * @property-read string $includeTags The tags to include in the channel + * @property-read string $excludeTags The tags to exclude in the channel + * @property-read string $fullTextSearch full text search pattern + * @property-read int $mediaType Media types that are included in the channel + * @property-read int $circle Circle or timeline this channel is based on */ -final class Timeline extends \Friendica\BaseEntity +class Timeline extends \Friendica\BaseEntity { - const WHATSHOT = 'whatshot'; - const FORYOU = 'foryou'; - const FOLLOWERS = 'followers'; - const SHARERSOFSHARERS = 'sharersofsharers'; - const IMAGE = 'image'; - 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; /** @var string */ @@ -56,13 +46,31 @@ final class Timeline extends \Friendica\BaseEntity protected $accessKey; /** @var string */ protected $path; + /** @var int */ + protected $uid; + /** @var int */ + protected $circle; + /** @var string */ + protected $includeTags; + /** @var string */ + protected $excludeTags; + /** @var string */ + protected $fullTextSearch; + /** @var int */ + protected $mediaType; - public function __construct(string $code, string $label, string $description, string $accessKey, string $path = null) + public function __construct(string $code = null, string $label = null, string $description = null, string $accessKey = null, string $path = null, int $uid = null, string $includeTags = null, string $excludeTags = null, string $fullTextSearch = null, int $mediaType = null, int $circle = null) { - $this->code = $code; - $this->label = $label; - $this->description = $description; - $this->accessKey = $accessKey; - $this->path = $path; + $this->code = $code; + $this->label = $label; + $this->description = $description; + $this->accessKey = $accessKey; + $this->path = $path; + $this->uid = $uid; + $this->includeTags = $includeTags; + $this->excludeTags = $excludeTags; + $this->fullTextSearch = $fullTextSearch; + $this->mediaType = $mediaType; + $this->circle = $circle; } } diff --git a/src/Content/Conversation/Entity/UserDefinedChannel.php b/src/Content/Conversation/Entity/UserDefinedChannel.php new file mode 100644 index 0000000000..3d88c6666b --- /dev/null +++ b/src/Content/Conversation/Entity/UserDefinedChannel.php @@ -0,0 +1,26 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Entity; + +class UserDefinedChannel extends Channel +{ +} diff --git a/src/Content/Conversation/Factory/Channel.php b/src/Content/Conversation/Factory/Channel.php new file mode 100644 index 0000000000..f03b589251 --- /dev/null +++ b/src/Content/Conversation/Factory/Channel.php @@ -0,0 +1,59 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Factory; + +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Content\Conversation\Entity\Channel as ChannelEntity; +use Friendica\Model\User; + +final class Channel extends Timeline +{ + /** + * List of available channels + * + * @param integer $uid + * @return Timelines + */ + public function getTimelines(int $uid): Timelines + { + $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 Timelines($tabs); + } + + public function isTimeline(string $selectedTab): bool + { + return in_array($selectedTab, [ChannelEntity::WHATSHOT, ChannelEntity::FORYOU, ChannelEntity::FOLLOWERS, ChannelEntity::SHARERSOFSHARERS, ChannelEntity::IMAGE, ChannelEntity::VIDEO, ChannelEntity::AUDIO, ChannelEntity::LANGUAGE]); + } +} diff --git a/src/Content/Conversation/Factory/Community.php b/src/Content/Conversation/Factory/Community.php new file mode 100644 index 0000000000..bd86ce3a9f --- /dev/null +++ b/src/Content/Conversation/Factory/Community.php @@ -0,0 +1,56 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Factory; + +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Content\Conversation\Entity\Community as CommunityEntity; +use Friendica\Module\Conversation\Community as CommunityModule; + +final class Community extends Timeline +{ + /** + * List of available communities + * + * @param boolean $authenticated + * @return Timelines + */ + public function getTimelines(bool $authenticated): Timelines + { + $page_style = $this->config->get('system', 'community_page_style'); + + $tabs = []; + + if (($authenticated || in_array($page_style, [CommunityModule::LOCAL_AND_GLOBAL, CommunityModule::LOCAL])) && empty($this->config->get('system', 'singleuser'))) { + $tabs[] = new CommunityEntity(CommunityEntity::LOCAL, $this->l10n->t('Local Community'), $this->l10n->t('Posts from local users on this server'), 'l'); + } + + if ($authenticated || in_array($page_style, [CommunityModule::LOCAL_AND_GLOBAL, CommunityModule::GLOBAL])) { + $tabs[] = new CommunityEntity(CommunityEntity::GLOBAL, $this->l10n->t('Global Community'), $this->l10n->t('Posts from users of the whole federated network'), 'g'); + } + return new Timelines($tabs); + } + + public function isTimeline(string $selectedTab): bool + { + return in_array($selectedTab, [CommunityEntity::LOCAL, CommunityEntity::GLOBAL]); + } +} diff --git a/src/Content/Conversation/Factory/Network.php b/src/Content/Conversation/Factory/Network.php new file mode 100644 index 0000000000..d7457a8460 --- /dev/null +++ b/src/Content/Conversation/Factory/Network.php @@ -0,0 +1,51 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Factory; + +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Content\Conversation\Entity\Network as NetworkEntity; + +final class Network extends Timeline +{ + /** + * List of available network timelines + * + * @param string $command + * @return Timelines + */ + public function getTimelines(string $command): Timelines + { + $tabs = [ + new NetworkEntity(NetworkEntity::COMMENTED, $this->l10n->t('Latest Activity'), $this->l10n->t('Sort by latest activity'), 'e', $command . '?' . http_build_query(['order' => 'commented'])), + new NetworkEntity(NetworkEntity::RECEIVED, $this->l10n->t('Latest Posts'), $this->l10n->t('Sort by post received date'), 't', $command . '?' . http_build_query(['order' => 'received'])), + new NetworkEntity(NetworkEntity::CREATED, $this->l10n->t('Latest Creation'), $this->l10n->t('Sort by post creation date'), 'q', $command . '?' . http_build_query(['order' => 'created'])), + new NetworkEntity(NetworkEntity::MENTION, $this->l10n->t('Personal'), $this->l10n->t('Posts that mention or involve you'), 'r', $command . '?' . http_build_query(['mention' => true])), + new NetworkEntity(NetworkEntity::STAR, $this->l10n->t('Starred'), $this->l10n->t('Favourite Posts'), 'm', $command . '?' . http_build_query(['star' => true])), + ]; + return new Timelines($tabs); + } + + public function isTimeline(string $selectedTab): bool + { + return in_array($selectedTab, [NetworkEntity::COMMENTED, NetworkEntity::RECEIVED, NetworkEntity::CREATED, NetworkEntity::MENTION, NetworkEntity::STAR]); + } +} diff --git a/src/Content/Conversation/Factory/Timeline.php b/src/Content/Conversation/Factory/Timeline.php index 817f617131..a726762fd5 100644 --- a/src/Content/Conversation/Factory/Timeline.php +++ b/src/Content/Conversation/Factory/Timeline.php @@ -21,100 +21,28 @@ namespace Friendica\Content\Conversation\Factory; -use Friendica\Content\Conversation\Collection\Timelines; -use Friendica\Model\User; +use Friendica\Capabilities\ICanCreateFromTableRow; use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity; +use Friendica\Content\Conversation\Repository\UserDefinedChannel; 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 +class Timeline extends \Friendica\BaseFactory { /** @var L10n */ protected $l10n; /** @var IManageConfigValues The config */ protected $config; + /** @var UserDefinedChannel */ + protected $channelRepository; - public function __construct(L10n $l10n, LoggerInterface $logger, IManageConfigValues $config) + public function __construct(UserDefinedChannel $channel, 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]); + $this->channelRepository = $channel; + $this->l10n = $l10n; + $this->config = $config; } } diff --git a/src/Content/Conversation/Factory/UserDefinedChannel.php b/src/Content/Conversation/Factory/UserDefinedChannel.php new file mode 100644 index 0000000000..12816126db --- /dev/null +++ b/src/Content/Conversation/Factory/UserDefinedChannel.php @@ -0,0 +1,51 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Factory; + +use Friendica\Capabilities\ICanCreateFromTableRow; +use Friendica\Content\Conversation\Collection\Timelines; +use Friendica\Content\Conversation\Entity; + +final class UserDefinedChannel extends Timeline implements ICanCreateFromTableRow +{ + public function isTimeline(string $selectedTab, int $uid): bool + { + return is_numeric($selectedTab) && $uid && $this->channelRepository->existsById($selectedTab, $uid); + } + + public function createFromTableRow(array $row): Entity\UserDefinedChannel + { + return new Entity\UserDefinedChannel( + $row['id'] ?? null, + $row['label'], + $row['description'] ?? null, + $row['access-key'] ?? null, + null, + $row['uid'], + $row['include-tags'] ?? null, + $row['exclude-tags'] ?? null, + $row['full-text-search'] ?? null, + $row['media-type'] ?? null, + $row['circle'] ?? null, + ); + } +} diff --git a/src/Content/Conversation/Repository/UserDefinedChannel.php b/src/Content/Conversation/Repository/UserDefinedChannel.php new file mode 100644 index 0000000000..1014711765 --- /dev/null +++ b/src/Content/Conversation/Repository/UserDefinedChannel.php @@ -0,0 +1,133 @@ +. + * + */ + +namespace Friendica\Content\Conversation\Repository; + +use Friendica\BaseCollection; +use Friendica\Content\Conversation\Collection\UserDefinedChannels; +use Friendica\Content\Conversation\Entity; +use Friendica\Content\Conversation\Factory; +use Friendica\Database\Database; +use Psr\Log\LoggerInterface; + +class UserDefinedChannel extends \Friendica\BaseRepository +{ + protected static $table_name = 'channel'; + + public function __construct(Database $database, LoggerInterface $logger, Factory\UserDefinedChannel $factory) + { + parent::__construct($database, $logger, $factory); + } + + /** + * @param array $condition + * @param array $params + * @return UserDefinedChannels + * @throws \Exception + */ + protected function _select(array $condition, array $params = []): BaseCollection + { + $rows = $this->db->selectToArray(static::$table_name, [], $condition, $params); + + $Entities = new UserDefinedChannels(); + foreach ($rows as $fields) { + $Entities[] = $this->factory->createFromTableRow($fields); + } + + return $Entities; + } + + /** + * Fetch a single user channel + * + * @param int $id The id of the user defined channel + * @param int $uid The user that this channel belongs to. (Not part of the primary key) + * @return Entity\UserDefinedChannel + * @throws \Friendica\Network\HTTPException\NotFoundException + */ + public function selectById(int $id, int $uid): Entity\UserDefinedChannel + { + return $this->_selectOne(['id' => $id, 'uid' => $uid]); + } + + /** + * Checks if the provided channel id exists for this user + * + * @param integer $id + * @param integer $uid + * @return boolean + */ + public function existsById(int $id, int $uid): bool + { + return $this->exists(['id' => $id, 'uid' => $uid]); + } + + /** + * Delete the given channel + * + * @param integer $id + * @param integer $uid + * @return boolean + */ + public function deleteById(int $id, int $uid): bool + { + return $this->db->delete('channel', ['id' => $id, 'uid' => $uid]); + } + + /** + * Fetch all user channels + * + * @param integer $uid + * @return UserDefinedChannels + * @throws \Exception + */ + public function selectByUid(int $uid): UserDefinedChannels + { + return $this->_select(['uid' => $uid]); + } + + public function save(Entity\UserDefinedChannel $Channel): Entity\UserDefinedChannel + { + $fields = [ + 'label' => $Channel->label, + 'description' => $Channel->description, + 'access-key' => $Channel->accessKey, + 'uid' => $Channel->uid, + 'circle' => $Channel->circle, + 'include-tags' => $Channel->includeTags, + 'exclude-tags' => $Channel->excludeTags, + 'full-text-search' => $Channel->fullTextSearch, + 'media-type' => $Channel->mediaType, + ]; + + if ($Channel->code) { + $this->db->update(self::$table_name, $fields, ['uid' => $Channel->uid, 'id' => $Channel->code]); + } else { + $this->db->insert(self::$table_name, $fields, Database::INSERT_IGNORE); + + $newChannelId = $this->db->lastInsertId(); + + $Channel = $this->selectById($newChannelId, $Channel->uid); + } + + return $Channel; + } +} diff --git a/src/Content/Widget.php b/src/Content/Widget.php index 1f73f04495..3147e99a56 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -560,12 +560,30 @@ class Widget { $channels = []; - foreach (DI::TimelineFactory()->getChannelsForUser($uid) as $channel) { - $channels[] = ['ref' => $channel->code, 'name' => $channel->label]; + $enabled = DI::pConfig()->get($uid, 'system', 'enabled_timelines', []); + + foreach (DI::NetworkFactory()->getTimelines('') as $channel) { + if (empty($enabled) || in_array($channel->code, $enabled)) { + $channels[] = ['ref' => $channel->code, 'name' => $channel->label]; + } } - foreach (DI::TimelineFactory()->getCommunities(true) as $community) { - $channels[] = ['ref' => $community->code, 'name' => $community->label]; + foreach (DI::ChannelFactory()->getTimelines($uid) as $channel) { + if (empty($enabled) || in_array($channel->code, $enabled)) { + $channels[] = ['ref' => $channel->code, 'name' => $channel->label]; + } + } + + foreach (DI::userDefinedChannel()->selectByUid($uid) as $channel) { + if (empty($enabled) || in_array($channel->code, $enabled)) { + $channels[] = ['ref' => $channel->code, 'name' => $channel->label]; + } + } + + foreach (DI::CommunityFactory()->getTimelines(true) as $community) { + if (empty($enabled) || in_array($community->code, $enabled)) { + $channels[] = ['ref' => $community->code, 'name' => $community->label]; + } } return self::filter( diff --git a/src/Content/Widget/VCard.php b/src/Content/Widget/VCard.php index 478ab52fe7..d57f7130f9 100644 --- a/src/Content/Widget/VCard.php +++ b/src/Content/Widget/VCard.php @@ -102,8 +102,15 @@ class VCard } if ($contact['contact-type'] == Contact::TYPE_COMMUNITY) { - $showgroup_link = 'network/group/' . $id; + $mention_label = DI::l10n()->t('Post to group'); + $mention_url = 'compose/0?body=!' . $contact['addr']; + $showgroup_label = DI::l10n()->t('View group'); + $showgroup_url = 'network/group/' . $id; + } else { + $mention_label = DI::l10n()->t('Mention'); + $mention_url = 'compose/0?body=@' . $contact['addr']; } + } return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [ @@ -124,10 +131,8 @@ class VCard '$unfollow_link' => $unfollow_link, '$wallmessage' => DI::l10n()->t('Message'), '$wallmessage_link' => $wallmessage_link, - '$mention' => DI::l10n()->t('Mention'), - '$posttogroup' => DI::l10n()->t('Post to group'), - '$showgroup' => DI::l10n()->t('View group'), - '$showgroup_link' => $showgroup_link, + '$mention' => [$mention_label, $mention_url], + '$showgroup' => [$showgroup_label, $showgroup_url] ]); } } diff --git a/src/DI.php b/src/DI.php index 5ffce51752..4205640cc1 100644 --- a/src/DI.php +++ b/src/DI.php @@ -555,6 +555,35 @@ abstract class DI return self::$dice->create(Content\Conversation\Factory\Timeline::class); } + /** + * @return Content\Conversation\Factory\Community + */ + public static function CommunityFactory() + { + return self::$dice->create(Content\Conversation\Factory\Community::class); + } + + /** + * @return Content\Conversation\Factory\Channel + */ + public static function ChannelFactory() + { + return self::$dice->create(Content\Conversation\Factory\Channel::class); + } + + public static function userDefinedChannel(): Content\Conversation\Repository\UserDefinedChannel + { + return self::$dice->create(Content\Conversation\Repository\UserDefinedChannel::class); + } + + /** + * @return Content\Conversation\Factory\Network + */ + public static function NetworkFactory() + { + return self::$dice->create(Content\Conversation\Factory\Network::class); + } + /** * @return Contact\Introduction\Repository\Introduction */ diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 3e547fcaff..be3cec4701 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1198,20 +1198,16 @@ class Contact $pm_url = 'message/new/' . $contact['id']; } - if ($contact['contact-type'] == Contact::TYPE_COMMUNITY) { - $mention_label = DI::l10n()->t('Post to group'); - $mention_url = 'compose/0?body=!' . $contact['addr']; - } else { - $mention_label = DI::l10n()->t('Mention'); - $mention_url = 'compose/0?body=@' . $contact['addr']; - } - $contact_url = 'contact/' . $contact['id']; if ($contact['contact-type'] == Contact::TYPE_COMMUNITY) { + $mention_label = DI::l10n()->t('Post to group'); + $mention_url = 'compose/0?body=!' . $contact['addr']; $network_label = DI::l10n()->t('View group'); $network_url = 'network/group/' . $contact['id']; } else { + $mention_label = DI::l10n()->t('Mention'); + $mention_url = 'compose/0?body=@' . $contact['addr']; $network_label = DI::l10n()->t('Network Posts'); $network_url = 'contact/' . $contact['id'] . '/conversations'; } diff --git a/src/Model/Post/Engagement.php b/src/Model/Post/Engagement.php index 80f1247a65..9ffd48aa00 100644 --- a/src/Model/Post/Engagement.php +++ b/src/Model/Post/Engagement.php @@ -21,6 +21,7 @@ namespace Friendica\Model\Post; +use Friendica\Content\Text\BBCode; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Database\Database; @@ -34,6 +35,7 @@ use Friendica\Model\Verb; use Friendica\Protocol\Activity; use Friendica\Protocol\Relay; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Strings; // Channel @@ -52,9 +54,11 @@ class Engagement return; } - $parent = Post::selectFirst(['created', 'owner-id', 'uid', 'private', 'contact-contact-type', 'language'], ['uri-id' => $item['parent-uri-id']]); + $parent = Post::selectFirst(['uri-id', 'created', 'author-id', 'owner-id', 'uid', 'private', 'contact-contact-type', 'language', 'network', + 'title', 'content-warning', 'body', 'author-contact-type', 'author-nick', 'author-addr', 'owner-contact-type', 'owner-nick', 'owner-addr'], + ['uri-id' => $item['parent-uri-id']]); - if ($parent['created'] < DateTimeFormat::utc('now - ' . DI::config()->get('channel', 'engagement_hours') . ' hour')) { + if ($parent['created'] < self::getCreationDateLimit(false)) { Logger::debug('Post is too old', ['uri-id' => $item['uri-id'], 'parent-uri-id' => $item['parent-uri-id'], 'created' => $parent['created']]); return; } @@ -87,6 +91,7 @@ class Engagement 'contact-type' => $parent['contact-contact-type'], 'media-type' => $mediatype, 'language' => $parent['language'], + 'searchtext' => self::getSearchText($parent), 'created' => $parent['created'], 'restricted' => !in_array($item['network'], Protocol::FEDERATED) || ($parent['private'] != Item::PUBLIC), 'comments' => DBA::count('post', ['parent-uri-id' => $item['parent-uri-id'], 'gravity' => Item::GRAVITY_COMMENT]), @@ -104,6 +109,69 @@ class Engagement Logger::debug('Engagement stored', ['fields' => $engagement, 'ret' => $ret]); } + private static function getSearchText(array $item): string + { + $body = '[nosmile]network:' . $item['network']; + + switch ($item['private']) { + case Item::PUBLIC: + $body .= ' visibility:public'; + break; + case Item::UNLISTED: + $body .= ' visibility:unlisted'; + break; + case Item::PRIVATE: + $body .= ' visibility:private'; + break; + } + + if ($item['author-contact-type'] == Contact::TYPE_COMMUNITY) { + $body .= ' group:' . $item['author-nick'] . ' group:' . $item['author-addr']; + } elseif (in_array($item['author-contact-type'], [Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION])) { + $body .= ' from:' . $item['author-nick'] . ' from:' . $item['author-addr']; + } + + if ($item['author-id'] != $item['owner-id']) { + if ($item['owner-contact-type'] == Contact::TYPE_COMMUNITY) { + $body .= ' group:' . $item['owner-nick'] . ' group:' . $item['owner-addr']; + } elseif (in_array($item['owner-contact-type'], [Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION])) { + $body .= ' from:' . $item['owner-nick'] . ' from:' . $item['owner-addr']; + } + } + + foreach (Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]) as $tag) { + $contact = Contact::getByURL($tag['name'], false, ['nick', 'addr', 'contact-type']); + if (empty($contact)) { + continue; + } + + if (($contact['contact-type'] == Contact::TYPE_COMMUNITY) && !strpos($body, 'group:' . $contact['addr'])) { + $body .= ' group:' . $contact['nick'] . ' group:' . $contact['addr']; + } elseif (in_array($contact['contact-type'], [Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION])) { + $body .= ' to:' . $contact['nick'] . ' to:' . $contact['addr']; + } + } + + foreach (Tag::getByURIId($item['uri-id'], [Tag::HASHTAG]) as $tag) { + $body .= ' tag:' . $tag['name']; + } + + $body .= ' ' . $item['title'] . ' ' . $item['content-warning'] . ' ' . $item['body']; + + $body = preg_replace("~\[url\=.*\]https?:.*\[\/url\]~", '', $body); + + $body = Post\Media::addAttachmentsToBody($item['uri-id'], $body, [Post\Media::IMAGE]); + $text = BBCode::toPlaintext($body, false); + $text = preg_replace(Strings::autoLinkRegEx(), '', $text); + + do { + $oldtext = $text; + $text = str_replace([' ', "\n", "\r"], ' ', $text); + } while ($oldtext != $text); + + return $text; + } + private static function getMediaType(int $uri_id): int { $media = Post\Media::getByURIId($uri_id); @@ -127,7 +195,27 @@ class Engagement */ public static function expire() { - DBA::delete('post-engagement', ["`created` < ?", DateTimeFormat::utc('now - ' . DI::config()->get('channel', 'engagement_hours') . ' hour')]); - Logger::notice('Cleared expired engagements', ['rows' => DBA::affectedRows()]); + $limit = self::getCreationDateLimit(true); + if (empty($limit)) { + Logger::notice('Expiration limit not reached'); + return; + } + DBA::delete('post-engagement', ["`created` < ?", $limit]); + Logger::notice('Cleared expired engagements', ['limit' => $limit, 'rows' => DBA::affectedRows()]); + } + + private static function getCreationDateLimit(bool $forDeletion): string + { + $posts = DI::config()->get('channel', 'engagement_post_limit'); + if (!empty($posts)) { + $limit = DBA::selectToArray('post-engagement', ['created'], [], ['limit' => [$posts, 1], 'order' => ['created' => true]]); + if (!empty($limit)) { + return $limit[0]['created']; + } elseif ($forDeletion) { + return ''; + } + } + + return DateTimeFormat::utc('now - ' . DI::config()->get('channel', 'engagement_hours') . ' hour'); } } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index 9c7aab54ad..c2b21e904b 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -453,6 +453,18 @@ class Profile Logger::warning('Missing hidewall key in profile array', ['profile' => $profile, 'callstack' => System::callstack(10)]); } + if ($profile['account-type'] == Contact::TYPE_COMMUNITY) { + $mention_label = DI::l10n()->t('Post to group'); + $mention_url = 'compose/0?body=!' . $profile['addr']; + $network_label = DI::l10n()->t('View group'); + $network_url = 'network/group/' . $profile['id']; + } else { + $mention_label = DI::l10n()->t('Mention'); + $mention_url = 'compose/0?body=@' . $profile['addr']; + $network_label = DI::l10n()->t('Network Posts'); + $network_url = 'contact/' . $profile['id'] . '/conversations'; + } + $tpl = Renderer::getMarkupTemplate('profile/vcard.tpl'); $o .= Renderer::replaceMacros($tpl, [ '$profile' => $p, @@ -476,6 +488,10 @@ class Profile '$updated' => $updated, '$diaspora' => $diaspora, '$contact_block' => $contact_block, + '$mention_label' => $mention_label, + '$mention_url' => $mention_url, + '$network_label' => $network_label, + '$network_url' => $network_url, ]); $arr = ['profile' => &$profile, 'entry' => &$o]; diff --git a/src/Module/BaseSettings.php b/src/Module/BaseSettings.php index f3acb19a21..c1033e730c 100644 --- a/src/Module/BaseSettings.php +++ b/src/Module/BaseSettings.php @@ -121,6 +121,13 @@ class BaseSettings extends BaseModule 'accesskey' => 'i', ]; + $tabs[] = [ + 'label' => $this->t('Channels'), + 'url' => 'settings/channels', + 'selected' => static::class == Settings\Channels::class ? 'active' : '', + 'accesskey' => '', + ]; + $tabs[] = [ 'label' => $this->t('Social Networks'), 'url' => 'settings/connectors', diff --git a/src/Module/Conversation/Channel.php b/src/Module/Conversation/Channel.php index 35c988ce7c..171236e966 100644 --- a/src/Module/Conversation/Channel.php +++ b/src/Module/Conversation/Channel.php @@ -25,8 +25,13 @@ 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\Entity\Channel as ChannelEntity; +use Friendica\Content\Conversation\Factory\UserDefinedChannel as UserDefinedChannelFactory; use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; +use Friendica\Content\Conversation\Repository\UserDefinedChannel as ChannelRepository; +use Friendica\Content\Conversation\Factory\Channel as ChannelFactory; +use Friendica\Content\Conversation\Factory\Community as CommunityFactory; +use Friendica\Content\Conversation\Factory\Network as NetworkFactory; use Friendica\Content\Feature; use Friendica\Content\Nav; use Friendica\Content\Text\HTML; @@ -56,15 +61,27 @@ class Channel extends Timeline protected $page; /** @var SystemMessages */ protected $systemMessages; + /** @var ChannelFactory */ + protected $channel; + /** @var UserDefinedChannelFactory */ + protected $userDefinedChannel; + /** @var CommunityFactory */ + protected $community; + /** @var NetworkFactory */ + protected $networkFactory; - 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 = []) + public function __construct(UserDefinedChannelFactory $userDefinedChannel, NetworkFactory $network, CommunityFactory $community, ChannelFactory $channelFactory, ChannelRepository $channel, 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); + parent::__construct($channel, $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; + $this->timeline = $timeline; + $this->conversation = $conversation; + $this->page = $page; + $this->systemMessages = $systemMessages; + $this->channel = $channelFactory; + $this->community = $community; + $this->networkFactory = $network; + $this->userDefinedChannel = $userDefinedChannel; } protected function content(array $request = []): string @@ -87,8 +104,9 @@ class Channel extends Timeline } if (empty($request['mode']) || ($request['mode'] != 'raw')) { - $tabs = $this->getTabArray($this->timeline->getChannelsForUser($this->session->getLocalUserId()), 'channel'); - $tabs = array_merge($tabs, $this->getTabArray($this->timeline->getCommunities(true), 'channel')); + $tabs = $this->getTabArray($this->channel->getTimelines($this->session->getLocalUserId()), 'channel'); + $tabs = array_merge($tabs, $this->getTabArray($this->channelRepository->selectByUid($this->session->getLocalUserId()), 'channel')); + $tabs = array_merge($tabs, $this->getTabArray($this->community->getTimelines(true), 'channel')); $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); $o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]); @@ -97,7 +115,7 @@ class Channel extends Timeline $this->page['aside'] .= Widget::accountTypes('channel/' . $this->selectedTab, $this->accountTypeString); - if (!in_array($this->selectedTab, [TimelineEntity::FOLLOWERS, TimelineEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { + if (!in_array($this->selectedTab, [ChannelEntity::FOLLOWERS, ChannelEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { $this->page['aside'] .= $this->getNoSharerWidget('channel'); } @@ -109,7 +127,7 @@ class Channel extends Timeline $o .= $this->conversation->statusEditor([], 0, true); } - if ($this->timeline->isChannel($this->selectedTab)) { + if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) { $items = $this->getChannelItems(); $order = 'created'; } else { @@ -152,10 +170,10 @@ class Channel extends Timeline parent::parseRequest($request); if (!$this->selectedTab) { - $this->selectedTab = TimelineEntity::FORYOU; + $this->selectedTab = ChannelEntity::FORYOU; } - if (!$this->timeline->isChannel($this->selectedTab) && !$this->timeline->isCommunity($this->selectedTab)) { + if (!$this->channel->isTimeline($this->selectedTab) && !$this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId()) && !$this->community->isTimeline($this->selectedTab)) { throw new HTTPException\BadRequestException($this->l10n->t('Channel not available.')); } diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index 42f5620967..d0e3fb8702 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -26,8 +26,9 @@ 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\Conversation\Entity\Community as CommunityEntity; +use Friendica\Content\Conversation\Factory\Community as CommunityFactory; +use Friendica\Content\Conversation\Repository\UserDefinedChannel; use Friendica\Content\Feature; use Friendica\Content\Nav; use Friendica\Content\Text\HTML; @@ -60,8 +61,8 @@ class Community extends Timeline protected $pageStyle; - /** @var TimelineFactory */ - protected $timeline; + /** @var CommunityFactory */ + protected $community; /** @var Conversation */ protected $conversation; /** @var App\Page */ @@ -69,11 +70,11 @@ class Community extends Timeline /** @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 = []) + public function __construct(UserDefinedChannel $channel, CommunityFactory $community, 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); + parent::__construct($channel, $mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->timeline = $timeline; + $this->community = $community; $this->conversation = $conversation; $this->page = $page; $this->systemMessages = $systemMessages; @@ -87,7 +88,7 @@ class Community extends Timeline $o = Renderer::replaceMacros($t, [ '$content' => '', '$header' => '', - '$show_global_community_hint' => ($this->selectedTab == TimelineEntity::GLOBAL) && $this->config->get('system', 'show_global_community_hint'), + '$show_global_community_hint' => ($this->selectedTab == CommunityEntity::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.") ]); @@ -97,7 +98,7 @@ class Community extends Timeline } if (empty($request['mode']) || ($request['mode'] != 'raw')) { - $tabs = $this->getTabArray($this->timeline->getCommunities($this->session->isAuthenticated()), 'community'); + $tabs = $this->getTabArray($this->community->getTimelines($this->session->isAuthenticated()), 'community'); $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); $o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]); @@ -168,14 +169,14 @@ class Community extends Timeline if (!$this->selectedTab) { if (!empty($this->config->get('system', 'singleuser'))) { // On single user systems only the global page does make sense - $this->selectedTab = TimelineEntity::GLOBAL; + $this->selectedTab = CommunityEntity::GLOBAL; } else { // When only the global community is allowed, we use this as default - $this->selectedTab = $this->pageStyle == self::GLOBAL ? TimelineEntity::GLOBAL : TimelineEntity::LOCAL; + $this->selectedTab = $this->pageStyle == self::GLOBAL ? CommunityEntity::GLOBAL : CommunityEntity::LOCAL; } } - if (!$this->timeline->isCommunity($this->selectedTab)) { + if (!$this->community->isTimeline($this->selectedTab)) { throw new HTTPException\BadRequestException($this->l10n->t('Community option not available.')); } @@ -184,11 +185,11 @@ class Community extends Timeline $available = $this->pageStyle == self::LOCAL_AND_GLOBAL; if (!$available) { - $available = ($this->pageStyle == self::LOCAL) && ($this->selectedTab == TimelineEntity::LOCAL); + $available = ($this->pageStyle == self::LOCAL) && ($this->selectedTab == CommunityEntity::LOCAL); } if (!$available) { - $available = ($this->pageStyle == self::GLOBAL) && ($this->selectedTab == TimelineEntity::GLOBAL); + $available = ($this->pageStyle == self::GLOBAL) && ($this->selectedTab == CommunityEntity::GLOBAL); } if (!$available) { diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index beb3a72f3e..24e3e57891 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -25,8 +25,13 @@ 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\Entity\Network as NetworkEntity; use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; +use Friendica\Content\Conversation\Repository\UserDefinedChannel; +use Friendica\Content\Conversation\Factory\Channel as ChannelFactory; +use Friendica\Content\Conversation\Factory\UserDefinedChannel as UserDefinedChannelFactory; +use Friendica\Content\Conversation\Factory\Community as CommunityFactory; +use Friendica\Content\Conversation\Factory\Network as NetworkFactory; use Friendica\Content\Feature; use Friendica\Content\GroupManager; use Friendica\Content\Nav; @@ -95,16 +100,28 @@ class Network extends Timeline protected $database; /** @var TimelineFactory */ protected $timeline; + /** @var ChannelFactory */ + protected $channel; + /** @var UserDefinedChannelFactory */ + protected $userDefinedChannel; + /** @var CommunityFactory */ + protected $community; + /** @var NetworkFactory */ + protected $networkFactory; - public function __construct(App $app, 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 = []) + public function __construct(UserDefinedChannelFactory $userDefinedChannel, NetworkFactory $network, CommunityFactory $community, ChannelFactory $channelFactory, UserDefinedChannel $channel, App $app, 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); + parent::__construct($channel, $mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->app = $app; - $this->timeline = $timeline; - $this->systemMessages = $systemMessages; - $this->conversation = $conversation; - $this->page = $page; + $this->app = $app; + $this->timeline = $timeline; + $this->systemMessages = $systemMessages; + $this->conversation = $conversation; + $this->page = $page; + $this->channel = $channelFactory; + $this->community = $community; + $this->networkFactory = $network; + $this->userDefinedChannel = $userDefinedChannel; } protected function content(array $request = []): string @@ -117,25 +134,14 @@ class Network extends Timeline $module = 'network'; - $this->page['aside'] .= Widget::channels($module, $this->selectedTab, $this->session->getLocalUserId()); - $this->page['aside'] .= Widget::accountTypes($module, $this->accountTypeString); - $arr = ['query' => $this->args->getQueryString()]; Hook::callAll('network_content_init', $arr); $o = ''; - if ($this->timeline->isChannel($this->selectedTab)) { - if (!in_array($this->selectedTab, [TimelineEntity::FOLLOWERS, TimelineEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) { - $this->page['aside'] .= $this->getNoSharerWidget($module); - } - + if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) { $items = $this->getChannelItems(); - } elseif ($this->timeline->isCommunity($this->selectedTab)) { - if ($this->session->getLocalUserId() && $this->config->get('system', 'community_no_sharer')) { - $this->page['aside'] .= $this->getNoSharerWidget($module); - } - + } elseif ($this->community->isTimeline($this->selectedTab)) { $items = $this->getCommunityItems(); } else { $items = $this->getItems(); @@ -145,6 +151,8 @@ class Network extends Timeline $this->page['aside'] .= GroupManager::widget($module . '/group', $this->session->getLocalUserId(), $this->groupContactId); $this->page['aside'] .= Widget::postedByYear($module . '/archive', $this->session->getLocalUserId(), false); $this->page['aside'] .= Widget::networks($module, !$this->groupContactId ? $this->network : ''); + $this->page['aside'] .= Widget::accountTypes($module, $this->accountTypeString); + $this->page['aside'] .= Widget::channels($module, $this->selectedTab, $this->session->getLocalUserId()); $this->page['aside'] .= Widget\SavedSearches::getHTML($this->args->getQueryString()); $this->page['aside'] .= Widget::fileAs('filed', ''); @@ -274,13 +282,13 @@ class Network extends Timeline */ private function getTabsHTML() { - // @todo user confgurable selection of tabs - $tabs = $this->getTabArray($this->timeline->getNetworkFeeds($this->args->getCommand()), 'network'); + $tabs = $this->getTabArray($this->networkFactory->getTimelines($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')); + $tabs = array_merge($tabs, $this->getTabArray($this->channel->getTimelines($this->session->getLocalUserId()), 'network', 'channel')); + $tabs = array_merge($tabs, $this->getTabArray($this->channelRepository->selectByUid($this->session->getLocalUserId()), 'network', 'channel')); + $tabs = array_merge($tabs, $this->getTabArray($this->community->getTimelines(true), 'network', 'channel')); } $arr = ['tabs' => $tabs]; @@ -289,9 +297,9 @@ class Network extends Timeline if (!empty($network_timelines)) { $tabs = []; - foreach (array_keys($arr['tabs']) as $tab) { - if (in_array($tab, $network_timelines)) { - $tabs[] = $arr['tabs'][$tab]; + foreach ($arr['tabs'] as $tab) { + if (in_array($tab['code'], $network_timelines)) { + $tabs[] = $tab; } } } else { @@ -313,26 +321,26 @@ class Network extends Timeline if (!$this->selectedTab) { $this->selectedTab = self::getTimelineOrderBySession($this->session, $this->pConfig); - } elseif (!$this->timeline->isChannel($this->selectedTab) && !$this->timeline->isCommunity($this->selectedTab)) { + } elseif (!$this->networkFactory->isTimeline($this->selectedTab) && !$this->channel->isTimeline($this->selectedTab) && !$this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId()) && !$this->community->isTimeline($this->selectedTab)) { throw new HTTPException\BadRequestException($this->l10n->t('Network feed not available.')); } - if (($this->network || $this->circleId || $this->groupContactId) && ($this->timeline->isChannel($this->selectedTab) || $this->timeline->isCommunity($this->selectedTab))) { - $this->selectedTab = TimelineEntity::RECEIVED; + if (($this->network || $this->circleId || $this->groupContactId) && ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId()) || $this->community->isTimeline($this->selectedTab))) { + $this->selectedTab = NetworkEntity::RECEIVED; } if (!empty($request['star'])) { - $this->selectedTab = TimelineEntity::STAR; + $this->selectedTab = NetworkEntity::STAR; $this->star = true; } else { - $this->star = $this->selectedTab == TimelineEntity::STAR; + $this->star = $this->selectedTab == NetworkEntity::STAR; } if (!empty($request['mention'])) { - $this->selectedTab = TimelineEntity::MENTION; + $this->selectedTab = NetworkEntity::MENTION; $this->mention = true; } else { - $this->mention = $this->selectedTab == TimelineEntity::MENTION; + $this->mention = $this->selectedTab == NetworkEntity::MENTION; } if (!empty($request['order'])) { @@ -340,9 +348,9 @@ class Network extends Timeline $this->order = $request['order']; $this->star = false; $this->mention = false; - } elseif (in_array($this->selectedTab, [TimelineEntity::RECEIVED, TimelineEntity::STAR]) || $this->timeline->isCommunity($this->selectedTab)) { + } elseif (in_array($this->selectedTab, [NetworkEntity::RECEIVED, NetworkEntity::STAR]) || $this->community->isTimeline($this->selectedTab)) { $this->order = 'received'; - } elseif (($this->selectedTab == TimelineEntity::CREATED) || $this->timeline->isChannel($this->selectedTab)) { + } elseif (($this->selectedTab == NetworkEntity::CREATED) || $this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) { $this->order = 'created'; } else { $this->order = 'commented'; @@ -352,16 +360,16 @@ class Network extends Timeline // Upon updates in the background and order by last comment we order by received date, // since otherwise the feed will optically jump, when some already visible thread has been updated. - if ($this->update && ($this->selectedTab == TimelineEntity::COMMENTED)) { + if ($this->update && ($this->selectedTab == NetworkEntity::COMMENTED)) { $this->order = 'received'; $request['last_received'] = $request['last_commented'] ?? null; $request['first_received'] = $request['first_commented'] ?? null; } // Prohibit combined usage of "star" and "mention" - if ($this->selectedTab == TimelineEntity::STAR) { + if ($this->selectedTab == NetworkEntity::STAR) { $this->mention = false; - } elseif ($this->selectedTab == TimelineEntity::MENTION) { + } elseif ($this->selectedTab == NetworkEntity::MENTION) { $this->star = false; } diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php index 1a4d98e61c..eb362eba7b 100644 --- a/src/Module/Conversation/Timeline.php +++ b/src/Module/Conversation/Timeline.php @@ -25,7 +25,8 @@ 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\Content\Conversation\Entity\Channel as ChannelEntity; +use Friendica\Content\Conversation\Repository\UserDefinedChannel; use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Config\Capability\IManageConfigValues; @@ -79,17 +80,20 @@ class Timeline extends BaseModule protected $config; /** @var ICanCache */ protected $cache; + /** @var UserDefinedChannel */ + protected $channelRepository; - 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 = []) + public function __construct(UserDefinedChannel $channel, 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; + $this->channelRepository = $channel; + $this->mode = $mode; + $this->session = $session; + $this->database = $database; + $this->pConfig = $pConfig; + $this->config = $config; + $this->cache = $cache; } /** @@ -176,6 +180,7 @@ class Timeline extends BaseModule $path = $tab->path ?? $prefix . '/' . $tab->code; } $tabs[$tab->code] = [ + 'code' => $tab->code, 'label' => $tab->label, 'url' => $path, 'sel' => $this->selectedTab == $tab->code ? 'active' : '', @@ -264,13 +269,13 @@ class Timeline extends BaseModule { $uid = $this->session->getLocalUserId(); - if ($this->selectedTab == TimelineEntity::WHATSHOT) { + if ($this->selectedTab == ChannelEntity::WHATSHOT) { if (!is_null($this->accountType)) { $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` = ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $this->accountType]; } else { $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` != ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), Contact::TYPE_COMMUNITY]; } - } elseif ($this->selectedTab == TimelineEntity::FORYOU) { + } elseif ($this->selectedTab == ChannelEntity::FORYOU) { $cid = Contact::getPublicIdByUserId($uid); $condition = [ @@ -280,9 +285,9 @@ class Timeline extends BaseModule $cid, $this->getMedianRelationThreadScore($cid, 4), $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $cid, $uid, Contact\User::FREQUENCY_ALWAYS ]; - } elseif ($this->selectedTab == TimelineEntity::FOLLOWERS) { + } elseif ($this->selectedTab == ChannelEntity::FOLLOWERS) { $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER]; - } elseif ($this->selectedTab == TimelineEntity::SHARERSOFSHARERS) { + } elseif ($this->selectedTab == ChannelEntity::SHARERSOFSHARERS) { $cid = Contact::getPublicIdByUserId($uid); // @todo Suggest posts from contacts that are followed most by our followers @@ -292,17 +297,19 @@ class Timeline extends BaseModule 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 ($this->selectedTab == TimelineEntity::IMAGE) { + } elseif ($this->selectedTab == ChannelEntity::IMAGE) { $condition = ["`media-type` & ?", 1]; - } elseif ($this->selectedTab == TimelineEntity::VIDEO) { + } elseif ($this->selectedTab == ChannelEntity::VIDEO) { $condition = ["`media-type` & ?", 2]; - } elseif ($this->selectedTab == TimelineEntity::AUDIO) { + } elseif ($this->selectedTab == ChannelEntity::AUDIO) { $condition = ["`media-type` & ?", 4]; - } elseif ($this->selectedTab == TimelineEntity::LANGUAGE) { + } elseif ($this->selectedTab == ChannelEntity::LANGUAGE) { $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", $this->l10n->convertCodeForLanguageDetection(User::getLanguageCode($uid))]; + } elseif (is_numeric($this->selectedTab)) { + $condition = $this->getUserChannelConditions($this->selectedTab, $this->session->getLocalUserId()); } - if ($this->selectedTab != TimelineEntity::LANGUAGE) { + if ($this->selectedTab != ChannelEntity::LANGUAGE) { $condition = $this->addLanguageCondition($uid, $condition); } @@ -310,7 +317,7 @@ class Timeline extends BaseModule $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` OR `is-blocked` OR `channel-frequency` = ?))", $uid, Contact\User::FREQUENCY_NEVER]); - if (($this->selectedTab != TimelineEntity::WHATSHOT) && !is_null($this->accountType)) { + if (($this->selectedTab != ChannelEntity::WHATSHOT) && !is_null($this->accountType)) { $condition = DBA::mergeConditions($condition, ['contact-type' => $this->accountType]); } @@ -359,6 +366,53 @@ class Timeline extends BaseModule return $items; } + private function getUserChannelConditions(int $id, int $uid): array + { + $channel = $this->channelRepository->selectById($id, $uid); + if (empty($channel)) { + return []; + } + + $condition = []; + + if (!empty($channel->circle)) { + if ($channel->circle == -1) { + $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?))", $uid, Contact::SHARING, Contact::FRIEND]; + } elseif ($channel->circle == -2) { + $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER]; + } elseif ($channel->circle > 0) { + $condition = DBA::mergeConditions($condition, ["`owner-id` IN (SELECT `pid` FROM `group_member` INNER JOIN `account-user-view` ON `group_member`.`contact-id` = `account-user-view`.`id` WHERE `gid` = ? AND `account-user-view`.`uid` = ?)", $channel->circle, $uid]); + } + } + + if (!empty($channel->fullTextSearch)) { + $search = $channel->fullTextSearch; + foreach (['from', 'to', 'group', 'tag', 'network', 'visibility'] as $keyword) { + $search = preg_replace('~(' . $keyword . ':.[\w@\.-]+)~', '"$1"', $search); + } + $condition = DBA::mergeConditions($condition, ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE)", $search]); + } + + if (!empty($channel->includeTags)) { + $search = explode(',', mb_strtolower($channel->includeTags)); + $placeholders = substr(str_repeat("?, ", count($search)), 0, -2); + $condition = DBA::mergeConditions($condition, array_merge(["`uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search)); + } + + if (!empty($channel->excludeTags)) { + $search = explode(',', mb_strtolower($channel->excludeTags)); + $placeholders = substr(str_repeat("?, ", count($search)), 0, -2); + $condition = DBA::mergeConditions($condition, array_merge(["NOT `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search)); + } + + if (!empty($channel->mediaType)) { + $condition = DBA::mergeConditions($condition, ["`media-type` & ?", $channel->mediaType]); + } + + // For "addLanguageCondition" to work, the condition must not be empty + return $condition ?: ["true"]; + } + private function addLanguageCondition(int $uid, array $condition): array { $conditions = []; diff --git a/src/Module/Settings/Channels.php b/src/Module/Settings/Channels.php new file mode 100644 index 0000000000..55938e06f9 --- /dev/null +++ b/src/Module/Settings/Channels.php @@ -0,0 +1,188 @@ +. + * + */ + +namespace Friendica\Module\Settings; + +use Friendica\App; +use Friendica\Content\Conversation\Factory; +use Friendica\Content\Conversation\Repository\UserDefinedChannel; +use Friendica\Core\L10n; +use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Model\Circle; +use Friendica\Module\BaseSettings; +use Friendica\Module\Response; +use Friendica\Network\HTTPException; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; + +class Channels extends BaseSettings +{ + /** @var UserDefinedChannel */ + private $channel; + /** @var Factory\UserDefinedChannel */ + private $userDefinedChannel; + + public function __construct(Factory\UserDefinedChannel $userDefinedChannel, UserDefinedChannel $channel, App\Page $page, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->userDefinedChannel = $userDefinedChannel; + $this->channel = $channel; + } + + protected function post(array $request = []) + { + $uid = $this->session->getLocalUserId(); + if (!$uid) { + throw new HTTPException\ForbiddenException($this->t('Permission denied.')); + } + + if (empty($request['edit_channel']) && empty($request['add_channel'])) { + return; + } + + self::checkFormSecurityTokenRedirectOnError('/settings/channels', 'settings_channels'); + + if (!empty($request['add_channel'])) { + $channel = $this->userDefinedChannel->createFromTableRow([ + 'label' => $request['new_label'], + 'description' => $request['new_description'], + 'access-key' => substr(mb_strtolower($request['new_access_key']), 0, 1), + 'uid' => $uid, + 'circle' => (int)$request['new_circle'], + 'include-tags' => $this->cleanTags($request['new_include_tags']), + 'exclude-tags' => $this->cleanTags($request['new_exclude_tags']), + 'full-text-search' => $this->cleanTags($request['new_text_search']), + 'media-type' => ($request['new_image'] ? 1 : 0) | ($request['new_video'] ? 2 : 0) | ($request['new_audio'] ? 4 : 0), + ]); + $saved = $this->channel->save($channel); + $this->logger->debug('New channel added', ['saved' => $saved]); + return; + } + + foreach (array_keys($request['label']) as $id) { + if ($request['delete'][$id]) { + $success = $this->channel->deleteById($id, $uid); + $this->logger->debug('Channel deleted', ['id' => $id, 'success' => $success]); + continue; + } + + $channel = $this->userDefinedChannel->createFromTableRow([ + 'id' => $id, + 'label' => $request['label'][$id], + 'description' => $request['description'][$id], + 'access-key' => substr(mb_strtolower($request['access_key'][$id]), 0, 1), + 'uid' => $uid, + 'circle' => (int)$request['circle'][$id], + 'include-tags' => $this->cleanTags($request['include_tags'][$id]), + 'exclude-tags' => $this->cleanTags($request['exclude_tags'][$id]), + 'full-text-search' => $this->cleanTags($request['text_search'][$id]), + 'media-type' => ($request['image'][$id] ? 1 : 0) | ($request['video'][$id] ? 2 : 0) | ($request['audio'][$id] ? 4 : 0), + ]); + $saved = $this->channel->save($channel); + $this->logger->debug('Save channel', ['id' => $id, 'saved' => $saved]); + } + + $this->baseUrl->redirect('/settings/channels'); + } + + protected function content(array $request = []): string + { + parent::content(); + + $uid = $this->session->getLocalUserId(); + if (!$uid) { + throw new HTTPException\ForbiddenException($this->t('Permission denied.')); + } + + $circles = [ + 0 => $this->l10n->t('Global Community'), + -1 => $this->l10n->t('Following'), + -2 => $this->l10n->t('Followers'), + ]; + + foreach (Circle::getByUserId($uid) as $circle) { + $circles[$circle['id']] = $circle['name']; + } + + $blocklistform = []; + foreach ($this->channel->selectByUid($uid) as $channel) { + $blocklistform[] = [ + 'label' => ["label[$channel->code]", $this->t('Label'), $channel->label, '', $this->t('Required')], + 'description' => ["description[$channel->code]", $this->t("Description"), $channel->description], + 'access_key' => ["access_key[$channel->code]", $this->t("Access Key"), $channel->accessKey], + 'circle' => ["circle[$channel->code]", $this->t('Circle/Channel'), $channel->circle, '', $circles], + 'include_tags' => ["include_tags[$channel->code]", $this->t("Include Tags"), $channel->includeTags], + 'exclude_tags' => ["exclude_tags[$channel->code]", $this->t("Exclude Tags"), $channel->excludeTags], + 'text_search' => ["text_search[$channel->code]", $this->t("Full Text Search"), $channel->fullTextSearch], + 'image' => ["image[$channel->code]", $this->t("Images"), $channel->mediaType & 1], + 'video' => ["video[$channel->code]", $this->t("Videos"), $channel->mediaType & 2], + 'audio' => ["audio[$channel->code]", $this->t("Audio"), $channel->mediaType & 4], + 'delete' => ["delete[$channel->code]", $this->t("Delete channel") . ' (' . $channel->label . ')', false, $this->t("Check to delete this entry from the channel list")] + ]; + } + + $t = Renderer::getMarkupTemplate('settings/channels.tpl'); + return Renderer::replaceMacros($t, [ + 'label' => ["new_label", $this->t('Label'), '', $this->t('Short name for the channel. It is displayed on the channels widget.'), $this->t('Required')], + 'description' => ["new_description", $this->t("Description"), '', $this->t('This should describe the content of the channel in a few word.')], + 'access_key' => ["new_access_key", $this->t("Access Key"), '', $this->t('When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one.')], + 'circle' => ['new_circle', $this->t('Circle/Channel'), 0, $this->t('Select a circle or channel, that your channel should be based on.'), $circles], + 'include_tags' => ["new_include_tags", $this->t("Include Tags"), '', $this->t('Comma separated list of tags. A post will be used when it contains any of the listed tags.')], + 'exclude_tags' => ["new_exclude_tags", $this->t("Exclude Tags"), '', $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.')], + 'text_search' => ["new_text_search", $this->t("Full Text Search"), '', $this->t('Search terms for the body, supports the "boolean mode" operators from MariaDB. See the help for a complete list of operators and additional keywords: %s', 'help/Channels')], + 'image' => ['new_image', $this->t("Images"), false, $this->t("Check to display images in the channel.")], + 'video' => ["new_video", $this->t("Videos"), false, $this->t("Check to display videos in the channel.")], + 'audio' => ["new_audio", $this->t("Audio"), false, $this->t("Check to display audio in the channel.")], + '$l10n' => [ + 'title' => $this->t('Channels'), + 'intro' => $this->t('This page can be used to define your own channels.'), + 'addtitle' => $this->t('Add new entry to the channel list'), + 'addsubmit' => $this->t('Add'), + 'savechanges' => $this->t('Save'), + 'currenttitle' => $this->t('Current Entries in the channel list'), + 'thurl' => $this->t('Blocked server domain pattern'), + 'threason' => $this->t('Reason for the block'), + 'delentry' => $this->t('Delete entry from the channel list'), + 'confirm_delete' => $this->t('Delete entry from the channel list?'), + ], + '$entries' => $blocklistform, + '$baseurl' => $this->baseUrl, + + '$form_security_token' => self::getFormSecurityToken('settings_channels'), + ]); + } + + private function cleanTags(string $tag_list): string + { + $tags = []; + + $tagitems = explode(',', mb_strtolower($tag_list)); + foreach ($tagitems as $tag) { + $tag = trim($tag, '# '); + if (!empty($tag)) { + $tags[] = $tag; + } + } + return implode(',', $tags); + } +} diff --git a/src/Module/Settings/Display.php b/src/Module/Settings/Display.php index 71c4caed59..b5dbf01eb8 100644 --- a/src/Module/Settings/Display.php +++ b/src/Module/Settings/Display.php @@ -22,8 +22,13 @@ namespace Friendica\Module\Settings; use Friendica\App; +use Friendica\Content\Conversation\Collection\Timelines; use Friendica\Content\Text\BBCode; +use Friendica\Content\Conversation\Factory\Channel as ChannelFactory; +use Friendica\Content\Conversation\Factory\Community as CommunityFactory; +use Friendica\Content\Conversation\Factory\Network as NetworkFactory; use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory; +use Friendica\Content\Conversation\Repository; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; use Friendica\Core\L10n; @@ -52,18 +57,30 @@ class Display extends BaseSettings private $app; /** @var SystemMessages */ private $systemMessages; + /** @var ChannelFactory */ + protected $channel; + /** @var Repository\UserDefinedChannel */ + protected $userDefinedChannel; + /** @var CommunityFactory */ + protected $community; + /** @var NetworkFactory */ + protected $network; /** @var TimelineFactory */ protected $timeline; - 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 = []) + public function __construct(Repository\UserDefinedChannel $userDefinedChannel, NetworkFactory $network, CommunityFactory $community, ChannelFactory $channel, 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); - $this->config = $config; - $this->pConfig = $pConfig; - $this->app = $app; - $this->systemMessages = $systemMessages; - $this->timeline = $timeline; + $this->config = $config; + $this->pConfig = $pConfig; + $this->app = $app; + $this->systemMessages = $systemMessages; + $this->timeline = $timeline; + $this->channel = $channel; + $this->community = $community; + $this->network = $network; + $this->userDefinedChannel = $userDefinedChannel; } protected function post(array $request = []) @@ -80,7 +97,8 @@ 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'] : []; + $enable = !empty($request['enable']) ? $request['enable'] : []; + $bookmark = !empty($request['bookmark']) ? $request['bookmark'] : []; $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'; @@ -98,6 +116,20 @@ class Display extends BaseSettings } } + $enabled_timelines = []; + foreach ($enable as $code => $enabled) { + if ($enabled) { + $enabled_timelines[] = $code; + } + } + + $network_timelines = []; + foreach ($bookmark as $code => $bookmarked) { + if ($bookmarked) { + $network_timelines[] = $code; + } + } + $itemspage_network = !empty($request['itemspage_network']) ? intval($request['itemspage_network']) : $this->config->get('system', 'itemspage_network'); @@ -127,6 +159,7 @@ class Display extends BaseSettings $this->pConfig->set($uid, 'system', 'preview_mode' , $preview_mode); $this->pConfig->set($uid, 'system', 'network_timelines' , $network_timelines); + $this->pConfig->set($uid, 'system', 'enabled_timelines' , $enabled_timelines); $this->pConfig->set($uid, 'channel', 'languages' , $channel_languages); $this->pConfig->set($uid, 'calendar', 'first_day_of_week' , $first_day_of_week); @@ -224,10 +257,20 @@ 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))); + $bookmarked_timelines = $this->pConfig->get($uid, 'system', 'network_timelines', $this->getAvailableTimelines($uid, true)->column('code')); + $enabled_timelines = $this->pConfig->get($uid, 'system', 'enabled_timelines', $this->getAvailableTimelines($uid, false)->column('code')); $channel_languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]); $languages = $this->l10n->getAvailableLanguages(true); - $timelines = $this->getAvailableTimelines($uid); + + $timelines = []; + foreach ($this->getAvailableTimelines($uid) as $timeline) { + $timelines[] = [ + 'label' => $timeline->label, + 'description' => $timeline->description, + 'enable' => ["enable[{$timeline->code}]", '', in_array($timeline->code, $enabled_timelines)], + 'bookmark' => ["bookmark[{$timeline->code}]", '', in_array($timeline->code, $bookmarked_timelines)], + ]; + } $first_day_of_week = $this->pConfig->get($uid, 'calendar', 'first_day_of_week', 0); $weekdays = [ @@ -284,7 +327,13 @@ 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'], + '$timeline_label' => $this->t('Label'), + '$timeline_descriptiom' => $this->t('Description'), + '$timeline_enable' => $this->t('Enable'), + '$timeline_bookmark' => $this->t('Bookmark'), + '$timelines' => $timelines, + '$timeline_explanation' => $this->t('Enable timelines that you want to see in the channels widget. Bookmark timelines that you want to see in the top menu.'), + '$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], @@ -292,26 +341,30 @@ class Display extends BaseSettings ]); } - private function getAvailableTimelines(int $uid, bool $only_network = false): array + private function getAvailableTimelines(int $uid, bool $only_network = false): Timelines { $timelines = []; - foreach ($this->timeline->getNetworkFeeds('') as $channel) { - $timelines[$channel->code] = $this->t('%s: %s', $channel->label, $channel->description); + foreach ($this->network->getTimelines('') as $channel) { + $timelines[] = $channel; } if ($only_network) { - return $timelines; + return new Timelines($timelines); } - foreach ($this->timeline->getChannelsForUser($uid) as $channel) { - $timelines[$channel->code] = $this->t('%s: %s', $channel->label, $channel->description); + foreach ($this->channel->getTimelines($uid) as $channel) { + $timelines[] = $channel; } - foreach ($this->timeline->getCommunities(true) as $community) { - $timelines[$community->code] = $this->t('%s: %s', $community->label, $community->description); + foreach ($this->userDefinedChannel->selectByUid($uid) as $channel) { + $timelines[] = $channel; } - return $timelines; + foreach ($this->community->getTimelines(true) as $community) { + $timelines[] = $community; + } + + return new Timelines($timelines); } } diff --git a/src/Module/Update/Channel.php b/src/Module/Update/Channel.php index ac35d9c26b..8435a7cc1b 100644 --- a/src/Module/Update/Channel.php +++ b/src/Module/Update/Channel.php @@ -38,7 +38,7 @@ class Channel extends ChannelModule $o = ''; if ($this->update || $this->force) { - if ($this->timeline->isChannel($this->selectedTab)) { + if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) { $items = $this->getChannelItems(); } else { $items = $this->getCommunityItems(); diff --git a/src/Module/Update/Network.php b/src/Module/Update/Network.php index e8a2dce07d..983680ac0b 100644 --- a/src/Module/Update/Network.php +++ b/src/Module/Update/Network.php @@ -41,9 +41,9 @@ class Network extends NetworkModule System::htmlUpdateExit($o); } - if ($this->timeline->isChannel($this->selectedTab)) { + if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) { $items = $this->getChannelItems(); - } elseif ($this->timeline->isCommunity($this->selectedTab)) { + } elseif ($this->community->isTimeline($this->selectedTab)) { $items = $this->getCommunityItems(); } else { $items = $this->getItems(); diff --git a/src/Object/Post.php b/src/Object/Post.php index dd3074cfa3..4df953cab6 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -156,15 +156,16 @@ class Post /** * Get data in a form usable by a conversation template * - * @param array $conv_responses conversation responses - * @param string $formSecurityToken A security Token to avoid CSF attacks - * @param integer $thread_level default = 1 + * @param array $conv_responses conversation responses + * @param string $formSecurityToken A security Token to avoid CSF attacks + * @param integer $thread_level default = 1 + * @param array $thread_parent Array of parent guid and parent author names * * @return mixed The data requested on success, false on failure * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public function getTemplateData(array $conv_responses, string $formSecurityToken, int $thread_level = 1) + public function getTemplateData(array $conv_responses, string $formSecurityToken, int $thread_level = 1, array $thread_parent = []) { $item = $this->getData(); $edited = false; @@ -496,7 +497,12 @@ class Post $browsershare = null; } + $parent_guid = $thread_parent[$item['thr-parent-id']]['guid'] ?? ''; + $parent_username = $thread_parent[$item['thr-parent-id']]['name'] ?? ''; + $tmp_item = [ + 'parentguid' => $parent_guid, + 'isreplyto' => DI::l10n()->t('in reply to %s', $parent_username), 'template' => $this->getTemplate(), 'type' => implode('', array_slice(explode('/', $item['verb']), -1)), 'comment_firstcollapsed' => false, @@ -609,8 +615,10 @@ class Post $children = $this->getChildren(); $nb_children = count($children); if ($nb_children > 0) { + $thread_parent[$item['uri-id']] = ['guid' => $item['guid'], 'name' => $item['author-name']]; foreach ($children as $child) { - $result['children'][] = $child->getTemplateData($conv_responses, $formSecurityToken, $thread_level + 1); + $thread_parent[$child->getDataValue('uri-id')] = ['guid' => $child->getDataValue('guid'), 'name' => $child->getDataValue('author-name')]; + $result['children'][] = $child->getTemplateData($conv_responses, $formSecurityToken, $thread_level + 1, $thread_parent); } // Collapse diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 682c719919..3439b901d3 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -56,7 +56,7 @@ use Friendica\Database\DBA; // This file is required several times during the test in DbaDefinition which justifies this condition if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1535); + define('DB_UPDATE_VERSION', 1536); } return [ @@ -551,6 +551,25 @@ return [ "k_expires" => ["k", "expires"], ] ], + "channel" => [ + "comment" => "User defined Channels", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => ""], + "uid" => ["type" => "mediumint unsigned", "not null" => "1", "foreign" => ["user" => "uid"], "comment" => "User id"], + "label" => ["type" => "varchar(64)", "not null" => "1", "comment" => "Channel label"], + "description" => ["type" => "varchar(64)", "comment" => "Channel description"], + "circle" => ["type" => "int", "comment" => "Circle or channel that this channel is based on"], + "access-key" => ["type" => "varchar(1)", "comment" => "Access key"], + "include-tags" => ["type" => "varchar(255)", "comment" => "Comma separated list of tags that will be included in the channel"], + "exclude-tags" => ["type" => "varchar(255)", "comment" => "Comma separated list of tags that aren't allowed in the channel"], + "full-text-search" => ["type" => "varchar(255)", "comment" => "Full text search pattern, see https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode"], + "media-type" => ["type" => "smallint unsigned", "comment" => "Filtered media types"], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "uid" => ["uid"], + ] + ], "config" => [ "comment" => "main configuration storage", "fields" => [ @@ -1332,6 +1351,7 @@ return [ "contact-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Person, organisation, news, community, relay"], "media-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Type of media in a bit array (1 = image, 2 = video, 4 = audio"], "language" => ["type" => "varbinary(128)", "comment" => "Language information about this post"], + "searchtext" => ["type" => "mediumtext", "comment" => "Simplified text for the full text search"], "created" => ["type" => "datetime", "comment" => ""], "restricted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "If true, this post is either unlisted or not from a federated network"], "comments" => ["type" => "mediumint unsigned", "comment" => "Number of comments"], @@ -1341,6 +1361,7 @@ return [ "PRIMARY" => ["uri-id"], "owner-id" => ["owner-id"], "created" => ["created"], + "searchtext" => ["FULLTEXT", "searchtext"], ] ], "post-history" => [ diff --git a/static/dbview.config.php b/static/dbview.config.php index d688cef3fa..057c8fccc5 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -184,6 +184,7 @@ "author-blocked" => ["author", "blocked"], "author-hidden" => ["author", "hidden"], "author-updated" => ["author", "updated"], + "author-contact-type" => ["author", "contact-type"], "author-gsid" => ["author", "gsid"], "author-baseurl" => ["author", "baseurl"], "owner-id" => ["post-user", "owner-id"], @@ -366,6 +367,7 @@ "author-blocked" => ["author", "blocked"], "author-hidden" => ["author", "hidden"], "author-updated" => ["author", "updated"], + "author-contact-type" => ["author", "contact-type"], "author-gsid" => ["author", "gsid"], "owner-id" => ["post-thread-user", "owner-id"], "owner-uri-id" => ["owner", "uri-id"], @@ -532,6 +534,7 @@ "author-blocked" => ["author", "blocked"], "author-hidden" => ["author", "hidden"], "author-updated" => ["author", "updated"], + "author-contact-type" => ["author", "contact-type"], "author-gsid" => ["author", "gsid"], "owner-id" => ["post", "owner-id"], "owner-uri-id" => ["owner", "uri-id"], @@ -675,6 +678,7 @@ "author-blocked" => ["author", "blocked"], "author-hidden" => ["author", "hidden"], "author-updated" => ["author", "updated"], + "author-contact-type" => ["author", "contact-type"], "author-gsid" => ["author", "gsid"], "owner-id" => ["post-thread", "owner-id"], "owner-uri-id" => ["owner", "uri-id"], diff --git a/static/defaults.config.php b/static/defaults.config.php index ceb60c4151..b639e19166 100644 --- a/static/defaults.config.php +++ b/static/defaults.config.php @@ -798,9 +798,13 @@ return [ ], 'channel' => [ // engagement_hours (Integer) - // Number of hours posts are held in the engagement table + // Maximum age of incoming posts for the engagement table, when the engagement post limit is 0 or hasn't been reached yet. 'engagement_hours' => 24, + // engagement_post_limit (Integer) + // NUmber of posts that are held in the engagement table + 'engagement_post_limit' => 20000, + // interaction_score_days (Integer) // Number of days that are used to calculate the interaction score. 'interaction_score_days' => 30, diff --git a/static/routes.config.php b/static/routes.config.php index afad469fd3..1b708140ca 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -651,6 +651,7 @@ return [ '/{open}' => [Module\Settings\Account::class, [R::GET, R::POST]], ], '/addons[/{addon}]' => [Module\Settings\Addons::class, [R::GET, R::POST]], + '/channels' => [Module\Settings\Channels::class, [R::GET, R::POST]], '/connectors[/{connector}]' => [Module\Settings\Connectors::class, [R::GET, R::POST]], '/delegation[/{action}/{user_id}]' => [Module\Settings\Delegation::class, [R::GET, R::POST]], '/display' => [Module\Settings\Display::class, [R::GET, R::POST]], diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index c154526238..249c15e2f0 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-rc\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-10-07 18:46+0200\n" +"POT-Creation-Date: 2023-10-07 19:00+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -68,8 +68,9 @@ msgstr "" #: src/Module/Register.php:90 src/Module/Register.php:206 #: 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/Channels.php:56 src/Module/Settings/Channels.php:114 #: src/Module/Settings/Delegation.php:41 src/Module/Settings/Delegation.php:71 -#: src/Module/Settings/Display.php:73 src/Module/Settings/Display.php:160 +#: src/Module/Settings/Display.php:90 src/Module/Settings/Display.php:193 #: 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 @@ -294,7 +295,7 @@ msgstr "" #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 #: 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 +#: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:584 msgid "Please wait" msgstr "" @@ -316,7 +317,7 @@ msgstr "" #: src/Module/Moderation/Report/Create.php:263 #: src/Module/Profile/Profile.php:274 src/Module/Profile/UnkMail.php:155 #: src/Module/Settings/Profile/Index.php:257 -#: src/Module/Settings/Server/Action.php:79 src/Object/Post.php:1095 +#: src/Module/Settings/Server/Action.php:79 src/Object/Post.php:1103 #: view/theme/duepuntozero/config.php:85 view/theme/frio/config.php:171 #: view/theme/quattro/config.php:87 view/theme/vier/config.php:135 msgid "Submit" @@ -384,7 +385,7 @@ msgstr "" #: mod/notes.php:57 src/Content/Text/HTML.php:859 #: src/Module/Admin/Storage.php:142 src/Module/Filer/SaveTag.php:74 -#: src/Module/Post/Edit.php:129 +#: src/Module/Post/Edit.php:129 src/Module/Settings/Channels.php:161 msgid "Save" msgstr "" @@ -449,7 +450,7 @@ msgstr "" msgid "%1$s was tagged in %2$s by %3$s" msgstr "" -#: mod/photos.php:582 src/Module/Conversation/Community.php:159 +#: 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." @@ -601,30 +602,30 @@ msgstr "" #: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275 #: src/Module/Contact.php:619 src/Module/Item/Compose.php:188 -#: src/Object/Post.php:1092 +#: src/Object/Post.php:1100 msgid "This is you" msgstr "" #: mod/photos.php:1141 mod/photos.php:1197 mod/photos.php:1277 -#: src/Module/Moderation/Reports.php:95 src/Object/Post.php:572 -#: src/Object/Post.php:1094 +#: src/Module/Moderation/Reports.php:95 src/Object/Post.php:578 +#: src/Object/Post.php:1102 msgid "Comment" msgstr "" #: mod/photos.php:1143 mod/photos.php:1199 mod/photos.php:1279 #: src/Content/Conversation.php:414 src/Module/Calendar/Event/Form.php:248 #: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165 -#: src/Object/Post.php:1108 +#: src/Object/Post.php:1116 msgid "Preview" msgstr "" #: mod/photos.php:1144 src/Content/Conversation.php:367 -#: src/Module/Post/Edit.php:130 src/Object/Post.php:1096 +#: src/Module/Post/Edit.php:130 src/Object/Post.php:1104 msgid "Loading..." msgstr "" #: mod/photos.php:1236 src/Content/Conversation.php:1464 -#: src/Object/Post.php:260 +#: src/Object/Post.php:261 msgid "Select" msgstr "" @@ -637,19 +638,19 @@ msgstr "" msgid "Delete" msgstr "" -#: mod/photos.php:1298 src/Object/Post.php:408 +#: mod/photos.php:1298 src/Object/Post.php:409 msgid "Like" msgstr "" -#: mod/photos.php:1299 src/Object/Post.php:408 +#: mod/photos.php:1299 src/Object/Post.php:409 msgid "I like this (toggle)" msgstr "" -#: mod/photos.php:1300 src/Object/Post.php:409 +#: mod/photos.php:1300 src/Object/Post.php:410 msgid "Dislike" msgstr "" -#: mod/photos.php:1302 src/Object/Post.php:409 +#: mod/photos.php:1302 src/Object/Post.php:410 msgid "I don't like this (toggle)" msgstr "" @@ -793,13 +794,15 @@ msgstr "" msgid "All contacts" msgstr "" -#: src/BaseModule.php:435 src/Content/Conversation/Factory/Timeline.php:62 +#: src/BaseModule.php:435 src/Content/Conversation/Factory/Channel.php:45 #: 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 +#: src/Module/Settings/Channels.php:120 msgid "Followers" msgstr "" #: src/BaseModule.php:440 src/Content/Widget.php:240 src/Module/Contact.php:418 +#: src/Module/Settings/Channels.php:119 msgid "Following" msgstr "" @@ -1240,7 +1243,7 @@ msgid "Visible to everybody" msgstr "" #: src/Content/Conversation.php:337 src/Module/Item/Compose.php:200 -#: src/Object/Post.php:1107 +#: src/Object/Post.php:1115 msgid "Please enter a image/video/audio/webpage URL:" msgstr "" @@ -1285,52 +1288,52 @@ msgid "attach file" msgstr "" #: src/Content/Conversation.php:372 src/Module/Item/Compose.php:190 -#: src/Module/Post/Edit.php:171 src/Object/Post.php:1097 +#: src/Module/Post/Edit.php:171 src/Object/Post.php:1105 msgid "Bold" msgstr "" #: src/Content/Conversation.php:373 src/Module/Item/Compose.php:191 -#: src/Module/Post/Edit.php:172 src/Object/Post.php:1098 +#: src/Module/Post/Edit.php:172 src/Object/Post.php:1106 msgid "Italic" msgstr "" #: src/Content/Conversation.php:374 src/Module/Item/Compose.php:192 -#: src/Module/Post/Edit.php:173 src/Object/Post.php:1099 +#: src/Module/Post/Edit.php:173 src/Object/Post.php:1107 msgid "Underline" msgstr "" #: src/Content/Conversation.php:375 src/Module/Item/Compose.php:193 -#: src/Module/Post/Edit.php:174 src/Object/Post.php:1101 +#: src/Module/Post/Edit.php:174 src/Object/Post.php:1109 msgid "Quote" msgstr "" #: src/Content/Conversation.php:376 src/Module/Item/Compose.php:194 -#: src/Module/Post/Edit.php:175 src/Object/Post.php:1102 +#: src/Module/Post/Edit.php:175 src/Object/Post.php:1110 msgid "Add emojis" msgstr "" #: src/Content/Conversation.php:377 src/Module/Item/Compose.php:195 -#: src/Object/Post.php:1100 +#: src/Object/Post.php:1108 msgid "Content Warning" msgstr "" #: src/Content/Conversation.php:378 src/Module/Item/Compose.php:196 -#: src/Module/Post/Edit.php:176 src/Object/Post.php:1103 +#: src/Module/Post/Edit.php:176 src/Object/Post.php:1111 msgid "Code" msgstr "" #: src/Content/Conversation.php:379 src/Module/Item/Compose.php:197 -#: src/Object/Post.php:1104 +#: src/Object/Post.php:1112 msgid "Image" msgstr "" #: src/Content/Conversation.php:380 src/Module/Item/Compose.php:198 -#: src/Module/Post/Edit.php:177 src/Object/Post.php:1105 +#: src/Module/Post/Edit.php:177 src/Object/Post.php:1113 msgid "Link" msgstr "" #: src/Content/Conversation.php:381 src/Module/Item/Compose.php:199 -#: src/Module/Post/Edit.php:178 src/Object/Post.php:1106 +#: src/Module/Post/Edit.php:178 src/Object/Post.php:1114 msgid "Link or Media" msgstr "" @@ -1377,8 +1380,8 @@ msgstr "" msgid "Public post" msgstr "" -#: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:125 -#: src/Model/Profile.php:467 src/Module/Admin/Logs/View.php:92 +#: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:132 +#: src/Model/Profile.php:479 src/Module/Admin/Logs/View.php:92 #: src/Module/Post/Edit.php:181 msgid "Message" msgstr "" @@ -1486,25 +1489,25 @@ msgstr "" msgid "Pushed to us" msgstr "" -#: src/Content/Conversation.php:1492 src/Object/Post.php:247 +#: src/Content/Conversation.php:1492 src/Object/Post.php:248 msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1509 src/Object/Post.php:521 -#: src/Object/Post.php:522 +#: src/Content/Conversation.php:1509 src/Object/Post.php:527 +#: src/Object/Post.php:528 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1522 src/Object/Post.php:509 +#: src/Content/Conversation.php:1522 src/Object/Post.php:515 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1523 src/Object/Post.php:510 +#: src/Content/Conversation.php:1523 src/Object/Post.php:516 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1531 src/Object/Post.php:535 +#: src/Content/Conversation.php:1531 src/Object/Post.php:541 #, php-format msgid "%s from %s" msgstr "" @@ -1513,117 +1516,121 @@ msgstr "" msgid "View in context" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:59 +#: src/Content/Conversation/Factory/Channel.php:42 msgid "For you" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:59 +#: src/Content/Conversation/Factory/Channel.php:42 msgid "Posts from contacts you interact with and who interact with you" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:60 +#: src/Content/Conversation/Factory/Channel.php:43 msgid "What's Hot" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:60 +#: src/Content/Conversation/Factory/Channel.php:43 msgid "Posts with a lot of interactions" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:61 +#: src/Content/Conversation/Factory/Channel.php:44 #, php-format msgid "Posts in %s" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:62 +#: src/Content/Conversation/Factory/Channel.php:45 msgid "Posts from your followers that you don't follow" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:63 +#: src/Content/Conversation/Factory/Channel.php:46 msgid "Sharers of sharers" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:63 +#: src/Content/Conversation/Factory/Channel.php:46 msgid "Posts from accounts that are followed by accounts that you follow" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:64 +#: src/Content/Conversation/Factory/Channel.php:47 +#: src/Module/Settings/Channels.php:137 src/Module/Settings/Channels.php:153 msgid "Images" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:64 +#: src/Content/Conversation/Factory/Channel.php:47 msgid "Posts with images" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:65 +#: src/Content/Conversation/Factory/Channel.php:48 +#: src/Module/Settings/Channels.php:139 src/Module/Settings/Channels.php:155 msgid "Audio" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:65 +#: src/Content/Conversation/Factory/Channel.php:48 msgid "Posts with audio" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:66 +#: src/Content/Conversation/Factory/Channel.php:49 +#: src/Module/Settings/Channels.php:138 src/Module/Settings/Channels.php:154 msgid "Videos" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:66 +#: src/Content/Conversation/Factory/Channel.php:49 msgid "Posts with videos" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:84 +#: src/Content/Conversation/Factory/Community.php:43 msgid "Local Community" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:84 +#: src/Content/Conversation/Factory/Community.php:43 msgid "Posts from local users on this server" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:88 +#: src/Content/Conversation/Factory/Community.php:47 +#: src/Module/Settings/Channels.php:118 msgid "Global Community" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:88 +#: src/Content/Conversation/Factory/Community.php:47 msgid "Posts from users of the whole federated network" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:102 +#: src/Content/Conversation/Factory/Network.php:38 msgid "Latest Activity" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:102 +#: src/Content/Conversation/Factory/Network.php:38 msgid "Sort by latest activity" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:103 +#: src/Content/Conversation/Factory/Network.php:39 msgid "Latest Posts" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:103 +#: src/Content/Conversation/Factory/Network.php:39 msgid "Sort by post received date" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:104 +#: src/Content/Conversation/Factory/Network.php:40 msgid "Latest Creation" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:104 +#: src/Content/Conversation/Factory/Network.php:40 msgid "Sort by post creation date" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:105 +#: src/Content/Conversation/Factory/Network.php:41 #: src/Module/Settings/Profile/Index.php:260 msgid "Personal" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:105 +#: src/Content/Conversation/Factory/Network.php:41 msgid "Posts that mention or involve you" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:106 src/Object/Post.php:380 +#: src/Content/Conversation/Factory/Network.php:42 src/Object/Post.php:381 msgid "Starred" msgstr "" -#: src/Content/Conversation/Factory/Timeline.php:106 +#: src/Content/Conversation/Factory/Network.php:42 msgid "Favourite Posts" msgstr "" @@ -1784,30 +1791,31 @@ msgstr "" msgid "Follow Thread" msgstr "" -#: src/Content/Item.php:429 src/Model/Contact.php:1246 +#: src/Content/Item.php:429 src/Model/Contact.php:1242 msgid "View Status" msgstr "" #: src/Content/Item.php:430 src/Content/Item.php:451 src/Model/Contact.php:1176 -#: src/Model/Contact.php:1237 src/Model/Contact.php:1247 +#: src/Model/Contact.php:1233 src/Model/Contact.php:1243 #: src/Module/Directory.php:157 src/Module/Settings/Profile/Index.php:259 msgid "View Profile" msgstr "" -#: src/Content/Item.php:431 src/Model/Contact.php:1248 +#: src/Content/Item.php:431 src/Model/Contact.php:1244 msgid "View Photos" msgstr "" -#: src/Content/Item.php:432 src/Model/Contact.php:1215 +#: src/Content/Item.php:432 src/Model/Contact.php:1211 +#: src/Model/Profile.php:464 msgid "Network Posts" msgstr "" -#: src/Content/Item.php:433 src/Model/Contact.php:1239 -#: src/Model/Contact.php:1250 +#: src/Content/Item.php:433 src/Model/Contact.php:1235 +#: src/Model/Contact.php:1246 msgid "View Contact" msgstr "" -#: src/Content/Item.php:434 src/Model/Contact.php:1251 +#: src/Content/Item.php:434 src/Model/Contact.php:1247 msgid "Send PM" msgstr "" @@ -1832,17 +1840,17 @@ msgstr "" msgid "Collapse" msgstr "" -#: src/Content/Item.php:438 src/Object/Post.php:288 +#: src/Content/Item.php:438 src/Object/Post.php:289 #, php-format msgid "Ignore %s server" msgstr "" -#: src/Content/Item.php:442 src/Object/Post.php:490 +#: src/Content/Item.php:442 src/Object/Post.php:491 msgid "Languages" msgstr "" #: src/Content/Item.php:448 src/Content/Widget.php:80 -#: src/Model/Contact.php:1240 src/Model/Contact.php:1252 +#: src/Model/Contact.php:1236 src/Model/Contact.php:1248 #: src/Module/Contact/Follow.php:167 view/theme/vier/theme.php:195 msgid "Connect/Follow" msgstr "" @@ -1926,7 +1934,7 @@ msgstr "" #: src/Content/Nav.php:233 src/Content/Nav.php:293 #: 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:267 view/theme/frio/theme.php:236 +#: src/Module/Settings/Display.php:310 view/theme/frio/theme.php:236 #: view/theme/frio/theme.php:240 msgid "Calendar" msgstr "" @@ -2098,7 +2106,7 @@ msgid "Manage other pages" msgstr "" #: src/Content/Nav.php:327 src/Module/Admin/Addons/Details.php:114 -#: src/Module/Admin/Themes/Details.php:93 src/Module/BaseSettings.php:175 +#: src/Module/Admin/Themes/Details.php:93 src/Module/BaseSettings.php:182 #: src/Module/Welcome.php:52 view/theme/frio/theme.php:242 msgid "Settings" msgstr "" @@ -2213,8 +2221,8 @@ msgstr "" msgid "The end" msgstr "" -#: src/Content/Text/HTML.php:859 src/Content/Widget/VCard.php:121 -#: src/Model/Profile.php:461 src/Module/Contact/Profile.php:471 +#: src/Content/Text/HTML.php:859 src/Content/Widget/VCard.php:128 +#: src/Model/Profile.php:473 src/Module/Contact/Profile.php:471 msgid "Follow" msgstr "" @@ -2349,7 +2357,7 @@ msgstr "" msgid "Organisations" msgstr "" -#: src/Content/Widget.php:536 src/Model/Contact.php:1718 +#: src/Content/Widget.php:536 src/Model/Contact.php:1714 msgid "News" msgstr "" @@ -2361,7 +2369,8 @@ msgstr "" msgid "All" msgstr "" -#: src/Content/Widget.php:573 src/Module/Settings/Display.php:266 +#: src/Content/Widget.php:591 src/Module/BaseSettings.php:125 +#: src/Module/Settings/Channels.php:157 src/Module/Settings/Display.php:309 msgid "Channels" msgstr "" @@ -2411,17 +2420,32 @@ msgstr[1] "" msgid "More Trending Tags" msgstr "" -#: src/Content/Widget/VCard.php:114 src/Model/Profile.php:376 +#: src/Content/Widget/VCard.php:105 src/Model/Contact.php:1204 +#: src/Model/Profile.php:457 +msgid "Post to group" +msgstr "" + +#: src/Content/Widget/VCard.php:107 src/Model/Contact.php:1206 +#: src/Model/Profile.php:459 +msgid "View group" +msgstr "" + +#: src/Content/Widget/VCard.php:110 src/Model/Contact.php:1209 +#: src/Model/Profile.php:462 src/Module/Moderation/Item/Source.php:85 +msgid "Mention" +msgstr "" + +#: src/Content/Widget/VCard.php:121 src/Model/Profile.php:376 #: src/Module/Contact/Profile.php:408 src/Module/Profile/Profile.php:199 msgid "XMPP:" msgstr "" -#: src/Content/Widget/VCard.php:115 src/Model/Profile.php:377 +#: src/Content/Widget/VCard.php:122 src/Model/Profile.php:377 #: src/Module/Contact/Profile.php:410 src/Module/Profile/Profile.php:203 msgid "Matrix:" msgstr "" -#: src/Content/Widget/VCard.php:116 src/Model/Event.php:82 +#: src/Content/Widget/VCard.php:123 src/Model/Event.php:82 #: src/Model/Event.php:109 src/Model/Event.php:471 src/Model/Event.php:963 #: src/Model/Profile.php:371 src/Module/Contact/Profile.php:406 #: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 @@ -2429,30 +2453,17 @@ msgstr "" msgid "Location:" msgstr "" -#: src/Content/Widget/VCard.php:119 src/Model/Profile.php:474 +#: src/Content/Widget/VCard.php:126 src/Model/Profile.php:486 #: src/Module/Notifications/Introductions.php:201 msgid "Network:" msgstr "" -#: src/Content/Widget/VCard.php:123 src/Model/Contact.php:1241 -#: src/Model/Contact.php:1253 src/Model/Profile.php:463 +#: src/Content/Widget/VCard.php:130 src/Model/Contact.php:1237 +#: src/Model/Contact.php:1249 src/Model/Profile.php:475 #: src/Module/Contact/Profile.php:463 msgid "Unfollow" msgstr "" -#: src/Content/Widget/VCard.php:127 src/Model/Contact.php:1205 -#: src/Module/Moderation/Item/Source.php:85 -msgid "Mention" -msgstr "" - -#: src/Content/Widget/VCard.php:128 src/Model/Contact.php:1202 -msgid "Post to group" -msgstr "" - -#: src/Content/Widget/VCard.php:129 src/Model/Contact.php:1212 -msgid "View group" -msgstr "" - #: src/Core/ACL.php:166 src/Module/Profile/Profile.php:269 msgid "Yourself" msgstr "" @@ -2833,37 +2844,37 @@ msgid "Could not connect to database." msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:430 -#: src/Module/Settings/Display.php:235 +#: src/Module/Settings/Display.php:278 msgid "Monday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:431 -#: src/Module/Settings/Display.php:236 +#: src/Module/Settings/Display.php:279 msgid "Tuesday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:432 -#: src/Module/Settings/Display.php:237 +#: src/Module/Settings/Display.php:280 msgid "Wednesday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:433 -#: src/Module/Settings/Display.php:238 +#: src/Module/Settings/Display.php:281 msgid "Thursday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:434 -#: src/Module/Settings/Display.php:239 +#: src/Module/Settings/Display.php:282 msgid "Friday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:435 -#: src/Module/Settings/Display.php:240 +#: src/Module/Settings/Display.php:283 msgid "Saturday" msgstr "" #: src/Core/L10n.php:507 src/Model/Event.php:429 -#: src/Module/Settings/Display.php:234 +#: src/Module/Settings/Display.php:277 msgid "Sunday" msgstr "" @@ -3201,82 +3212,82 @@ msgstr "" msgid "Edit circles" msgstr "" -#: src/Model/Contact.php:1260 src/Module/Moderation/Users/Pending.php:102 +#: src/Model/Contact.php:1256 src/Module/Moderation/Users/Pending.php:102 #: src/Module/Notifications/Introductions.php:132 #: src/Module/Notifications/Introductions.php:204 msgid "Approve" msgstr "" -#: src/Model/Contact.php:1714 +#: src/Model/Contact.php:1710 msgid "Organisation" msgstr "" -#: src/Model/Contact.php:1722 +#: src/Model/Contact.php:1718 msgid "Group" msgstr "" -#: src/Model/Contact.php:3025 +#: src/Model/Contact.php:3021 msgid "Disallowed profile URL." msgstr "" -#: src/Model/Contact.php:3030 src/Module/Friendica.php:101 +#: src/Model/Contact.php:3026 src/Module/Friendica.php:101 msgid "Blocked domain" msgstr "" -#: src/Model/Contact.php:3035 +#: src/Model/Contact.php:3031 msgid "Connect URL missing." msgstr "" -#: src/Model/Contact.php:3044 +#: src/Model/Contact.php:3040 msgid "" "The contact could not be added. Please check the relevant network " "credentials in your Settings -> Social Networks page." msgstr "" -#: src/Model/Contact.php:3062 +#: src/Model/Contact.php:3058 #, php-format msgid "Expected network %s does not match actual network %s" msgstr "" -#: src/Model/Contact.php:3079 +#: src/Model/Contact.php:3075 msgid "The profile address specified does not provide adequate information." msgstr "" -#: src/Model/Contact.php:3081 +#: src/Model/Contact.php:3077 msgid "No compatible communication protocols or feeds were discovered." msgstr "" -#: src/Model/Contact.php:3084 +#: src/Model/Contact.php:3080 msgid "An author or name was not found." msgstr "" -#: src/Model/Contact.php:3087 +#: src/Model/Contact.php:3083 msgid "No browser URL could be matched to this address." msgstr "" -#: src/Model/Contact.php:3090 +#: src/Model/Contact.php:3086 msgid "" "Unable to match @-style Identity Address with a known protocol or email " "contact." msgstr "" -#: src/Model/Contact.php:3091 +#: src/Model/Contact.php:3087 msgid "Use mailto: in front of address to force email check." msgstr "" -#: src/Model/Contact.php:3097 +#: src/Model/Contact.php:3093 msgid "" "The profile address specified belongs to a network which has been disabled " "on this site." msgstr "" -#: src/Model/Contact.php:3102 +#: src/Model/Contact.php:3098 msgid "" "Limited profile. This person will be unable to receive direct/personal " "notifications from you." msgstr "" -#: src/Model/Contact.php:3168 +#: src/Model/Contact.php:3164 msgid "Unable to retrieve contact information." msgstr "" @@ -3308,17 +3319,17 @@ msgid "today" msgstr "" #: src/Model/Event.php:463 src/Module/Calendar/Show.php:129 -#: src/Module/Settings/Display.php:245 src/Util/Temporal.php:353 +#: src/Module/Settings/Display.php:288 src/Util/Temporal.php:353 msgid "month" msgstr "" #: src/Model/Event.php:464 src/Module/Calendar/Show.php:130 -#: src/Module/Settings/Display.php:246 src/Util/Temporal.php:354 +#: src/Module/Settings/Display.php:289 src/Util/Temporal.php:354 msgid "week" msgstr "" #: src/Model/Event.php:465 src/Module/Calendar/Show.php:131 -#: src/Module/Settings/Display.php:247 src/Util/Temporal.php:355 +#: src/Module/Settings/Display.php:290 src/Util/Temporal.php:355 msgid "day" msgstr "" @@ -3486,130 +3497,130 @@ msgstr "" msgid "About:" msgstr "" -#: src/Model/Profile.php:465 +#: src/Model/Profile.php:477 msgid "Atom feed" msgstr "" -#: src/Model/Profile.php:472 +#: src/Model/Profile.php:484 msgid "This website has been verified to belong to the same person." msgstr "" -#: src/Model/Profile.php:509 +#: src/Model/Profile.php:525 msgid "F d" msgstr "" -#: src/Model/Profile.php:573 src/Model/Profile.php:662 +#: src/Model/Profile.php:589 src/Model/Profile.php:678 msgid "[today]" msgstr "" -#: src/Model/Profile.php:582 +#: src/Model/Profile.php:598 msgid "Birthday Reminders" msgstr "" -#: src/Model/Profile.php:583 +#: src/Model/Profile.php:599 msgid "Birthdays this week:" msgstr "" -#: src/Model/Profile.php:611 +#: src/Model/Profile.php:627 msgid "g A l F d" msgstr "" -#: src/Model/Profile.php:649 +#: src/Model/Profile.php:665 msgid "[No description]" msgstr "" -#: src/Model/Profile.php:675 +#: src/Model/Profile.php:691 msgid "Event Reminders" msgstr "" -#: src/Model/Profile.php:676 +#: src/Model/Profile.php:692 msgid "Upcoming events the next 7 days:" msgstr "" -#: src/Model/Profile.php:875 +#: src/Model/Profile.php:891 #, php-format msgid "OpenWebAuth: %1$s welcomes %2$s" msgstr "" -#: src/Model/Profile.php:1015 +#: src/Model/Profile.php:1031 msgid "Hometown:" msgstr "" -#: src/Model/Profile.php:1016 +#: src/Model/Profile.php:1032 msgid "Marital Status:" msgstr "" -#: src/Model/Profile.php:1017 +#: src/Model/Profile.php:1033 msgid "With:" msgstr "" -#: src/Model/Profile.php:1018 +#: src/Model/Profile.php:1034 msgid "Since:" msgstr "" -#: src/Model/Profile.php:1019 +#: src/Model/Profile.php:1035 msgid "Sexual Preference:" msgstr "" -#: src/Model/Profile.php:1020 +#: src/Model/Profile.php:1036 msgid "Political Views:" msgstr "" -#: src/Model/Profile.php:1021 +#: src/Model/Profile.php:1037 msgid "Religious Views:" msgstr "" -#: src/Model/Profile.php:1022 +#: src/Model/Profile.php:1038 msgid "Likes:" msgstr "" -#: src/Model/Profile.php:1023 +#: src/Model/Profile.php:1039 msgid "Dislikes:" msgstr "" -#: src/Model/Profile.php:1024 +#: src/Model/Profile.php:1040 msgid "Title/Description:" msgstr "" -#: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:197 +#: src/Model/Profile.php:1041 src/Module/Admin/Summary.php:197 #: src/Module/Moderation/Report/Create.php:280 #: src/Module/Moderation/Summary.php:77 msgid "Summary" msgstr "" -#: src/Model/Profile.php:1026 +#: src/Model/Profile.php:1042 msgid "Musical interests" msgstr "" -#: src/Model/Profile.php:1027 +#: src/Model/Profile.php:1043 msgid "Books, literature" msgstr "" -#: src/Model/Profile.php:1028 +#: src/Model/Profile.php:1044 msgid "Television" msgstr "" -#: src/Model/Profile.php:1029 +#: src/Model/Profile.php:1045 msgid "Film/dance/culture/entertainment" msgstr "" -#: src/Model/Profile.php:1030 +#: src/Model/Profile.php:1046 msgid "Hobbies/Interests" msgstr "" -#: src/Model/Profile.php:1031 +#: src/Model/Profile.php:1047 msgid "Love/romance" msgstr "" -#: src/Model/Profile.php:1032 +#: src/Model/Profile.php:1048 msgid "Work/employment" msgstr "" -#: src/Model/Profile.php:1033 +#: src/Model/Profile.php:1049 msgid "School/education" msgstr "" -#: src/Model/Profile.php:1034 +#: src/Model/Profile.php:1050 msgid "Contact information and Social Networks" msgstr "" @@ -3891,7 +3902,7 @@ msgid "Disable" msgstr "" #: src/Module/Admin/Addons/Details.php:91 -#: src/Module/Admin/Themes/Details.php:49 +#: src/Module/Admin/Themes/Details.php:49 src/Module/Settings/Display.php:332 msgid "Enable" msgstr "" @@ -3907,7 +3918,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:132 +#: src/Module/BaseAdmin.php:92 src/Module/BaseSettings.php:139 msgid "Addons" msgstr "" @@ -3941,7 +3952,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:260 +#: src/Module/Settings/Delegation.php:171 src/Module/Settings/Display.php:303 #: src/Module/Settings/Features.php:76 msgid "Save Settings" msgstr "" @@ -4302,11 +4313,11 @@ msgstr "" msgid "%s is no valid input for maximum image size" msgstr "" -#: src/Module/Admin/Site.php:313 src/Module/Settings/Display.php:178 +#: src/Module/Admin/Site.php:313 src/Module/Settings/Display.php:211 msgid "No special theme for mobile devices" msgstr "" -#: src/Module/Admin/Site.php:330 src/Module/Settings/Display.php:188 +#: src/Module/Admin/Site.php:330 src/Module/Settings/Display.php:221 #, php-format msgid "%s - (Experimental)" msgstr "" @@ -5775,27 +5786,27 @@ msgstr "" msgid "Display" msgstr "" -#: src/Module/BaseSettings.php:125 src/Module/Settings/Connectors.php:204 +#: src/Module/BaseSettings.php:132 src/Module/Settings/Connectors.php:204 msgid "Social Networks" msgstr "" -#: src/Module/BaseSettings.php:139 src/Module/Settings/Delegation.php:172 +#: src/Module/BaseSettings.php:146 src/Module/Settings/Delegation.php:172 msgid "Manage Accounts" msgstr "" -#: src/Module/BaseSettings.php:146 +#: src/Module/BaseSettings.php:153 msgid "Connected apps" msgstr "" -#: src/Module/BaseSettings.php:153 +#: src/Module/BaseSettings.php:160 msgid "Remote servers" msgstr "" -#: src/Module/BaseSettings.php:160 src/Module/Settings/UserExport.php:98 +#: src/Module/BaseSettings.php:167 src/Module/Settings/UserExport.php:98 msgid "Export personal data" msgstr "" -#: src/Module/BaseSettings.php:167 +#: src/Module/BaseSettings.php:174 msgid "Remove account" msgstr "" @@ -5854,6 +5865,7 @@ msgstr "" #: src/Module/Moderation/Blocklist/Server/Index.php:116 #: src/Module/Moderation/Item/Delete.php:67 src/Module/Register.php:148 #: src/Module/Security/TwoFactor/Verify.php:101 +#: src/Module/Settings/Channels.php:130 src/Module/Settings/Channels.php:146 #: src/Module/Settings/TwoFactor/Index.php:140 #: src/Module/Settings/TwoFactor/Verify.php:155 msgid "Required" @@ -5915,7 +5927,7 @@ msgstr "" msgid "Create New Event" msgstr "" -#: src/Module/Calendar/Show.php:132 src/Module/Settings/Display.php:248 +#: src/Module/Calendar/Show.php:132 src/Module/Settings/Display.php:291 msgid "list" msgstr "" @@ -5949,7 +5961,7 @@ msgid "Contact not found." msgstr "" #: src/Module/Circle.php:102 src/Module/Contact/Contacts.php:66 -#: src/Module/Conversation/Network.php:232 +#: src/Module/Conversation/Network.php:240 msgid "Invalid contact." msgstr "" @@ -6051,7 +6063,7 @@ msgid "Only show blocked contacts" msgstr "" #: src/Module/Contact.php:369 src/Module/Contact.php:441 -#: src/Module/Settings/Server/Index.php:107 src/Object/Post.php:368 +#: src/Module/Settings/Server/Index.php:107 src/Object/Post.php:369 msgid "Ignored" msgstr "" @@ -6261,7 +6273,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:165 +#: 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 @@ -6711,52 +6723,52 @@ msgstr "" msgid "Unable to unfollow this contact, please contact your administrator" msgstr "" -#: src/Module/Conversation/Channel.php:121 -#: src/Module/Conversation/Community.php:125 src/Module/Search/Index.php:152 +#: src/Module/Conversation/Channel.php:139 +#: 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:159 +#: src/Module/Conversation/Channel.php:177 msgid "Channel not available." msgstr "" -#: src/Module/Conversation/Community.php:91 +#: 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:179 +#: src/Module/Conversation/Community.php:180 msgid "Community option not available." msgstr "" -#: src/Module/Conversation/Community.php:195 +#: src/Module/Conversation/Community.php:196 msgid "Not available." msgstr "" -#: src/Module/Conversation/Network.php:218 +#: src/Module/Conversation/Network.php:226 msgid "No such circle" msgstr "" -#: src/Module/Conversation/Network.php:222 +#: src/Module/Conversation/Network.php:230 #, php-format msgid "Circle: %s" msgstr "" -#: src/Module/Conversation/Network.php:317 +#: src/Module/Conversation/Network.php:325 msgid "Network feed not available." msgstr "" -#: src/Module/Conversation/Timeline.php:158 +#: src/Module/Conversation/Timeline.php:162 msgid "Own Contacts" msgstr "" -#: src/Module/Conversation/Timeline.php:162 +#: src/Module/Conversation/Timeline.php:166 msgid "Include" msgstr "" -#: src/Module/Conversation/Timeline.php:163 +#: src/Module/Conversation/Timeline.php:167 msgid "Hide" msgstr "" @@ -7125,6 +7137,7 @@ msgstr "" #: src/Module/Friendica.php:102 #: src/Module/Moderation/Blocklist/Server/Index.php:87 #: src/Module/Moderation/Blocklist/Server/Index.php:111 +#: src/Module/Settings/Channels.php:164 msgid "Reason for the block" msgstr "" @@ -7872,6 +7885,7 @@ msgstr "" #: src/Module/Moderation/Blocklist/Server/Index.php:86 #: src/Module/Moderation/Blocklist/Server/Index.php:110 +#: src/Module/Settings/Channels.php:163 msgid "Blocked server domain pattern" msgstr "" @@ -9909,6 +9923,119 @@ msgstr "" msgid "No Addon settings configured" msgstr "" +#: src/Module/Settings/Channels.php:130 src/Module/Settings/Channels.php:146 +#: src/Module/Settings/Display.php:330 +msgid "Label" +msgstr "" + +#: src/Module/Settings/Channels.php:131 src/Module/Settings/Channels.php:147 +#: src/Module/Settings/Display.php:331 +#: src/Module/Settings/TwoFactor/AppSpecific.php:134 +msgid "Description" +msgstr "" + +#: src/Module/Settings/Channels.php:132 src/Module/Settings/Channels.php:148 +msgid "Access Key" +msgstr "" + +#: src/Module/Settings/Channels.php:133 src/Module/Settings/Channels.php:149 +msgid "Circle/Channel" +msgstr "" + +#: src/Module/Settings/Channels.php:134 src/Module/Settings/Channels.php:150 +msgid "Include Tags" +msgstr "" + +#: src/Module/Settings/Channels.php:135 src/Module/Settings/Channels.php:151 +msgid "Exclude Tags" +msgstr "" + +#: src/Module/Settings/Channels.php:136 src/Module/Settings/Channels.php:152 +msgid "Full Text Search" +msgstr "" + +#: src/Module/Settings/Channels.php:140 +msgid "Delete channel" +msgstr "" + +#: src/Module/Settings/Channels.php:140 +msgid "Check to delete this entry from the channel list" +msgstr "" + +#: src/Module/Settings/Channels.php:146 +msgid "Short name for the channel. It is displayed on the channels widget." +msgstr "" + +#: src/Module/Settings/Channels.php:147 +msgid "This should describe the content of the channel in a few word." +msgstr "" + +#: src/Module/Settings/Channels.php:148 +msgid "" +"When you want to access this channel via an access key, you can define it " +"here. Pay attention to not use an already used one." +msgstr "" + +#: src/Module/Settings/Channels.php:149 +msgid "Select a circle or channel, that your channel should be based on." +msgstr "" + +#: src/Module/Settings/Channels.php:150 +msgid "" +"Comma separated list of tags. A post will be used when it contains any of " +"the listed tags." +msgstr "" + +#: src/Module/Settings/Channels.php:151 +msgid "" +"Comma separated list of tags. If a post contain any of these tags, then it " +"will not be part of nthis channel." +msgstr "" + +#: src/Module/Settings/Channels.php:152 +#, php-format +msgid "" +"Search terms for the body, supports the \"boolean mode\" operators from " +"MariaDB. See the help for a complete list of operators and additional " +"keywords: %s" +msgstr "" + +#: src/Module/Settings/Channels.php:153 +msgid "Check to display images in the channel." +msgstr "" + +#: src/Module/Settings/Channels.php:154 +msgid "Check to display videos in the channel." +msgstr "" + +#: src/Module/Settings/Channels.php:155 +msgid "Check to display audio in the channel." +msgstr "" + +#: src/Module/Settings/Channels.php:158 +msgid "This page can be used to define your own channels." +msgstr "" + +#: src/Module/Settings/Channels.php:159 +msgid "Add new entry to the channel list" +msgstr "" + +#: src/Module/Settings/Channels.php:160 src/Module/Settings/Delegation.php:181 +msgid "Add" +msgstr "" + +#: src/Module/Settings/Channels.php:162 +msgid "Current Entries in the channel list" +msgstr "" + +#: src/Module/Settings/Channels.php:165 +msgid "Delete entry from the channel list" +msgstr "" + +#: src/Module/Settings/Channels.php:166 +msgid "Delete entry from the channel list?" +msgstr "" + #: src/Module/Settings/Connectors.php:120 msgid "Failed to connect with email account using the settings provided." msgstr "" @@ -10174,179 +10301,171 @@ msgstr "" msgid "Potential Delegates" msgstr "" -#: src/Module/Settings/Delegation.php:181 -msgid "Add" -msgstr "" - #: src/Module/Settings/Delegation.php:182 msgid "No entries." msgstr "" -#: src/Module/Settings/Display.php:146 +#: src/Module/Settings/Display.php:179 msgid "The theme you chose isn't available." msgstr "" -#: src/Module/Settings/Display.php:186 +#: src/Module/Settings/Display.php:219 #, php-format msgid "%s - (Unsupported)" msgstr "" -#: src/Module/Settings/Display.php:221 +#: src/Module/Settings/Display.php:254 msgid "No preview" msgstr "" -#: src/Module/Settings/Display.php:222 +#: src/Module/Settings/Display.php:255 msgid "No image" msgstr "" -#: src/Module/Settings/Display.php:223 +#: src/Module/Settings/Display.php:256 msgid "Small Image" msgstr "" -#: src/Module/Settings/Display.php:224 +#: src/Module/Settings/Display.php:257 msgid "Large Image" msgstr "" -#: src/Module/Settings/Display.php:259 +#: src/Module/Settings/Display.php:302 msgid "Display Settings" msgstr "" -#: src/Module/Settings/Display.php:261 +#: src/Module/Settings/Display.php:304 msgid "General Theme Settings" msgstr "" -#: src/Module/Settings/Display.php:262 +#: src/Module/Settings/Display.php:305 msgid "Custom Theme Settings" msgstr "" -#: src/Module/Settings/Display.php:263 +#: src/Module/Settings/Display.php:306 msgid "Content Settings" msgstr "" -#: src/Module/Settings/Display.php:264 view/theme/duepuntozero/config.php:86 +#: src/Module/Settings/Display.php:307 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:265 +#: src/Module/Settings/Display.php:308 msgid "Timelines" msgstr "" -#: src/Module/Settings/Display.php:272 +#: src/Module/Settings/Display.php:315 msgid "Display Theme:" msgstr "" -#: src/Module/Settings/Display.php:273 +#: src/Module/Settings/Display.php:316 msgid "Mobile Theme:" msgstr "" -#: src/Module/Settings/Display.php:276 +#: src/Module/Settings/Display.php:319 msgid "Number of items to display per page:" msgstr "" -#: src/Module/Settings/Display.php:276 src/Module/Settings/Display.php:277 +#: src/Module/Settings/Display.php:319 src/Module/Settings/Display.php:320 msgid "Maximum of 100 items" msgstr "" -#: src/Module/Settings/Display.php:277 +#: src/Module/Settings/Display.php:320 msgid "Number of items to display per page when viewed from mobile device:" msgstr "" -#: src/Module/Settings/Display.php:278 +#: src/Module/Settings/Display.php:321 msgid "Update browser every xx seconds" msgstr "" -#: src/Module/Settings/Display.php:278 +#: src/Module/Settings/Display.php:321 msgid "Minimum of 10 seconds. Enter -1 to disable it." msgstr "" -#: src/Module/Settings/Display.php:279 +#: src/Module/Settings/Display.php:322 msgid "Display emoticons" msgstr "" -#: src/Module/Settings/Display.php:279 +#: src/Module/Settings/Display.php:322 msgid "When enabled, emoticons are replaced with matching symbols." msgstr "" -#: src/Module/Settings/Display.php:280 +#: src/Module/Settings/Display.php:323 msgid "Infinite scroll" msgstr "" -#: src/Module/Settings/Display.php:280 +#: src/Module/Settings/Display.php:323 msgid "Automatic fetch new items when reaching the page end." msgstr "" -#: src/Module/Settings/Display.php:281 +#: src/Module/Settings/Display.php:324 msgid "Enable Smart Threading" msgstr "" -#: src/Module/Settings/Display.php:281 +#: src/Module/Settings/Display.php:324 msgid "Enable the automatic suppression of extraneous thread indentation." msgstr "" -#: src/Module/Settings/Display.php:282 +#: src/Module/Settings/Display.php:325 msgid "Display the Dislike feature" msgstr "" -#: src/Module/Settings/Display.php:282 +#: src/Module/Settings/Display.php:325 msgid "Display the Dislike button and dislike reactions on posts and comments." msgstr "" -#: src/Module/Settings/Display.php:283 +#: src/Module/Settings/Display.php:326 msgid "Display the resharer" msgstr "" -#: src/Module/Settings/Display.php:283 +#: src/Module/Settings/Display.php:326 msgid "Display the first resharer as icon and text on a reshared item." msgstr "" -#: src/Module/Settings/Display.php:284 +#: src/Module/Settings/Display.php:327 msgid "Stay local" msgstr "" -#: src/Module/Settings/Display.php:284 +#: src/Module/Settings/Display.php:327 msgid "Don't go to a remote system when following a contact link." msgstr "" -#: src/Module/Settings/Display.php:285 +#: src/Module/Settings/Display.php:328 msgid "Link preview mode" msgstr "" -#: src/Module/Settings/Display.php:285 +#: src/Module/Settings/Display.php:328 msgid "Appearance of the link preview that is added to each post with a link." msgstr "" -#: src/Module/Settings/Display.php:287 -msgid "Timelines for the network page:" +#: src/Module/Settings/Display.php:333 +msgid "Bookmark" msgstr "" -#: src/Module/Settings/Display.php:287 -msgid "Select all the timelines that you want to see on your network page." +#: src/Module/Settings/Display.php:335 +msgid "" +"Enable timelines that you want to see in the channels widget. Bookmark " +"timelines that you want to see in the top menu." msgstr "" -#: src/Module/Settings/Display.php:288 +#: src/Module/Settings/Display.php:337 msgid "Channel languages:" msgstr "" -#: src/Module/Settings/Display.php:288 +#: src/Module/Settings/Display.php:337 msgid "Select all languages that you want to see in your channels." msgstr "" -#: src/Module/Settings/Display.php:290 +#: src/Module/Settings/Display.php:339 msgid "Beginning of week:" msgstr "" -#: src/Module/Settings/Display.php:291 +#: src/Module/Settings/Display.php:340 msgid "Default calendar view:" msgstr "" -#: src/Module/Settings/Display.php:300 src/Module/Settings/Display.php:308 -#: src/Module/Settings/Display.php:312 -#, php-format -msgid "%s: %s" -msgstr "" - #: src/Module/Settings/Features.php:74 msgid "Additional Features" msgstr "" @@ -10696,10 +10815,6 @@ msgid "" "see it again!" msgstr "" -#: src/Module/Settings/TwoFactor/AppSpecific.php:134 -msgid "Description" -msgstr "" - #: src/Module/Settings/TwoFactor/AppSpecific.php:135 msgid "Last Used" msgstr "" @@ -11786,246 +11901,251 @@ msgstr "" msgid "Unlisted Message" msgstr "" -#: src/Object/Post.php:181 +#: src/Object/Post.php:182 msgid "This entry was edited" msgstr "" -#: src/Object/Post.php:209 +#: src/Object/Post.php:210 msgid "Connector Message" msgstr "" -#: src/Object/Post.php:225 src/Object/Post.php:227 +#: src/Object/Post.php:226 src/Object/Post.php:228 msgid "Edit" msgstr "" -#: src/Object/Post.php:261 +#: src/Object/Post.php:262 msgid "Delete globally" msgstr "" -#: src/Object/Post.php:261 +#: src/Object/Post.php:262 msgid "Remove locally" msgstr "" -#: src/Object/Post.php:268 +#: src/Object/Post.php:269 #, php-format msgid "Block %s" msgstr "" -#: src/Object/Post.php:273 +#: src/Object/Post.php:274 #, php-format msgid "Ignore %s" msgstr "" -#: src/Object/Post.php:278 +#: src/Object/Post.php:279 #, php-format msgid "Collapse %s" msgstr "" -#: src/Object/Post.php:282 +#: src/Object/Post.php:283 msgid "Report post" msgstr "" -#: src/Object/Post.php:293 +#: src/Object/Post.php:294 msgid "Save to folder" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:334 msgid "I will attend" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:334 msgid "I will not attend" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:334 msgid "I might attend" msgstr "" -#: src/Object/Post.php:363 +#: src/Object/Post.php:364 msgid "Ignore thread" msgstr "" -#: src/Object/Post.php:364 +#: src/Object/Post.php:365 msgid "Unignore thread" msgstr "" -#: src/Object/Post.php:365 +#: src/Object/Post.php:366 msgid "Toggle ignore status" msgstr "" -#: src/Object/Post.php:375 +#: src/Object/Post.php:376 msgid "Add star" msgstr "" -#: src/Object/Post.php:376 +#: src/Object/Post.php:377 msgid "Remove star" msgstr "" -#: src/Object/Post.php:377 +#: src/Object/Post.php:378 msgid "Toggle star status" msgstr "" -#: src/Object/Post.php:388 +#: src/Object/Post.php:389 msgid "Pin" msgstr "" -#: src/Object/Post.php:389 +#: src/Object/Post.php:390 msgid "Unpin" msgstr "" -#: src/Object/Post.php:390 +#: src/Object/Post.php:391 msgid "Toggle pin status" msgstr "" -#: src/Object/Post.php:393 +#: src/Object/Post.php:394 msgid "Pinned" msgstr "" -#: src/Object/Post.php:398 +#: src/Object/Post.php:399 msgid "Add tag" msgstr "" -#: src/Object/Post.php:411 +#: src/Object/Post.php:412 msgid "Quote share this" msgstr "" -#: src/Object/Post.php:411 +#: src/Object/Post.php:412 msgid "Quote Share" msgstr "" -#: src/Object/Post.php:414 +#: src/Object/Post.php:415 msgid "Reshare this" msgstr "" -#: src/Object/Post.php:414 +#: src/Object/Post.php:415 msgid "Reshare" msgstr "" -#: src/Object/Post.php:415 +#: src/Object/Post.php:416 msgid "Cancel your Reshare" msgstr "" -#: src/Object/Post.php:415 +#: src/Object/Post.php:416 msgid "Unshare" msgstr "" -#: src/Object/Post.php:466 +#: src/Object/Post.php:467 #, php-format msgid "%s (Received %s)" msgstr "" -#: src/Object/Post.php:472 +#: src/Object/Post.php:473 msgid "Comment this item on your system" msgstr "" -#: src/Object/Post.php:472 +#: src/Object/Post.php:473 msgid "Remote comment" msgstr "" -#: src/Object/Post.php:494 +#: src/Object/Post.php:495 msgid "Share via ..." msgstr "" -#: src/Object/Post.php:494 +#: src/Object/Post.php:495 msgid "Share via external services" msgstr "" -#: src/Object/Post.php:523 +#: src/Object/Post.php:505 +#, php-format +msgid "in reply to %s" +msgstr "" + +#: src/Object/Post.php:529 msgid "to" msgstr "" -#: src/Object/Post.php:524 +#: src/Object/Post.php:530 msgid "via" msgstr "" -#: src/Object/Post.php:525 +#: src/Object/Post.php:531 msgid "Wall-to-Wall" msgstr "" -#: src/Object/Post.php:526 +#: src/Object/Post.php:532 msgid "via Wall-To-Wall:" msgstr "" -#: src/Object/Post.php:573 +#: src/Object/Post.php:579 #, php-format msgid "Reply to %s" msgstr "" -#: src/Object/Post.php:576 +#: src/Object/Post.php:582 msgid "More" msgstr "" -#: src/Object/Post.php:595 +#: src/Object/Post.php:601 msgid "Notifier task is pending" msgstr "" -#: src/Object/Post.php:596 +#: src/Object/Post.php:602 msgid "Delivery to remote servers is pending" msgstr "" -#: src/Object/Post.php:597 +#: src/Object/Post.php:603 msgid "Delivery to remote servers is underway" msgstr "" -#: src/Object/Post.php:598 +#: src/Object/Post.php:604 msgid "Delivery to remote servers is mostly done" msgstr "" -#: src/Object/Post.php:599 +#: src/Object/Post.php:605 msgid "Delivery to remote servers is done" msgstr "" -#: src/Object/Post.php:619 +#: src/Object/Post.php:627 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: src/Object/Post.php:620 +#: src/Object/Post.php:628 msgid "Show more" msgstr "" -#: src/Object/Post.php:621 +#: src/Object/Post.php:629 msgid "Show fewer" msgstr "" -#: src/Object/Post.php:657 +#: src/Object/Post.php:665 #, php-format msgid "Reshared by: %s" msgstr "" -#: src/Object/Post.php:662 +#: src/Object/Post.php:670 #, php-format msgid "Viewed by: %s" msgstr "" -#: src/Object/Post.php:667 +#: src/Object/Post.php:675 #, php-format msgid "Liked by: %s" msgstr "" -#: src/Object/Post.php:672 +#: src/Object/Post.php:680 #, php-format msgid "Disliked by: %s" msgstr "" -#: src/Object/Post.php:677 +#: src/Object/Post.php:685 #, php-format msgid "Attended by: %s" msgstr "" -#: src/Object/Post.php:682 +#: src/Object/Post.php:690 #, php-format msgid "Maybe attended by: %s" msgstr "" -#: src/Object/Post.php:687 +#: src/Object/Post.php:695 #, php-format msgid "Not attended by: %s" msgstr "" -#: src/Object/Post.php:692 +#: src/Object/Post.php:700 #, php-format msgid "Reacted with %s by: %s" msgstr "" diff --git a/view/templates/settings/channels.tpl b/view/templates/settings/channels.tpl new file mode 100644 index 0000000000..16bb510d6c --- /dev/null +++ b/view/templates/settings/channels.tpl @@ -0,0 +1,45 @@ +
+

{{$l10n.title}}

+

{{$l10n.intro}}

+

{{$l10n.addtitle}}

+
+ + {{include file="field_input.tpl" field=$label}} + {{include file="field_input.tpl" field=$description}} + {{include file="field_input.tpl" field=$access_key}} + {{include file="field_select.tpl" field=$circle}} + {{include file="field_input.tpl" field=$include_tags}} + {{include file="field_input.tpl" field=$exclude_tags}} + {{include file="field_input.tpl" field=$text_search}} + {{include file="field_checkbox.tpl" field=$image}} + {{include file="field_checkbox.tpl" field=$video}} + {{include file="field_checkbox.tpl" field=$audio}} +
+ +
+
+ + {{if $entries}} +

{{$l10n.currenttitle}}

+
+ + {{foreach $entries as $e}} + {{include file="field_input.tpl" field=$e.label}} + {{include file="field_input.tpl" field=$e.description}} + {{include file="field_input.tpl" field=$e.access_key}} + {{include file="field_select.tpl" field=$e.circle}} + {{include file="field_input.tpl" field=$e.include_tags}} + {{include file="field_input.tpl" field=$e.exclude_tags}} + {{include file="field_input.tpl" field=$e.text_search}} + {{include file="field_checkbox.tpl" field=$e.image}} + {{include file="field_checkbox.tpl" field=$e.video}} + {{include file="field_checkbox.tpl" field=$e.audio}} + {{include file="field_checkbox.tpl" field=$e.delete}} +
+ {{/foreach}} +
+ +
+ {{/if}} +
+
diff --git a/view/templates/settings/display.tpl b/view/templates/settings/display.tpl index 868acb38a2..d1cba7a195 100644 --- a/view/templates/settings/display.tpl +++ b/view/templates/settings/display.tpl @@ -22,7 +22,27 @@ {{include file="field_select.tpl" field=$preview_mode}}

{{$timeline_title}}

- {{include file="field_select.tpl" field=$network_timelines}} + {{$timeline_explanation}} + + + + + + + + + + + {{foreach $timelines as $t}} + + + + + + + {{/foreach}} + +
{{$timeline_label}}{{$timeline_descriptiom}}{{$timeline_enable}}{{$timeline_bookmark}}
{{$t.label}}{{$t.description}}{{include file="field_checkbox.tpl" field=$t.enable}}{{include file="field_checkbox.tpl" field=$t.bookmark}}

{{$channel_title}}

{{include file="field_select.tpl" field=$channel_languages}} diff --git a/view/theme/frio/templates/profile/vcard.tpl b/view/theme/frio/templates/profile/vcard.tpl index 94f62bd0d3..86d6bd9179 100644 --- a/view/theme/frio/templates/profile/vcard.tpl +++ b/view/theme/frio/templates/profile/vcard.tpl @@ -72,9 +72,16 @@ {{/if}} {{if $profile.addr}} + {{/if}} + {{if $network_label}} +
+
{{/if}} diff --git a/view/theme/frio/templates/settings/display.tpl b/view/theme/frio/templates/settings/display.tpl index f361fe9535..41dd11c10b 100644 --- a/view/theme/frio/templates/settings/display.tpl +++ b/view/theme/frio/templates/settings/display.tpl @@ -84,7 +84,27 @@
- {{include file="field_select.tpl" field=$network_timelines}} + {{$timeline_explanation}} + + + + + + + + + + + {{foreach $timelines as $t}} + + + + + + + {{/foreach}} + +
{{$timeline_label}}{{$timeline_descriptiom}}{{$timeline_enable}}{{$timeline_bookmark}}
{{$t.label}}{{$t.description}}{{include file="field_checkbox.tpl" field=$t.enable}}{{include file="field_checkbox.tpl" field=$t.bookmark}}