0

I created a TYPO3 extension which allows to select several images. I activated metadata via the filemetadata extension. When I browse the image files in a fluid template loop, I try to display the metadata. This works {file.properties.uid} {file.properties.categories}, but for the categories I get a number. Now I would like to have the category selected.

I used this: https://coding.musikinsnetz.de/typo3/fluid-viewhelpers/access-system-categories-in-content-elements-templates

<f: if condition = "{files}">
<f: for each = "{files}" as = "file">
            <f: for each = "{bg2yg: CategoriesOutput (recUid: data.uid)}" as = "category">
                <b style = 'color: blue'> <span class = "{category.title}"> CATEGORY: {category.title} </span> </b> <br />
            </ f: for>
</ f: for>
</ f: if>

This displays the main category because 'data.uid': {bg2yg: CategoriesOutput (recUid: data.uid)}

However, I want the categories of images, I tested this:

<f: for each = "{bg2yg: CategoriesOutput (recUid: file.properties.uid)}" as = "category">

Without success ! Do you have an idea ?

Best regards, Bruno

biesior
  • 55,576
  • 10
  • 125
  • 182
B. Guegan
  • 11
  • 7
  • Please have a deeper look into the copied viewhelper. There's an argument `tableName`which default to `tt_content`. But your categories are not related to this table, but to `sys_file` or `sys_file_reference`. `{bg2yg: CategoriesOutput (recUid: data.uid, tableName: 'sys_file_reference')}` should work. – Julian Hofmann Jul 02 '20 at 07:13
  • Thank you for your quick reply ! No, it doesn't work better ... – B. Guegan Jul 02 '20 at 07:52
  • I was wrong. The `categories` field is configured in `typo3/sysext/core/Configuration/TCA/Overrides/sys_file_metadata.php`. So, there we can find the correct table. – Julian Hofmann Jul 02 '20 at 09:21
  • I don't understand what I need to do, can you expand thank you? I have try `sys_file_metadata` table, but it's failled – B. Guegan Jul 02 '20 at 10:01
  • use `{file}` within your foreach loop to find what you want – biesior Jul 02 '20 at 13:49
  • In fact it is this table `sys_file_metadata` and the category field changes by incrementing when I add an additional category to my image. So I think the ViewHelper is the problem. Thanks for the debugging info! – B. Guegan Jul 02 '20 at 14:10
  • By returning the value of the variable `{file.properties.categories}` of the loop, I get a number corresponding to the number of categories checked per image. Afterwards, the ViewHelper obviously does not do what I would like it to do by returning the `title` of the categories. – B. Guegan Jul 03 '20 at 05:18

2 Answers2

1

It looks like categories are not handled in the model, so the fastest thing you can do is to create your own ViewHelper for fetching them as suggested in answer to a similar question, my fast test for TYPO3 9.5

IMPORTANT! for ver.: 10+ check annotation changes at the bottom of this post.

<?php

namespace VENDOR\Toolbox\ViewHelpers;

use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

/**
 *
 * @package TYPO3
 * @subpackage toolbox
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 2 or later
 * @author Marcus Biesioroff biesior@gmail.com>
 *
 * Sample ViewHelper for listing file's categories
 *
 * Usage:
 * {namespace toolbox=VENDOR\Toolbox\ViewHelpers}
 * or in ext_tables.php:
 * $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['toolbox'] = ['VENDOR\Toolbox\ViewHelpers'];
 *
 * <toolbox:fileCategories file="{file}" />
 * or
 * {toolbox:fileCategories(file: file)}
 */
class FileCategoriesViewHelper extends AbstractViewHelper
{

    /**
     * @var \TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository
     * @inject
     */
    protected $categoryRepository;

    public function initializeArguments()
    {
        parent::initializeArguments();
        $this->registerArgument('file', 'mixed', 'File');
    }


    public function render()
    {
        /** @var FileReference $fileRef */
        $fileRef = $this->arguments['file'];
        $file = $fileRef->getOriginalFile();
        $uid = $file->getUid();
        /** @var QueryResult $res */
        $res =  $this->getCategories($uid);
        return $res->toArray();
    }


    private function getCategories($uid)
    {
        $query = $this->categoryRepository->createQuery();
        $sql = "SELECT sys_category.* FROM sys_category
            INNER JOIN sys_category_record_mm ON sys_category_record_mm.uid_local = sys_category.uid AND sys_category_record_mm.fieldname = 'categories' AND sys_category_record_mm.tablenames = 'sys_file_metadata'
            INNER JOIN sys_file_metadata ON  sys_category_record_mm.uid_foreign = sys_file_metadata.uid
            WHERE sys_file_metadata.file = '" . (int)$uid . "'
            AND sys_category.deleted = 0
            ORDER BY sys_category_record_mm.sorting_foreign ASC";
        return $query->statement($sql)->execute();
    }
}

So you can use it within your Fluid template like:

<h3>File categories:</h3>
<ul>
    <f:for each="{toolbox:fileCategories(file: file)}" as="file_cat">
        <li>{file_cat.title}</li>
    </f:for>
</ul>

Don't forget to use your own vendor and subpackage key.

Of course, after injecting anything don't forget to flush your caches, sometimes maaaany times.

Important info for ver.: 10.* +

According to this article @inject annotation is depreciated in ver. 9.x and removed in ver." 10.x You should use @TYPO3\CMS\Extbase\Annotation\Inject instead.

So proper injection of the categoryRepository in TYPO3 10.x and newer should look like:

/**
 * @var \TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository
 * @TYPO3\CMS\Extbase\Annotation\Inject
 */
protected $categoryRepository;

You can also search for deprecated annotations within your ext via Upgrade module > Scan Extension Files

biesior
  • 55,576
  • 10
  • 125
  • 182
  • Thank you for that answer. However there is an error message : `"exception":"Doctrine\\Common\\Annotations\\AnnotationException: [Semantical Error] The annotation \"@inject\" in property BG2YG\\Hexagonalgallery\\ViewHelpers\\FileCategoriesViewHelper::$categoryRepository was never imported. Did you maybe forget to add a \"use\" statement for this annotation?` So, I did: `use TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository;` But without success, do you have an idea thank you? Best regards, Bruno TYPO3 10.4 the latest ... – B. Guegan Jul 04 '20 at 14:20
  • Actually, the exception isn't caused by repository type, but injection annotation. Old `@inject` annotation was removed in 10.x – biesior Jul 06 '20 at 16:18
  • Thank you, I'm going to watch this, because in the solution I propose below, I find it a bit complicated. – B. Guegan Jul 06 '20 at 17:28
  • Check my edit with info about annotation. I tested it within TYPO3 `10.4.3` and my sample works as required. – biesior Jul 06 '20 at 17:47
  • Probably a dumb question: Trying this, I am getting `Call to undefined method TYPO3\CMS\Core\Resource\File::getOriginalFile()` do you know why this would be? – user500665 Jul 30 '23 at 21:19
  • I just removed `getOriginalFile()` and it seems to work for me. So my only question: Is there a way to get the category slug field to come through? I get the title and uid etc, but not slug. – user500665 Jul 30 '23 at 21:52
  • 1
    @user500665 fast answer: no `getOriginalFile()` is not available as probably you doesn't call it in proper place (hard to know without code sample). TIP: use `f:debug` viewhelper to make sure you are trying to do it on proper object. Optionally create new question with full code and debugging sample and inform us here in the comment, so somebody maybe will find the mistake. – biesior Jul 31 '23 at 05:11
0

thank you for your respective help. Here I coded this:

    <?php
/**
 * This file is part of the "hexagonalgallery" Extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE file that was distributed with this source code.
 */

namespace BG2YG\Hexagonalgallery\ViewHelpers;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3Fluid\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 files:
 *    <f:if condition="{file}">
 *        <f:for each="{my:FileCategoriesOutput(recUid: data.uid)}" as="category">
 *            <span class="{category.title}">{category.title}</span>
 *        </f:for>
 *    </f:if>
 *
 * call a “data-categories” attribute with the slug field of the categories, comma-separated (default):
 *     {my:FileCategoriesOutput(recUid: file.properties.uid, tableName: 'sys_file_metadata', fieldString: 'title', htmlAttr: 'data-categories')}
 *     output: ' data-categories="catx,caty"'
 *
 * call all categories as CSS classes (space as string separator, prefix 'cat-' for each files)
 *     {my:FileCategoriesOutput(recUid: file.properties.uid, tableName: 'sys_file_metadata', fieldString: 'title', stringSeparator: ' ', catPrefix: 'cat-')}
 *     output: 'cat-catx cat-caty'
 */
class FileCategoriesOutputViewHelper 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'];
        define(DEBUG,false);

        /*
            SELECT uid_local FROM sys_file_reference WHERE uid = 152
        */        
        if (DEBUG) 
            echo "<b style='color:blue;'>\$recUid=".$recUid."</b><br />";
        /**
         * default query for sys_file_reference table
         * SQL : SELECT uid_local FROM sys_file_reference WHERE uid = $recUid
         */
        $queryBuilder0 = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
        $queryBuilder0->select('uid_local');
        $queryBuilder0->from('sys_file_reference');
        $queryBuilder0->where(
            $queryBuilder0->expr()->eq('sys_file_reference.uid', $queryBuilder0->createNamedParameter($recUid, \PDO::PARAM_INT)));
        $result_uid = $queryBuilder0->execute();
        $uid=$result_uid->fetch();
        $uid=$uid['uid_local'];
        
        if (DEBUG) 
            echo "<b style='color:blue;'>\$uid=".print_r($uid)."</b><br />";
        
        /**
         * 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_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($uid, \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 (DEBUG) {
            echo "\$returnString=" . $returnString . "<br />";
            echo "\$res=<b style='color:red;'>" . print_r($res) . "</b><br />";
        }
        if ($returnString !== '') {
            return ($htmlAttr !== null)
                ? ' ' . $htmlAttr . '="' . $returnString . '"'
                : $returnString;
        } elseif ($fieldString !== null) {
             return '';
        } else {
             return $res;
        }
    }
}

And it works. Thank you for indicating to me if this code seems to you written in the rules of art.

Best regards, Bruno

B. Guegan
  • 11
  • 7