Extending the domain model of the blog extension
Extending the domain model of existing extension has always been a pain in the ... you know. Extbase is unfortunately lacking this very importance feature. But... luckily evoWeb created the `extender` extension, which exatly fills this gap, as they say in the manual.
I wanted to achieve two things, which the blog extension did not provide by default:
- Using the manual sorting of the category records when displaying the categories of a post.
- Marking a single post as "pinned" or "top post" as some know it from the very popular news extension.
I installed `extender` through composer by
composer req evoweb/extender
A created two partial models, one for the category and one for the post.
<?php
declare(strict_types=1);
namespace Vendor\Sitepackage\Domain\Model;
class Category
{
protected int $sorting;
public function getSorting(): int
{
return $this->sorting;
}
public function setSorting(int $sorting): void
{
$this->sorting = $sorting;
}
}
<?php
declare(strict_types=1);
namespace Vendor\Sitepackage\Domain\Model;
class Post
{
protected bool $isTopPost;
public function getIsTopPost(): bool
{
return $this->isTopPost;
}
public function setIsTopPost(bool $isTopPost): void
{
$this->isTopPost = $isTopPost;
}
}
I added both fields to the TCA. For `sorting` the sole purpose is to make the extbase data mapper recognize and map the field to my model.
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('sys_category', [
'sorting' => [
'label' => 'Sorting (only for extbase mapping)',
'config' => [
'type' => 'passthrough'
]
]
]);
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('pages', [
'is_top_post' => [
'label' => 'LLL:EXT:sitepackage/Resources/Private/Language/locallang_backend.xlf:pages.is_top_post',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => []
],
],
]);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages', 'is_top_post', \T3G\AgencyPack\Blog\Constants::DOKTYPE_BLOG_POST, 'after:publish_date');
Afterwards I registered both model classes for `extender`. Doing so I needed to use `agency_pack` as the extension because `AgencyPack` ist the second part of the original namespace and thereby used as the extension key. Not perfect, but does not seem to cause any problems.
<?php
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['agency_pack']['extender'][\T3G\AgencyPack\Blog\Domain\Model\Category::class]['sitepackage'] = \Vendor\Sitepackage\Domain\Model\Category::class;
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['agency_pack']['extender'][\T3G\AgencyPack\Blog\Domain\Model\Post::class]['sitepackage'] = \Vendor\Sitepackage\Domain\Model\Post::class;
For handling the sorting, I wrote general sort view helper, that could handle array and Traversable types like extbase' QueryResultInterface.
<?php
declare(strict_types=1);
namespace Vender\Sitepackage\ViewHelpers;
use Traversable;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class SortViewHelper extends AbstractViewHelper
{
public function initializeArguments()
{
$this->registerArgument('collection', 'array|' . Traversable::class, 'The collection to sort');
$this->registerArgument('sortBy', 'string', 'Property/key to sort by', true);
$this->registerArgument('order', 'string', 'Sorting direction: asc or desc', false, 'asc');
}
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
$collection = $arguments['collection'] ?? $renderChildrenClosure();
$sortBy = $arguments['sortBy'];
$order = strtolower($arguments['order']);
if ($collection instanceof Traversable) {
$collection = iterator_to_array($collection);
}
usort($collection, function($a, $b) use ($sortBy, $order) {
$sortA = self::getProperty($a, $sortBy);
$sortB = self::getProperty($b, $sortBy);
if ($sortA > $sortB) {
return $order === 'asc' ? 1 : -1;
}
if ($sortA < $sortB) {
return $order === 'asc' ? -1 : 1;
}
return 0;
});
return $collection;
}
protected static function getProperty($data, $property)
{
if (is_array($data)) {
if (array_key_exists($property, $data)) {
return $data[$property];
}
return null;
} else {
$reflectionClass = new \ReflectionClass($data);
$getter = 'get' . ucfirst($property);
$isser = 'is' . ucfirst($property);
$hasser = 'has' . ucfirst($property);
foreach ([$getter, $isser, $hasser] as $method) {
if ($reflectionClass->hasMethod($method) && $reflectionClass->getMethod($method)->isPublic()) {
return $data->$getter();
}
}
if ($reflectionClass->hasProperty($property) && $reflectionClass->getProperty($property)->isPublic()) {
return $data->$property;
}
}
return null;
}
}
Finally I used the view helper in the template as a filter.
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:blog="http://typo3.org/ns/T3G/AgencyPack/Blog/ViewHelpers" xmlns:sp="http://typo3.org/ns/Vendor/Sitepackage/ViewHelpers" data-namespace-typo3-fluid="true">
...
<f:for each="{posts -> sp:sort(sortBy: 'isTopPost', order: 'desc')}" as="post">
<f:render section="Post" arguments="{_all}" />
</f:for>
...
<f:section name="Post">
...
<f:for each="{post.categories -> sp:sort(sortBy: 'sorting')}" as="category">
...
</f:for>
...
</f:section>
...
</html>
I hope this is helpful. Feel free to share, copy and use any of the code examples.
This website uses Disqus for displaying comments. Due to privacy regulations you need to explicitly consent to loading comments from Disqus.
Show comments