]> git.mxchange.org Git - friendica.git/commitdiff
Channels can now be created by users
authorMichael <heluecht@pirati.ca>
Tue, 19 Sep 2023 09:05:28 +0000 (09:05 +0000)
committerMichael <heluecht@pirati.ca>
Tue, 19 Sep 2023 09:05:28 +0000 (09:05 +0000)
14 files changed:
database.sql
doc/database.md
doc/database/db_channel.md [new file with mode: 0644]
src/Content/Conversation/Entity/Timeline.php
src/Content/Conversation/Factory/Timeline.php
src/Content/Conversation/Repository/Channel.php [new file with mode: 0644]
src/DI.php
src/Module/Conversation/Channel.php
src/Module/Conversation/Community.php
src/Module/Conversation/Network.php
src/Module/Conversation/Timeline.php
src/Module/Update/Channel.php
src/Module/Update/Network.php
static/dbstructure.config.php

index 329381b95b3dfc4e7efba5e50381646fe083941e..8598f6fe602cf7504241ed256677e85bb7a21422 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
 -- Friendica 2023.09-dev (Giant Rhubarb)
--- DB_UPDATE_VERSION 1534
+-- DB_UPDATE_VERSION 1535
 -- ------------------------------------------
 
 
@@ -492,6 +492,24 @@ 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',
+       `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
 --
index 25b9baefb1df42d032c6713e99e54ae532a0559f..516be3bae1dd69b53c50c14f11a258d64c36b488 100644 (file)
@@ -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 (file)
index 0000000..1d5bfc7
--- /dev/null
@@ -0,0 +1,36 @@
+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    |                |
+| 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)
index b9ab1e1a01f07f112731ff8f07976841dfb63a96..ce9e3c62fc82422410fba0c74e85e2f534ddd401 100644 (file)
 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
  */
 final class Timeline extends \Friendica\BaseEntity
 {
@@ -56,13 +60,28 @@ final class Timeline extends \Friendica\BaseEntity
        protected $accessKey;
        /** @var string */
        protected $path;
+       /** @var int */
+       protected $uid;
+       /** @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)
        {
-               $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;
        }
 }
index 817f6171316aea295b423dc464c36482531c19cd..0e4c0b76d6a7761a0171140fdecf737bd204bbad 100644 (file)
 
 namespace Friendica\Content\Conversation\Factory;
 
+use Friendica\Capabilities\ICanCreateFromTableRow;
 use Friendica\Content\Conversation\Collection\Timelines;
 use Friendica\Model\User;
 use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity;
+use Friendica\Content\Conversation\Repository\Channel;
 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
+final class Timeline extends \Friendica\BaseFactory implements ICanCreateFromTableRow
 {
        /** @var L10n */
        protected $l10n;
        /** @var IManageConfigValues The config */
        protected $config;
+       /** @var Channel */
+       protected $channel;
 
-       public function __construct(L10n $l10n, LoggerInterface $logger, IManageConfigValues $config)
+       public function __construct(Channel $channel, L10n $l10n, LoggerInterface $logger, IManageConfigValues $config)
        {
                parent::__construct($logger);
 
-               $this->l10n   = $l10n;
-               $this->config = $config;
+               $this->channel = $channel;
+               $this->l10n    = $l10n;
+               $this->config  = $config;
+       }
+
+       public function createFromTableRow(array $row): TimelineEntity
+       {
+               return new TimelineEntity(
+                       $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,
+               );
        }
 
        /**
@@ -65,6 +86,11 @@ final class Timeline extends \Friendica\BaseFactory
                        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'),
                ];
+
+               foreach ($this->channel->selectByUid($uid) as $channel) {
+                       $tabs[] = $channel;
+               }
+
                return new Timelines($tabs);
        }
 
@@ -113,8 +139,11 @@ final class Timeline extends \Friendica\BaseFactory
                return in_array($selectedTab, [TimelineEntity::LOCAL, TimelineEntity::GLOBAL]);
        }
 
-       public function isChannel(string $selectedTab): bool
+       public function isChannel(string $selectedTab, int $uid): bool
        {
+               if (is_numeric($selectedTab) && $uid && $this->channel->existsById($selectedTab, $uid)) {
+                       return true;
+               }
                return in_array($selectedTab, [TimelineEntity::WHATSHOT, TimelineEntity::FORYOU, TimelineEntity::FOLLOWERS, TimelineEntity::SHARERSOFSHARERS, TimelineEntity::IMAGE, TimelineEntity::VIDEO, TimelineEntity::AUDIO, TimelineEntity::LANGUAGE]);
        }
 }
diff --git a/src/Content/Conversation/Repository/Channel.php b/src/Content/Conversation/Repository/Channel.php
new file mode 100644 (file)
index 0000000..a88a2f5
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2023, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Content\Conversation\Repository;
+
+use Friendica\BaseCollection;
+use Friendica\Content\Conversation\Entity\Timeline as EntityTimeline;
+use Friendica\Content\Conversation\Factory\Timeline;
+use Friendica\Database\Database;
+use Psr\Log\LoggerInterface;
+
+class Channel extends \Friendica\BaseRepository
+{
+       protected static $table_name = 'channel';
+
+       public function __construct(Database $database, LoggerInterface $logger, Timeline $factory)
+       {
+               parent::__construct($database, $logger, $factory);
+       }
+
+       /**
+        * Fetch a single user channel
+        * 
+        * @param int $id
+        * @param int $uid
+        * @return EntityTimeline
+        * @throws \Friendica\Network\HTTPException\NotFoundException
+        */
+       public function selectById(int $id, int $uid): EntityTimeline
+       {
+               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]);
+       }
+
+       /**
+        * Fetch all user channels
+        *
+        * @param integer $uid
+        * @return BaseCollection
+        */
+       public function selectByUid(int $uid): BaseCollection
+       {
+               return $this->_select(['uid' => $uid]);
+       }
+
+       public function save(EntityTimeline $Channel): EntityTimeline
+       {
+               $fields = [
+                       'label'            => $Channel->label,
+                       'description'      => $Channel->description,
+                       'access-key'       => $Channel->accessKey,
+                       'uid'              => $Channel->uid,
+                       '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;
+       }
+}
index 681c8fcbc168ecba0f07e606a00f92de56b6cfa4..7c221b5efe310669d900bfe46eb6520f0cc65cf1 100644 (file)
@@ -555,6 +555,14 @@ abstract class DI
                return self::$dice->create(Content\Conversation\Factory\Timeline::class);
        }
 
+       /**
+        * @return Content\Conversation\Repository\Channel
+        */
+       public static function ChannelRepository()
+       {
+               return self::$dice->create(Content\Conversation\Repository\Channel::class);
+       }
+
        /**
         * @return Contact\Introduction\Repository\Introduction
         */
index 35c988ce7c81a81c6f7e4278e4cc07cdcd753828..9bb3a42333a664f4aca65fad382a2a49e7c287a1 100644 (file)
@@ -27,6 +27,7 @@ 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\Repository\Channel as RepositoryChannel;
 use Friendica\Content\Feature;
 use Friendica\Content\Nav;
 use Friendica\Content\Text\HTML;
@@ -57,9 +58,9 @@ class Channel 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(RepositoryChannel $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;
@@ -109,7 +110,7 @@ class Channel extends Timeline
                        $o .= $this->conversation->statusEditor([], 0, true);
                }
 
-               if ($this->timeline->isChannel($this->selectedTab)) {
+               if ($this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId())) {
                        $items = $this->getChannelItems();
                        $order = 'created';
                } else {
@@ -155,7 +156,7 @@ class Channel extends Timeline
                        $this->selectedTab = TimelineEntity::FORYOU;
                }
 
-               if (!$this->timeline->isChannel($this->selectedTab) && !$this->timeline->isCommunity($this->selectedTab)) {
+               if (!$this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId()) && !$this->timeline->isCommunity($this->selectedTab)) {
                        throw new HTTPException\BadRequestException($this->l10n->t('Channel not available.'));
                }
 
index 42f56209672a1a073c22354891cb9f2cac8bd710..75f937d3511b8f0281b75a0cb0affed0a93b883b 100644 (file)
@@ -28,6 +28,7 @@ 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\Repository\Channel;
 use Friendica\Content\Feature;
 use Friendica\Content\Nav;
 use Friendica\Content\Text\HTML;
@@ -69,9 +70,9 @@ 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(Channel $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;
index 21ead331f0a770f273f99381267c0caea4a58f71..04f3f232f53fcfaf637666e26876777c708268ec 100644 (file)
@@ -27,6 +27,7 @@ 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\Repository\Channel;
 use Friendica\Content\Feature;
 use Friendica\Content\GroupManager;
 use Friendica\Content\Nav;
@@ -96,9 +97,9 @@ class Network extends Timeline
        /** @var TimelineFactory */
        protected $timeline;
 
-       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(Channel $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;
@@ -125,7 +126,7 @@ class Network extends Timeline
 
                $o = '';
 
-               if ($this->timeline->isChannel($this->selectedTab)) {
+               if ($this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId())) {
                        if (!in_array($this->selectedTab, [TimelineEntity::FOLLOWERS, TimelineEntity::FORYOU]) && $this->config->get('system', 'community_no_sharer')) {
                                $this->page['aside'] .= $this->getNoSharerWidget($module);
                        }
@@ -274,7 +275,6 @@ class Network extends Timeline
         */
        private function getTabsHTML()
        {
-               // @todo user confgurable selection of tabs
                $tabs = $this->getTabArray($this->timeline->getNetworkFeeds($this->args->getCommand()), 'network');
 
                $network_timelines = $this->pConfig->get($this->session->getLocalUserId(), 'system', 'network_timelines', []);
@@ -289,7 +289,7 @@ class Network extends Timeline
                if (!empty($network_timelines)) {
                        $tabs = [];
 
-                       foreach (array_keys($arr['tabs']) as $tab) {
+                       foreach (array_column($arr['tabs'], 'code') as $tab) {
                                if (in_array($tab, $network_timelines)) {
                                        $tabs[] = $arr['tabs'][$tab];
                                }
@@ -313,11 +313,11 @@ 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->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId()) && !$this->timeline->isCommunity($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))) {
+               if (($this->network || $this->circleId || $this->groupContactId) && ($this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId()) || $this->timeline->isCommunity($this->selectedTab))) {
                        $this->selectedTab = TimelineEntity::RECEIVED;
                }
 
@@ -342,7 +342,7 @@ class Network extends Timeline
                        $this->mention = false;
                } elseif (in_array($this->selectedTab, [TimelineEntity::RECEIVED, TimelineEntity::STAR]) || $this->timeline->isCommunity($this->selectedTab)) {
                        $this->order = 'received';
-               } elseif (($this->selectedTab == TimelineEntity::CREATED) || $this->timeline->isChannel($this->selectedTab)) {
+               } elseif (($this->selectedTab == TimelineEntity::CREATED) || $this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId())) {
                        $this->order = 'created';
                } else {
                        $this->order = 'commented';
index 1a4d98e61c152d29b2a7d79c4a914ecbcadc2e4e..9c53634ea8825315cfa8884b742278a61c77a729 100644 (file)
@@ -26,6 +26,7 @@ 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\Repository\Channel;
 use Friendica\Core\Cache\Capability\ICanCache;
 use Friendica\Core\Cache\Enum\Duration;
 use Friendica\Core\Config\Capability\IManageConfigValues;
@@ -79,11 +80,14 @@ class Timeline extends BaseModule
        protected $config;
        /** @var ICanCache */
        protected $cache;
+       /** @var Channel */
+       protected $channel;
 
-       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(Channel $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->channel  = $channel;
                $this->mode     = $mode;
                $this->session  = $session;
                $this->database = $database;
@@ -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' : '',
@@ -300,6 +305,8 @@ class Timeline extends BaseModule
                        $condition = ["`media-type` & ?", 4];
                } elseif ($this->selectedTab == TimelineEntity::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) {
@@ -359,6 +366,39 @@ class Timeline extends BaseModule
                return $items;
        }
 
+       private function getUserChannelConditions(int $id, int $uid): array
+       {
+               $channel = $this->channel->selectById($id, $uid);
+               if (empty($channel)) {
+                       return [];
+               }
+
+               $condition = [];
+
+               if (!empty($channel->fullTextSearch)) {
+                       $first     = $this->database->selectFirst('post-engagement', ['uri-id']);
+                       $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-content` WHERE `uri-id` >= ? AND MATCH (`title`, `content-warning`, `body`) AGAINST (? IN BOOLEAN MODE))", $first['uri-id'], $channel->fullTextSearch]);
+               }
+
+               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]);
+               }
+
+               return $condition;
+       }
+
        private function addLanguageCondition(int $uid, array $condition): array
        {
                $conditions = [];
index ac35d9c26bd6a82b2779119ec23b2e0df56d9588..0abe036d6fe83569b92d83e922dc43771429775c 100644 (file)
@@ -38,7 +38,7 @@ class Channel extends ChannelModule
 
                $o = '';
                if ($this->update || $this->force) {
-                       if ($this->timeline->isChannel($this->selectedTab)) {
+                       if ($this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId())) {
                                $items = $this->getChannelItems();
                        } else {
                                $items = $this->getCommunityItems();
index e8a2dce07da614564e305f3c05b8c2bf11e229a4..612d4079c8cb73654e9ab9b518a3e4c2c248af85 100644 (file)
@@ -41,7 +41,7 @@ class Network extends NetworkModule
                        System::htmlUpdateExit($o);
                }
 
-               if ($this->timeline->isChannel($this->selectedTab)) {
+               if ($this->timeline->isChannel($this->selectedTab, $this->session->getLocalUserId())) {
                        $items = $this->getChannelItems();
                } elseif ($this->timeline->isCommunity($this->selectedTab)) {
                        $items = $this->getCommunityItems();
index 606173b817b15a15c6103c99e44951c321691d93..e918a44913896e1a2771fa5d0dce1ec9f25c23e8 100644 (file)
@@ -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', 1534);
+       define('DB_UPDATE_VERSION', 1535);
 }
 
 return [
@@ -551,6 +551,24 @@ 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"],
+                       "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" => [