• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FluidTYPO3 / vhs / 28366223948

29 Jun 2026 10:41AM UTC coverage: 69.744% (-0.1%) from 69.872%
28366223948

push

github

NamelessCoder
[REMOVAL] Make resource ViewHelpers demand uid or record

FAL etc. resource ViewHelpers no longer allow calling without both
record and uid arguments, which previously would have attempted
to resolve the current record data from ContentObectRenderer.

Calling these ViewHelpers must now include either record or uid as
arguments. Argument descriptions updated accordingly.

Close: #1963

4 of 5 new or added lines in 1 file covered. (80.0%)

6 existing lines in 2 files now uncovered.

4790 of 6868 relevant lines covered (69.74%)

4.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.58
/Classes/ViewHelpers/Resource/Record/FalViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Resource\Record;
3

4
/*
5
 * This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10

11
use FluidTYPO3\Vhs\Proxy\DoctrineQueryProxy;
12
use FluidTYPO3\Vhs\Proxy\ResourceFactoryProxy;
13
use FluidTYPO3\Vhs\Utility\RequestResolver;
14
use FluidTYPO3\Vhs\Utility\ResourceUtility;
15
use TYPO3\CMS\Core\Database\Connection;
16
use TYPO3\CMS\Core\Database\ConnectionPool;
17
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
18
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
19
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
20
use TYPO3\CMS\Core\Resource\FileReference;
21
use TYPO3\CMS\Core\Utility\ArrayUtility;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Core\Versioning\VersionState;
24

25
/**
26
 * Resolve FAL relations and return file records.
27
 *
28
 * ### Render a single image linked from a TCA record
29
 *
30
 * We assume that the table `tx_users` has a column `photo`, which is a FAL
31
 * relation field configured with
32
 * [`ExtensionManagementUtility::getFileFieldTCAConfig()`]
33
 * (https://docs.typo3.org/typo3cms/TCAReference/Reference/Columns/Inline/Index.html#file-abstraction-layer).
34
 * The template also has a `user` variable containing one of the table's
35
 * records.
36
 *
37
 * At first, fetch the record and store it in a variable.
38
 * Then use `<f:image>` to render it:
39
 *
40
 * ```
41
 * {v:resource.record.fal(table: 'tx_users', field: 'photo', record: user)
42
 *  -> v:iterator.first()
43
 *  -> v:variable.set(name: 'image')}
44
 * <f:if condition="{image}">
45
 *   <f:image treatIdAsReference="1" src="{image.id}" title="{image.title}" alt="{image.alternative}"/>
46
 * </f:if>
47
 * ```
48
 *
49
 * Use the `uid` attribute if you don't have a `record`.
50
 */
51
class FalViewHelper extends AbstractRecordResourceViewHelper
52
{
53
    protected ResourceFactoryProxy $resourceFactory;
54

55
    /**
56
     * @var boolean
57
     */
58
    protected $escapeOutput = false;
59

60
    public function __construct()
61
    {
62
        /** @var ResourceFactoryProxy $resourceFactory */
63
        $resourceFactory = GeneralUtility::makeInstance(ResourceFactoryProxy::class);
8✔
64
        $this->resourceFactory = $resourceFactory;
8✔
65
    }
66

67
    public function initializeArguments(): void
68
    {
69
        parent::initializeArguments();
6✔
70
        $this->registerArgument(
6✔
71
            'asObjects',
6✔
72
            'bool',
6✔
73
            'Can be set to TRUE to return objects instead of file information arrays.',
6✔
74
            false,
6✔
75
            false
6✔
76
        );
6✔
77
    }
78

79
    public function getResource(FileReference $fileReference): array
80
    {
81
        $file = $fileReference->getOriginalFile();
2✔
82
        $fileReferenceProperties = $fileReference->getProperties();
2✔
83
        $fileProperties = ResourceUtility::getFileArray($file);
2✔
84
        ArrayUtility::mergeRecursiveWithOverrule($fileProperties, $fileReferenceProperties, true, true, false);
2✔
85
        return $fileProperties;
2✔
86
    }
87

88
    public function getResources(array $record): array
89
    {
90
        if (empty($record)) {
6✔
UNCOV
91
            return [];
×
92
        }
93

94
        if (isset($record['t3ver_oid']) && (int) $record['t3ver_oid'] !== 0) {
6✔
95
            $sqlRecordUid = $record['t3ver_oid'];
×
96
        } elseif (isset($record['_LOCALIZED_UID'])) {
6✔
97
            $sqlRecordUid = $record['_LOCALIZED_UID'];
×
98
        } elseif (isset($record['_PAGES_OVERLAY_UID'])) {
6✔
99
            $sqlRecordUid = $record['_PAGES_OVERLAY_UID'];
×
100
        } else {
101
            $sqlRecordUid = $record[$this->idField];
6✔
102
        }
103

104
        $references = $this->fetchFileReferences($sqlRecordUid);
6✔
105

106
        $fileReferences = [];
6✔
107

108
        foreach ($references as $reference) {
6✔
109
            try {
110
                // Just passing the reference uid, the factory is doing workspace
111
                // overlays automatically depending on the current environment
112
                $fileReferences[] = $this->resourceFactory->getFileReferenceObject($reference['uid'] ?? 0);
4✔
113
            } catch (ResourceDoesNotExistException $exception) {
×
114
                // No handling, just omit the invalid reference uid
115
                continue;
×
116
            }
117
        }
118

119
        $resources = [];
6✔
120
        foreach ($fileReferences as $file) {
6✔
121
            // Exclude workspace deleted files references
122
            if ($file->getProperty('t3ver_state') !== VersionState::DELETE_PLACEHOLDER) {
4✔
123
                try {
124
                    $resources[] = $this->arguments['asObjects'] ? $file : $this->getResource($file);
4✔
125
                } catch (\InvalidArgumentException $error) {
×
126
                    // Pokemon-style, catch-all and suppress. This exception type is thrown if a file gets removed.
127
                }
128
            }
129
        }
130
        return $resources;
6✔
131
    }
132

133
    protected function fetchFileReferences(int $sqlRecordUid): array
134
    {
135
        /** @var ConnectionPool $connectionPool */
136
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
4✔
137
        /** @var QueryBuilder $queryBuilder */
138
        $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
4✔
139

140
        if (RequestResolver::isPreview()) {
4✔
141
            $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
×
142
        }
143

144
        $queryBuilder->createNamedParameter($this->getTable(), Connection::PARAM_STR, ':tablenames');
4✔
145
        $queryBuilder->createNamedParameter($sqlRecordUid, Connection::PARAM_INT, ':uid_foreign');
4✔
146
        $queryBuilder->createNamedParameter($this->getField(), Connection::PARAM_STR, ':fieldname');
4✔
147

148
        $queryBuilder
4✔
149
            ->select('uid')
4✔
150
            ->from('sys_file_reference')
4✔
151
            ->where(
4✔
152
                $queryBuilder->expr()->eq('tablenames', ':tablenames')
4✔
153
            )
4✔
154
            ->andWhere(
4✔
155
                $queryBuilder->expr()->eq('uid_foreign', ':uid_foreign')
4✔
156
            )
4✔
157
            ->andWhere(
4✔
158
                $queryBuilder->expr()->eq('fieldname', ':fieldname')
4✔
159
            );
4✔
160

161
        if (($workspaceId = RequestResolver::getWorkspaceId())) {
4✔
162
            $queryBuilder->createNamedParameter($workspaceId, Connection::PARAM_INT, ':t3ver_wsid');
×
163
            $queryBuilder
×
164
                ->andWhere(
×
165
                    $queryBuilder->expr()->eq('deleted', 0)
×
166
                )
×
167
                ->andWhere(
×
168
                    $queryBuilder->expr()->eq('t3ver_wsid', 0)
×
169
                    . ' OR ' .
×
170
                    $queryBuilder->expr()->eq('t3ver_wsid', ':t3ver_wsid')
×
171
                )
×
172
                ->andWhere(
×
173
                    $queryBuilder->expr()->neq('pid', -1)
×
174
                );
×
175
        } else {
176
            $queryBuilder
4✔
177
                ->andWhere(
4✔
178
                    $queryBuilder->expr()->eq('deleted', 0)
4✔
179
                )
4✔
180
                ->andWhere(
4✔
181
                    $queryBuilder->expr()->lte('t3ver_state', 0)
4✔
182
                )
4✔
183
                ->andWhere(
4✔
184
                    $queryBuilder->expr()->neq('pid', -1)
4✔
185
                )
4✔
186
                ->andWhere(
4✔
187
                    $queryBuilder->expr()->eq('hidden', 0)
4✔
188
                );
4✔
189
        }
190

191
        $queryBuilder->orderBy('sorting_foreign');
4✔
192

193
        // Execute
194
        $statement = DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder);
4✔
195
        /** @var array[] $references */
196
        $references = DoctrineQueryProxy::fetchAllAssociative($statement);
4✔
197
        return $references;
4✔
198
    }
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc