Skip to content

Commit 1471bf7

Browse files
committed
[FEATURE] Add a filter plugin that combines category and tag
More filters are possible via filter interface.
1 parent 1842a6b commit 1471bf7

File tree

26 files changed

+911
-2
lines changed

26 files changed

+911
-2
lines changed

Classes/Constants.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ class Constants
6666
'blog_commentswidget' => -1600000017,
6767
'blog_archivewidget' => -1600000018,
6868
'blog_feedwidget' => -1600000019,
69+
'blog_filter' => -1600000020,
6970
];
7071
}

Classes/Controller/PostController.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
use Psr\Http\Message\ResponseInterface;
1414
use Psr\Http\Message\ServerRequestInterface;
15+
use T3G\AgencyPack\Blog\Domain\Factory\PostFilterFactory;
1516
use T3G\AgencyPack\Blog\Domain\Model\Author;
1617
use T3G\AgencyPack\Blog\Domain\Model\Category;
1718
use T3G\AgencyPack\Blog\Domain\Model\Post;
@@ -27,6 +28,7 @@
2728
use T3G\AgencyPack\Blog\Utility\ArchiveUtility;
2829
use TYPO3\CMS\Core\Http\NormalizedParams;
2930
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
31+
use TYPO3\CMS\Core\Utility\GeneralUtility;
3032
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
3133
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
3234
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
@@ -251,6 +253,29 @@ public function listPostsByTagAction(?Tag $tag = null, int $currentPage = 1): Re
251253
return $this->htmlResponse();
252254
}
253255

256+
/**
257+
* Show a list of posts by given filter.
258+
*/
259+
public function listPostsByFilterAction(int $currentPage = 1): ResponseInterface
260+
{
261+
$factory = GeneralUtility::makeInstance(PostFilterFactory::class, $this->categoryRepository, $this->tagRepository);
262+
$filter = $factory->getFilterFromRequest($this->request);
263+
$posts = $this->postRepository->findAllByFilter($filter);
264+
$pagination = $this->getPagination($posts, $currentPage);
265+
266+
$this->view->assign('type', 'byfilter');
267+
$this->view->assign('posts', $posts);
268+
$this->view->assign('pagination', $pagination);
269+
$this->view->assign('filter', $filter);
270+
$this->view->assign('categories', $this->categoryRepository->findAll());
271+
$this->view->assign('tags', $this->tagRepository->findAll());
272+
273+
MetaTagService::set(MetaTagService::META_TITLE, $filter->getTitle());
274+
MetaTagService::set(MetaTagService::META_DESCRIPTION, $filter->getDescription());
275+
276+
return $this->htmlResponse();
277+
}
278+
254279
/**
255280
* Sidebar action.
256281
*/
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
/*
5+
* This file is part of the package t3g/blog.
6+
*
7+
* For the full copyright and license information, please read the
8+
* LICENSE file that was distributed with this source code.
9+
*/
10+
11+
namespace T3G\AgencyPack\Blog\Domain\Factory;
12+
13+
use T3G\AgencyPack\Blog\Domain\Filter\Post\CategoryTag;
14+
use T3G\AgencyPack\Blog\Domain\Filter\PostFilter;
15+
use T3G\AgencyPack\Blog\Domain\Repository\CategoryRepository;
16+
use T3G\AgencyPack\Blog\Domain\Repository\TagRepository;
17+
use TYPO3\CMS\Core\SingletonInterface;
18+
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
19+
20+
/**
21+
* The post filter factory sets filter values from the request.
22+
*/
23+
class PostFilterFactory implements SingletonInterface
24+
{
25+
protected CategoryRepository $categoryRepository;
26+
27+
protected TagRepository $tagRepository;
28+
29+
public function __construct(CategoryRepository $categoryRepository, TagRepository $tagRepository)
30+
{
31+
$this->categoryRepository = $categoryRepository;
32+
$this->tagRepository = $tagRepository;
33+
}
34+
35+
public function getFilterFromRequest(RequestInterface $request): PostFilter
36+
{
37+
// Currently there is only one filter implementation.
38+
$filter = new CategoryTag();
39+
if ($request->hasArgument('category')) {
40+
$filter->setCategory($this->categoryRepository->findByUid((int)$request->getArgument('category')));
41+
}
42+
if ($request->hasArgument('tag')) {
43+
$filter->setTag($this->tagRepository->findByUid((int)$request->getArgument('tag')));
44+
}
45+
return $filter;
46+
}
47+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
/*
5+
* This file is part of the package t3g/blog.
6+
*
7+
* For the full copyright and license information, please read the
8+
* LICENSE file that was distributed with this source code.
9+
*/
10+
11+
namespace T3G\AgencyPack\Blog\Domain\Filter\Post;
12+
13+
use T3G\AgencyPack\Blog\Domain\Filter\PostFilter;
14+
15+
/**
16+
* Base class for all post filters.
17+
*/
18+
abstract class AbstractBase implements PostFilter
19+
{
20+
/**
21+
* The class name (without namespace) is used as the filter's name.
22+
*/
23+
public function getName(): string
24+
{
25+
$classWithNamespace = get_class($this);
26+
$rightBackslash = strrpos($classWithNamespace, '\\') ?? 0;
27+
return substr($classWithNamespace, $rightBackslash > 0 ? ++$rightBackslash : $rightBackslash);
28+
}
29+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
/*
5+
* This file is part of the package t3g/blog.
6+
*
7+
* For the full copyright and license information, please read the
8+
* LICENSE file that was distributed with this source code.
9+
*/
10+
11+
namespace T3G\AgencyPack\Blog\Domain\Filter\Post;
12+
13+
use T3G\AgencyPack\Blog\Domain\Model\Category;
14+
use T3G\AgencyPack\Blog\Domain\Model\Tag;
15+
use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException;
16+
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface;
17+
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
18+
19+
/**
20+
* This filter combines one selected Category and one selected Tag (both optional).
21+
*/
22+
class CategoryTag extends AbstractBase
23+
{
24+
protected ?Category $category = null;
25+
26+
protected ?Tag $tag = null;
27+
28+
/**
29+
* Title is simply the concatenated Category and Tag titles (separated by a SPACE character).
30+
*/
31+
public function getTitle(): string
32+
{
33+
if ($this->category) {
34+
if ($this->tag) {
35+
return trim($this->category->getTitle() . ' ' . $this->tag->getTitle());
36+
} else {
37+
return $this->category->getTitle();
38+
}
39+
}
40+
if ($this->tag) {
41+
return $this->tag->getTitle();
42+
}
43+
return '';
44+
}
45+
46+
/**
47+
* Description is simply the concatenated Category and Tag titles (separated by a SPACE character).
48+
*/
49+
public function getDescription(): string
50+
{
51+
if ($this->category) {
52+
if ($this->tag) {
53+
return trim($this->category->getDescription() . ' ' . $this->tag->getDescription());
54+
} else {
55+
return $this->category->getDescription();
56+
}
57+
}
58+
if ($this->tag) {
59+
return $this->tag->getDescription();
60+
}
61+
return '';
62+
}
63+
64+
/**
65+
* @return array<ComparisonInterface>
66+
* @throws InvalidQueryException
67+
*/
68+
public function getConstraints(QueryInterface $query): array
69+
{
70+
$constraints = [];
71+
if ($this->category) {
72+
$constraints[] = $query->contains('categories', $this->category);
73+
}
74+
if ($this->tag) {
75+
$constraints[] = $query->contains('tags', $this->tag);
76+
}
77+
return $constraints;
78+
}
79+
80+
public function getCategory(): ?Category
81+
{
82+
return $this->category;
83+
}
84+
85+
public function setCategory(?Category $category): void
86+
{
87+
$this->category = $category;
88+
}
89+
90+
public function getTag(): ?Tag
91+
{
92+
return $this->tag;
93+
}
94+
95+
public function setTag(?Tag $tag): void
96+
{
97+
$this->tag = $tag;
98+
}
99+
}

Classes/Domain/Filter/PostFilter.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
/*
5+
* This file is part of the package t3g/blog.
6+
*
7+
* For the full copyright and license information, please read the
8+
* LICENSE file that was distributed with this source code.
9+
*/
10+
11+
namespace T3G\AgencyPack\Blog\Domain\Filter;
12+
13+
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface;
14+
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
15+
16+
/**
17+
* Interface for post filters used in the listPostsByFilterAction.
18+
*/
19+
interface PostFilter
20+
{
21+
/**
22+
* The name of this filter, used for rendering the corresponding Fluid partial.
23+
*/
24+
public function getName(): string;
25+
26+
/**
27+
* The title of the filter result, used in MetaTagService.
28+
*/
29+
public function getTitle(): string;
30+
31+
/**
32+
* A description of the filter result, used in MetaTagService.
33+
*/
34+
public function getDescription(): string;
35+
36+
/**
37+
* Apply current filter values in given Posts query.
38+
*
39+
* @return array<ComparisonInterface>
40+
*/
41+
public function getConstraints(QueryInterface $query): array;
42+
}

Classes/Domain/Repository/PostRepository.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Psr\Http\Message\ServerRequestInterface;
1414
use T3G\AgencyPack\Blog\Constants;
1515
use T3G\AgencyPack\Blog\DataTransferObject\PostRepositoryDemand;
16+
use T3G\AgencyPack\Blog\Domain\Filter\PostFilter;
1617
use T3G\AgencyPack\Blog\Domain\Model\Author;
1718
use T3G\AgencyPack\Blog\Domain\Model\Category;
1819
use T3G\AgencyPack\Blog\Domain\Model\Post;
@@ -226,6 +227,18 @@ public function findAllByTag(Tag $tag): QueryResultInterface
226227
return $query->matching($query->logicalAnd(...$constraints))->execute();
227228
}
228229

230+
public function findAllByFilter(PostFilter $filter): QueryResultInterface
231+
{
232+
$query = $this->createQuery();
233+
$constraints = array_merge($this->defaultConstraints, $filter->getConstraints($query));
234+
$storagePidConstraint = $this->getStoragePidConstraint();
235+
if ($storagePidConstraint instanceof ComparisonInterface) {
236+
$constraints[] = $storagePidConstraint;
237+
}
238+
239+
return $query->matching($query->logicalAnd(...$constraints))->execute();
240+
}
241+
229242
public function findByMonthAndYear(int $year, int $month = null): QueryResultInterface
230243
{
231244
$query = $this->createQuery();

Configuration/TCA/Overrides/tt_content.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@
5757
);
5858
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['blog_archive'] = 'select_key';
5959

60+
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
61+
'Blog',
62+
'Filter',
63+
'LLL:EXT:blog/Resources/Private/Language/locallang_db.xlf:plugin.blog_filter.title',
64+
'plugin-blog-filter'
65+
);
66+
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['blog_filter'] = 'select_key';
67+
6068
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
6169
'Blog',
6270
'Sidebar',

Configuration/TsConfig/Page/Wizards.tsconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ mod.wizards.newContentElement.wizardItems.blog {
5959
list_type = blog_archive
6060
}
6161
}
62+
blog_filter {
63+
iconIdentifier = plugin-blog-filter
64+
title = LLL:EXT:blog/Resources/Private/Language/locallang_db.xlf:plugin.blog_filter.title
65+
description = LLL:EXT:blog/Resources/Private/Language/locallang_db.xlf:plugin.blog_filter.description
66+
tt_content_defValues {
67+
CType = list
68+
list_type = blog_filter
69+
}
70+
}
6271
blog_demandedposts {
6372
iconIdentifier = plugin-blog-demandedposts
6473
title = LLL:EXT:blog/Resources/Private/Language/locallang_db.xlf:plugin.blog_demandedposts.title

Configuration/TypoScript/Static/setup.typoscript

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,8 @@ blog_rss_author < blog_rss_posts
440440
blog_rss_author.typeNum = 250
441441
blog_rss_author.10 < tt_content.list.20.blog_authorposts
442442
blog_rss_author.10.format = rss
443+
444+
blog_rss_filter < blog_rss_posts
445+
blog_rss_filter.typeNum = 260
446+
blog_rss_filter.10 < tt_content.list.20.blog_filter
447+
blog_rss_filter.10.format = rss

0 commit comments

Comments
 (0)