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

FluidTYPO3 / flux / 27757015719

18 Jun 2026 11:42AM UTC coverage: 89.162% (-3.5%) from 92.646%
27757015719

Pull #2288

github

web-flow
Merge f2162927f into 2614049c6
Pull Request #2288: [FEATURE] Prepare for v14 support

210 of 348 new or added lines in 56 files covered. (60.34%)

121 existing lines in 9 files now uncovered.

6228 of 6985 relevant lines covered (89.16%)

40.84 hits per line

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

83.78
/Classes/ViewHelpers/Content/GetViewHelper.php
1
<?php
2
namespace FluidTYPO3\Flux\ViewHelpers\Content;
3

4
/*
5
 * This file is part of the FluidTYPO3/Flux 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\Flux\Form\Container\Column;
12
use FluidTYPO3\Flux\Form\Container\Grid;
13
use FluidTYPO3\Flux\Hooks\HookHandler;
14
use FluidTYPO3\Flux\Provider\AbstractProvider;
15
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
16
use FluidTYPO3\Flux\Utility\ColumnNumberUtility;
17
use FluidTYPO3\Flux\Utility\ContentObjectFetcher;
18
use FluidTYPO3\Flux\ViewHelpers\AbstractViewHelper;
19
use FluidTYPO3\Flux\ViewHelpers\FormViewHelper;
20
use TYPO3\CMS\Backend\Utility\BackendUtility;
21
use TYPO3\CMS\Core\Context\Context;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
25
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
26
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
27

28
/**
29
 * Gets all child content of a record based on area.
30
 *
31
 * The elements are already rendered, they just need to be output.
32
 *
33
 * ### Example: Render all child elements with a border
34
 *
35
 * `fluidcontent` element with one column of child elements.
36
 * Each element gets a red border:
37
 *
38
 *     <f:section name="Configuration">
39
 *      <flux:grid>
40
 *       <flux:grid.row>
41
 *        <flux:grid.column name="teaser" colPos="0"/>
42
 *       </flux:grid.row>
43
 *      </flux:grid>
44
 *     </f:section>
45
 *
46
 *     <f:section name="Main">
47
 *      <f:for each="{flux:content.get(area:'teaser')}" as="element">
48
 *       <div style="border: 1px solid red">
49
 *        <f:format.raw>{element}</f:format.raw>
50
 *       </div>
51
 *      </f:for>
52
 *     </f:section>
53
 */
54
class GetViewHelper extends AbstractViewHelper
55
{
56
    /**
57
     * @var boolean
58
     */
59
    protected $escapeOutput = false;
60

61
    protected static ?ConfigurationManagerInterface $configurationManager = null;
62
    protected static ?WorkspacesAwareRecordService $recordService = null;
63

64
    public function initializeArguments(): void
65
    {
66
        $this->registerArgument('area', 'string', 'Name or "colPos" value of the content area to render', true);
8✔
67
        $this->registerArgument('limit', 'integer', 'Optional limit to the number of content elements to render');
8✔
68
        $this->registerArgument('offset', 'integer', 'Optional offset to the limit', false, 0);
8✔
69
        $this->registerArgument(
8✔
70
            'order',
8✔
71
            'string',
8✔
72
            'Optional sort order of content elements - RAND() supported',
8✔
73
            false,
8✔
74
            'sorting'
8✔
75
        );
8✔
76
        $this->registerArgument('sortDirection', 'string', 'Optional sort direction of content elements', false, 'ASC');
8✔
77
        $this->registerArgument(
8✔
78
            'as',
8✔
79
            'string',
8✔
80
            'Variable name to register, then render child content and insert all results as an array of records'
8✔
81
        );
8✔
82
        $this->registerArgument('loadRegister', 'array', 'List of LOAD_REGISTER variable');
8✔
83
        $this->registerArgument('render', 'boolean', 'Optional returning variable as original table rows', false, true);
8✔
84
        $this->registerArgument('hideUntranslated', 'boolean', 'Exclude untranslated records', false, false);
8✔
85
    }
86

87
    /**
88
     * @return array|string|null
89
     */
90
    public function render()
91
    {
92
        return static::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext);
36✔
93
    }
94

95
    /**
96
     * @return string|array|null
97
     */
98
    public static function renderStatic(
99
        array $arguments,
100
        \Closure $renderChildrenClosure,
101
        RenderingContextInterface $renderingContext
102
    ) {
103
        $contentObjectRenderer = ContentObjectFetcher::resolve();
36✔
104

105
        $registerVariables = (array) $arguments['loadRegister'];
36✔
106
        $loadRegister = false;
36✔
107
        if (!empty($registerVariables)) {
36✔
108
            $contentObjectRenderer->cObjGetSingle('LOAD_REGISTER', $registerVariables);
8✔
109
            $loadRegister = true;
8✔
110
        }
111
        $templateVariableContainer = $renderingContext->getVariableProvider();
36✔
112
        $record = $renderingContext->getViewHelperVariableContainer()->get(FormViewHelper::class, 'record');
36✔
113
        if (!is_array($record)) {
36✔
114
            return null;
×
115
        }
116

117
        /** @var Context $context */
118
        $context = GeneralUtility::makeInstance(Context::class);
36✔
119
        $workspaceId = $context->getPropertyFromAspect('workspace', 'id');
36✔
120

121
        if (is_numeric($workspaceId) && $workspaceId > 0) {
36✔
122
            $placeholder = BackendUtility::getWorkspaceVersionOfRecord(
×
NEW
123
                (int) $workspaceId,
×
124
                'tt_content',
×
125
                $record['uid'] ?? 0
×
126
            );
×
127
            if ($placeholder) {
×
128
                // Use the move placeholder if one exists, ensuring that "pid" and "tx_flux_parent" values are taken
129
                // from the workspace-only placeholder.
130
                /** @var array $record */
131
                $record = $placeholder;
×
132
            }
133
        }
134

135
        /** @var AbstractProvider $provider */
136
        $provider = $renderingContext->getViewHelperVariableContainer()->get(FormViewHelper::class, 'provider');
36✔
137
        $grid = $provider->getGrid($record);
36✔
138
        $rows = static::getContentRecords($arguments, $record, $grid);
36✔
139

140
        $elements = false === (bool) $arguments['render'] ? $rows : static::getRenderedRecords($rows);
36✔
141
        if (empty($arguments['as'])) {
36✔
142
            $content = $elements;
12✔
143
        } else {
144
            /** @var string $as */
145
            $as = $arguments['as'];
24✔
146
            if ($templateVariableContainer->exists($as)) {
24✔
147
                $backup = $templateVariableContainer->get($as);
×
148
                $templateVariableContainer->remove($as);
×
149
            }
150
            $templateVariableContainer->add($as, $elements);
24✔
151
            $content = $renderChildrenClosure();
24✔
152
            $templateVariableContainer->remove($as);
24✔
153
            if (isset($backup)) {
24✔
154
                $templateVariableContainer->add($as, $backup);
×
155
            }
156
        }
157
        if ($loadRegister) {
36✔
158
            $contentObjectRenderer->cObjGetSingle('RESTORE_REGISTER', []);
8✔
159
        }
160
        return $content;
36✔
161
    }
162

163
    protected static function getContentRecords(array $arguments, array $parent, Grid $grid): array
164
    {
165
        $columnPosition = $arguments['area'];
36✔
166
        if (!ctype_digit((string) $columnPosition)) {
36✔
167
            $column = $grid->get((string) $columnPosition, true, Column::class);
36✔
168
            if ($column instanceof Column) {
36✔
169
                $columnPosition = $column->getColumnPosition();
36✔
170
            } else {
171
                throw new Exception(
×
172
                    sprintf(
×
173
                        'Argument "column" or "area" for "flux:content.(get|render)" was a string column name "%s", ' .
×
174
                        'but this column was not defined',
×
175
                        $columnPosition
×
176
                    )
×
177
                );
×
178
            }
179
        }
180

181
        /** @var ConnectionPool $connectionPool */
182
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
36✔
183
        $queryBuilder = $connectionPool->getQueryBuilderForTable('tt_content');
36✔
184

185
        $conditions = $queryBuilder->expr()->eq(
36✔
186
            'colPos',
36✔
187
            ColumnNumberUtility::calculateColumnNumberForParentAndColumn(
36✔
188
                ($parent['l18n_parent'] ?? null) ?: $parent['uid'],
36✔
189
                (int) $columnPosition
36✔
190
            )
36✔
191
        );
36✔
192

193
        $rows = ContentObjectFetcher::resolve()->getRecords(
36✔
194
            'tt_content',
36✔
195
            [
36✔
196
                'max' => $arguments['limit'],
36✔
197
                'begin' => $arguments['offset'],
36✔
198
                'orderBy' => $arguments['order'] . ' ' . $arguments['sortDirection'],
36✔
199
                'where' => $conditions,
36✔
200
                'pidInList' => $parent['pid'] ?? null,
36✔
201
                'includeRecordsWithoutDefaultTranslation' => !($arguments['hideUntranslated'] ?? false)
36✔
202
            ]
36✔
203
        );
36✔
204

205
        return HookHandler::trigger(
36✔
206
            HookHandler::NESTED_CONTENT_FETCHED,
36✔
207
            [
36✔
208
                'records' => $rows
36✔
209
            ]
36✔
210
        )['records'];
36✔
211
    }
212

213
    /**
214
     * This function renders an array of tt_content record into an array of rendered content
215
     * it returns a list of elements rendered by typoscript RECORDS function
216
     */
217
    protected static function getRenderedRecords(array $rows): array
218
    {
219
        $elements = [];
32✔
220
        foreach ($rows as $row) {
32✔
221
            $conf = [
4✔
222
                'tables' => 'tt_content',
4✔
223
                'source' => $row['uid'],
4✔
224
                'dontCheckPid' => 1,
4✔
225
            ];
4✔
226
            $elements[] = ContentObjectFetcher::resolve()->cObjGetSingle('RECORDS', $conf);
4✔
227
        }
228
        return HookHandler::trigger(
32✔
229
            HookHandler::NESTED_CONTENT_RENDERED,
32✔
230
            [
32✔
231
                'rows' => $rows,
32✔
232
                'rendered' => $elements
32✔
233
            ]
32✔
234
        )['rendered'];
32✔
235
    }
236
}
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