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

FluidTYPO3 / vhs / 28026578140

23 Jun 2026 12:35PM UTC coverage: 71.202% (-0.2%) from 71.402%
28026578140

Pull #1977

github

web-flow
Merge b23e7e66c into 557bc4cc1
Pull Request #1977: [TASK] Clean up record/FAL ViewHelpers pattern

50 of 75 new or added lines in 4 files covered. (66.67%)

4903 of 6886 relevant lines covered (71.2%)

14.62 hits per line

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

76.74
/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);
28✔
64
        $this->resourceFactory = $resourceFactory;
28✔
65
    }
66

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

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

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

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

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

106
        $fileReferences = [];
21✔
107

108
        foreach ($references as $reference) {
21✔
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);
14✔
NEW
113
            } catch (ResourceDoesNotExistException $exception) {
×
114
                // No handling, just omit the invalid reference uid
NEW
115
                continue;
×
116
            }
117
        }
118

119
        $resources = [];
21✔
120
        foreach ($fileReferences as $file) {
21✔
121
            // Exclude workspace deleted files references
122
            if ($file->getProperty('t3ver_state') !== VersionState::DELETE_PLACEHOLDER) {
14✔
123
                try {
124
                    $resources[] = $this->arguments['asObjects'] ? $file : $this->getResource($file);
14✔
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;
21✔
131
    }
132

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

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

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

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

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

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

193
        // Execute
194
        $statement = DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder);
14✔
195
        /** @var array[] $references */
196
        $references = DoctrineQueryProxy::fetchAllAssociative($statement);
14✔
197
        return $references;
14✔
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