TYPO3 Website Development: Fluid ViewHelpers
Access System Categories in Content Elements Templates
The hurdles you’ll stumble upon in TYPO3 are sometimes so absurd you wouldn’t make them up … in the backend form of content elements you can assign system categories to each content element – but don’t dare believing you could access them in the fluid_styled_content templates of those content elements. Because you can’t.
Example
I have a content element of type Text & Media based on the fluid_styled_content template Textmedia.html. I assigned it to two different categories. In the template I wanted to add the slug
value of each category to a class attribute for later use with a JS filter. I was trying to achieve that by going through the categories with a loop <for each="{data.categories}" as="category">
and hoped to get an array with the data for each category, available within the loop: {category.slug}
or {category.title}
.
Well, it appears my thought was completely preposterous! I checked the {data}
array with <debug>
and the only available information is the number of assigned categories in {data.categories}
. Very helpful indeed. So I had to create a …
ViewHelper Returning Category Data
(add it to your Site Package Extension folder Classes/ViewHelpers for example)
By default this ViewHelper returns an array with category data of a content element (tt_content) specified by its UID, which is the only required argument. But you can do many more things with it. Some examples:
- return the categories of an element of any other table but tt_content (by using the optional
tableName
argument) – you could use it in templates of your own extensions, although it shouldn’t be necessary if you had added the categories to the model correctly - return an HTML tag attribute (argument
htmlAttr
) with a string containing e.g. theslug
(argumentfieldString
) values of all categories - return a space separated (argument
stringSeparator
) string with a fixed prefix (argumentcatPrefix
) for each category’s slug value
Please check the template examples in the class comment section as well as the description of the arguments in method initializeArguments()
<?php
namespace YourVendor\YourExtension\ViewHelpers;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* will return certain system categories (sys_category) data of an element
* either as an array or as a string with certain parameters
*
* EXAMPLES:
*
* EMBEDDING IN TEMPLATE: {namespace my = YourVendor\YourExtension\ViewHelpers}
*
* call an array with all category data to be used in a loop, e.g. for an HTML tag for each category:
* <f:if condition="{data.categories}">
* <f:for each="{my:CategoriesOutput(recUid: data.uid)}" as="category">
* <span class="{category.slug}">{category.title}</span>
* </f:for>
* </f:if>
*
* call a “data-categories” attribute with the slug field of the categories, comma-separated (default):
* {my:CategoriesOutput(recUid: data.uid, fieldString: 'slug', htmlAttr: 'data-categories')}
* output: ' data-categories="catx,caty"'
*
* call all categories as CSS classes (space as string separator, prefix 'cat-' for each category)
* {my:CategoriesOutput(recUid: data.uid, fieldString: 'slug', stringSeparator: ' ', catPrefix: 'cat-')}
* output: 'cat-catx cat-caty'
*/
class CategoriesOutputViewHelper extends AbstractViewHelper
{
protected $escapeOutput = false;
public function initializeArguments()
{
$this->registerArgument('recUid', 'integer', 'record UID, e.g. of a content element', true);
$this->registerArgument('tableName', 'string', 'optional: table of records you want the categories returned for (default: tt_content)', false, 'tt_content');
$this->registerArgument('fieldString', 'string', 'optional: name of sys_categories table field – if given, the return value will be a string', false, null);
$this->registerArgument('stringSeparator', 'string', 'optional: separator for string', false, ',');
$this->registerArgument('htmlAttr', 'string', 'optional: wrap in attribute for HTML tag (in case of fieldString given)', false, null);
$this->registerArgument('catPrefix', 'string', 'optional: prefix for each category (e.g. for CSS classes)', false, null);
}
/**
* @return mixed
*/
public function render()
{
$recUid = $this->arguments['recUid'];
$tableName = $this->arguments['tableName'];
$fieldString = $this->arguments['fieldString'];
$stringSeparator = $this->arguments['stringSeparator'];
$htmlAttr = $this->arguments['htmlAttr'];
$catPrefix = $this->arguments['catPrefix'];
/**
* default query for sys_category table
*/
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_category');
/**
* select the fields that will be returned, use asterisk for all
*/
$queryBuilder->select('sys_category.uid', 'sys_category.title', 'sys_category.slug', 'sys_category_record_mm.uid_foreign', 'sys_category_record_mm.tablenames');
$queryBuilder->from('sys_category');
$queryBuilder->join(
'sys_category',
'sys_category_record_mm',
'sys_category_record_mm',
$queryBuilder->expr()->eq('sys_category_record_mm.uid_local', $queryBuilder->quoteIdentifier('sys_category.uid'))
);
$queryBuilder->where(
$queryBuilder->expr()->eq('sys_category_record_mm.uid_foreign', $queryBuilder->createNamedParameter($recUid, \PDO::PARAM_INT)),
$queryBuilder->expr()->like('sys_category_record_mm.tablenames', $queryBuilder->createNamedParameter($tableName))
);
$result = $queryBuilder->execute();
$res = [];
$returnString = '';
$i = 1;
while ($row = $result->fetch()) {
$res[] = $row;
if ($fieldString !== null) {
if (isset($row[$fieldString])) {
$returnString .= ($i === 1) ? '' : $stringSeparator;
$returnString .= ($catPrefix !== null) ? $catPrefix : '';
$returnString .= $row[$fieldString];
}
}
$i++;
}
if ($returnString !== '') {
return ($htmlAttr !== null)
? ' ' . $htmlAttr . '="' . $returnString . '"'
: $returnString;
} elseif ($fieldString !== null) {
return '';
} else {
return $res;
}
}
}
}