From 0050371de784913c739dce78925c29c91e460520 Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Fri, 31 Dec 2021 19:13:19 +0000 Subject: [PATCH] [PLUGIN][NoteTypeFeedFilter][MediaFeed][COMPONENT][Feed] Rename MediaFeed to NoteTypeFeedFilter and add support for filtering by more types, moving functionality from Feed component --- components/Feed/Util/FeedController.php | 44 ----- plugins/MediaFeed/MediaFeed.php | 98 ---------- .../templates/mediaFeed/tabs.html.twig | 2 - .../NoteTypeFeedFilter/NoteTypeFeedFilter.php | 172 ++++++++++++++++++ .../NoteTypeFeedFilter/tabs.html.twig | 3 + .../assets/css/noteTypeFeedFilter.css} | 0 6 files changed, 175 insertions(+), 144 deletions(-) delete mode 100644 plugins/MediaFeed/MediaFeed.php delete mode 100644 plugins/MediaFeed/templates/mediaFeed/tabs.html.twig create mode 100644 plugins/NoteTypeFeedFilter/NoteTypeFeedFilter.php create mode 100644 plugins/NoteTypeFeedFilter/templates/NoteTypeFeedFilter/tabs.html.twig rename public/plugins/{MediaFeed/assets/css/mediaFeed.css => NoteTypeFeedFilter/assets/css/noteTypeFeedFilter.css} (100%) diff --git a/components/Feed/Util/FeedController.php b/components/Feed/Util/FeedController.php index 8972972a30..d625374cd0 100644 --- a/components/Feed/Util/FeedController.php +++ b/components/Feed/Util/FeedController.php @@ -34,12 +34,9 @@ namespace Component\Feed\Util; use App\Core\Controller; use App\Core\Event; -use function App\Core\I18n\_m; use App\Entity\Actor; use App\Entity\Note; use App\Util\Common; -use App\Util\Exception\ClientException; -use App\Util\Formatting; use Functional as F; abstract class FeedController extends Controller @@ -56,10 +53,6 @@ abstract class FeedController extends Controller if (\array_key_exists('notes', $result)) { $notes = $result['notes']; self::enforceScope($notes, $actor); - $types = $this->string('types'); - if (!\is_null($types)) { - self::filterNoteType($notes, $types); - } Event::handle('FilterNoteList', [$actor, &$notes, $result['request']]); Event::handle('FormatNoteList', [$notes, &$result['notes']]); } @@ -67,43 +60,6 @@ abstract class FeedController extends Controller return $result; } - private static function filterNoteType(array &$notes, string $types): void - { - $types = explode(',', $types); - $types = [ - ...$types, - // Add any missing types as a negation - ...array_diff(['!media', '!link', '!text', '!tags'], F\map($types, fn ($t) => $t[0] === '!' ? $t : '!' . $t)), - ]; - - $notes = F\select($notes, function (Note $note) use ($types) { - $include = false; - foreach ($types as $type) { - $is_negate = $type[0] === '!'; - $type = Formatting::removePrefix($type, '!'); - $ret = (function () use ($note, $type) { - switch ($type) { - case 'media': - return !empty($note->getAttachments()); - case 'link': - return !empty($note->getLinks()); - case 'text': - return !\is_null($note->getContent()); - case 'tags': - return !empty($note->getTags()); - default: - throw new ClientException(_m('Unknown note type requested ({type})', ['{type}' => $type])); - } - })(); - if ($is_negate && $ret) { - return false; - } - $include = $include || $ret; - } - return $include; - }); - } - private static function enforceScope(array &$notes, ?Actor $actor): void { $notes = F\select($notes, fn (Note $n) => $n->isVisibleTo($actor)); diff --git a/plugins/MediaFeed/MediaFeed.php b/plugins/MediaFeed/MediaFeed.php deleted file mode 100644 index c06d8498eb..0000000000 --- a/plugins/MediaFeed/MediaFeed.php +++ /dev/null @@ -1,98 +0,0 @@ -. -// }}} -/** - * Media Feed Plugin for GNU social - * - * @package GNUsocial - * @category Plugin - * - * @author Phablulo - * @copyright 2018-2019, 2021 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -namespace Plugin\MediaFeed; - -use App\Core\Event; -use App\Core\Modules\Plugin; -use App\Entity\Actor; -use App\Entity\Note; -use App\Util\Formatting; -use Functional as F; -use Symfony\Component\HttpFoundation\Request; - -class MediaFeed extends Plugin -{ - public function onFilterNoteList(?Actor $actor, array &$notes, Request $request): bool - { - if ($request->get('filter-type') === 'media') { - $notes = F\select($notes, fn (Note $n) => \count($n->getAttachments()) > 0); - } - return Event::next; - } - - /** - * Draw the media feed navigation. - * - * @return bool hook value; true means continue processing, false means stop - */ - public function onAddFeedActions(Request $request, &$res): bool - { - $isMediaActive = $request->get('filter-type') === 'media'; - // we need two urls: one with filter-type=media and without it. - $query = mb_strpos($request->getRequestUri(), '?'); - $mediaURL = $request->getRequestUri() . ($query !== false ? '&' : '?') . 'filter-type=media'; - $allURL = $request->getPathInfo(); - if ($query !== false) { - $params = explode('&', mb_substr($request->getRequestUri(), $query + 1)); - $params = array_filter($params, fn ($s) => $s !== 'filter-type=media'); - $params = implode('&', $params); - if ($params) { - $allURL .= '?' . $params; - } - } - - $res[] = Formatting::twigRenderFile('mediaFeed/tabs.html.twig', [ - 'main' => [ - 'active' => !$isMediaActive, - 'url' => $isMediaActive ? $allURL : '', - ], - 'media' => [ - 'active' => $isMediaActive, - 'url' => $isMediaActive ? '' : $mediaURL, - ], - ]); - return Event::next; - } - - /** - * Output our dedicated stylesheet - * - * @param array $styles stylesheets path - * - * @return bool hook value; true means continue processing, false means stop - */ - public function onEndShowStyles(array &$styles, string $route): bool - { - $styles[] = 'plugins/MediaFeed/assets/css/mediaFeed.css'; - return Event::next; - } -} diff --git a/plugins/MediaFeed/templates/mediaFeed/tabs.html.twig b/plugins/MediaFeed/templates/mediaFeed/tabs.html.twig deleted file mode 100644 index ee339b0036..0000000000 --- a/plugins/MediaFeed/templates/mediaFeed/tabs.html.twig +++ /dev/null @@ -1,2 +0,0 @@ -{{ icon('feed', 'icon icon-right') | raw }} -{{ icon('media', 'icon icon-right') | raw }} \ No newline at end of file diff --git a/plugins/NoteTypeFeedFilter/NoteTypeFeedFilter.php b/plugins/NoteTypeFeedFilter/NoteTypeFeedFilter.php new file mode 100644 index 0000000000..6fd815933e --- /dev/null +++ b/plugins/NoteTypeFeedFilter/NoteTypeFeedFilter.php @@ -0,0 +1,172 @@ +. +// }}} +/** + * Media Feed Plugin for GNU social + * + * @package GNUsocial + * @category Plugin + * + * @author Phablulo + * @copyright 2018-2019, 2021 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace Plugin\NoteTypeFeedFilter; + +use App\Core\Event; +use function App\Core\I18n\_m; +use App\Core\Modules\Plugin; +use App\Entity\Actor; +use App\Entity\Note; +use App\Util\Exception\BugFoundException; +use App\Util\Exception\ClientException; +use App\Util\Formatting; +use App\Util\Functional as GSF; +use Functional as F; +use Symfony\Component\HttpFoundation\Request; + +class NoteTypeFeedFilter extends Plugin +{ + public const ALLOWED_TYPES = ['media', 'link', 'text', 'tag']; + + private function unknownType(string $type): ClientException + { + return new ClientException(_m('Unknown note type requested ({type})', ['{type}' => $type])); + } + + private function normalizeTypesList(array $types, bool $add_missing = true): array + { + if (empty($types)) { + return self::ALLOWED_TYPES; + } else { + $result = []; + foreach (self::ALLOWED_TYPES as $allowed_type) { + foreach ($types as $type) { + if ($type === 'all') { + return self::ALLOWED_TYPES; + } elseif (mb_detect_encoding($type, 'ASCII', strict: true) === false || empty($type)) { + throw $this->unknownType($type); + } elseif (\in_array( + $allowed_type, + GSF::cartesianProduct([ + ['', '!'], + [$type, mb_substr($type, 1), mb_substr($type, 0, -1)], // The original, without the first or without the last character + ]), + )) { + $result[] = ($type[0] === '!' ? '!' : '') . $allowed_type; + continue 2; + } + } // else + if ($add_missing) { + $result[] = '!' . $allowed_type; + } + } + return $result; + } + } + + public function onFilterNoteList(?Actor $actor, array &$notes, Request $request): bool + { + $types = $this->normalizeTypesList(\is_null($request->get('note-types')) ? [] : explode(',', $request->get('note-types'))); + $notes = F\select( + $notes, + function (Note $note) use ($types) { + $include = false; // TODO Would like to express this as a reduce of some sort... + foreach ($types as $type) { + $is_negate = $type[0] === '!'; + $type = Formatting::removePrefix($type, '!'); + switch ($type) { + case 'text': + $ret = !\is_null($note->getContent()); + break; + case 'media': + $ret = !empty($note->getAttachments()); + break; + case 'link': + $ret = !empty($note->getLinks()); + break; + case 'tag': + $ret = !empty($note->getTags()); + break; + default: + throw new BugFoundException("Unkown note type requested {$type}", previous: $this->unknownType($type)); + } + if ($is_negate && $ret) { + return false; + } + $include = $include || $ret; + } + return $include; + }, + ); + return Event::next; + } + + /** + * Draw the media feed navigation. + */ + public function onAddFeedActions(Request $request, &$res): bool + { + $qs = []; + parse_str($request->getQueryString(), $qs); + if (\array_key_exists('p', $qs) && \is_string($qs['p'])) { + unset($qs['p']); + } + $types = $this->normalizeTypesList(\is_null($request->get('note-types')) ? [] : explode(',', $request->get('note-types')), add_missing: false); + $ftypes = array_flip($types); + + $tabs = [ + 'all' => [ + 'active' => empty($types) || $types === self::ALLOWED_TYPES, + 'url' => '?' . http_build_query(['note-types' => implode(',', self::ALLOWED_TYPES)], '', '&', \PHP_QUERY_RFC3986), + 'icon' => 'All', + ], + ]; + + foreach (self::ALLOWED_TYPES as $allowed_type) { + $active = \array_key_exists($allowed_type, $ftypes); + $new_types = $this->normalizeTypesList([($active ? '!' : '') . $allowed_type, ...$types], add_missing: false); + $new_qs = $qs; + $new_qs['note-types'] = implode(',', $new_types); + $tabs[$allowed_type] = [ + 'active' => $active, + 'url' => '?' . http_build_query($new_qs, '', '&', \PHP_QUERY_RFC3986), + 'icon' => $allowed_type, + ]; + } + + $res[] = Formatting::twigRenderFile('NoteTypeFeedFilter/tabs.html.twig', ['tabs' => $tabs]); + return Event::next; + } + + /** + * Output our dedicated stylesheet + * + * @param array $styles stylesheets path + * + * @return bool hook value; true means continue processing, false means stop + */ + public function onEndShowStyles(array &$styles, string $route): bool + { + $styles[] = 'plugins/NoteTypeFeedFilter/assets/css/noteTypeFeedFilter.css'; + return Event::next; + } +} diff --git a/plugins/NoteTypeFeedFilter/templates/NoteTypeFeedFilter/tabs.html.twig b/plugins/NoteTypeFeedFilter/templates/NoteTypeFeedFilter/tabs.html.twig new file mode 100644 index 0000000000..25bc0ccdc6 --- /dev/null +++ b/plugins/NoteTypeFeedFilter/templates/NoteTypeFeedFilter/tabs.html.twig @@ -0,0 +1,3 @@ +{% for tab in tabs %} + {{ tab['icon'] }} +{% endfor %} diff --git a/public/plugins/MediaFeed/assets/css/mediaFeed.css b/public/plugins/NoteTypeFeedFilter/assets/css/noteTypeFeedFilter.css similarity index 100% rename from public/plugins/MediaFeed/assets/css/mediaFeed.css rename to public/plugins/NoteTypeFeedFilter/assets/css/noteTypeFeedFilter.css