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

FluidTYPO3 / vhs / 13566190336

27 Feb 2025 12:18PM UTC coverage: 72.127% (-0.6%) from 72.746%
13566190336

push

github

NamelessCoder
[TER] 7.1.0

5649 of 7832 relevant lines covered (72.13%)

20.01 hits per line

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

74.26
/Classes/ViewHelpers/Content/AbstractContentViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Content;
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\Traits\SlideViewHelperTrait;
13
use FluidTYPO3\Vhs\Utility\ContentObjectFetcher;
14
use TYPO3\CMS\Core\Database\ConnectionPool;
15
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
16
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
17

18
/**
19
 * Base class: Content ViewHelpers
20
 */
21
abstract class AbstractContentViewHelper extends AbstractViewHelper
22
{
23
    use SlideViewHelperTrait;
24

25
    /**
26
     * @var ConfigurationManagerInterface
27
     */
28
    protected $configurationManager;
29

30
    /**
31
     * @var boolean
32
     */
33
    protected $escapeOutput = false;
34

35
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
36
    {
37
        $this->configurationManager = $configurationManager;
105✔
38
    }
39

40
    public function initializeArguments(): void
41
    {
42
        $this->registerArgument('column', 'integer', 'Column position number (colPos) of the column to render');
35✔
43
        $this->registerArgument(
35✔
44
            'order',
35✔
45
            'string',
35✔
46
            'Optional sort field of content elements - RAND() supported. Note that when sliding is enabled, the ' .
35✔
47
            'sorting will be applied to records on a per-page basis and not to the total set of collected records.',
35✔
48
            false,
35✔
49
            'sorting'
35✔
50
        );
35✔
51
        $this->registerArgument('sortDirection', 'string', 'Optional sort direction of content elements', false, 'ASC');
35✔
52
        $this->registerArgument(
35✔
53
            'pageUid',
35✔
54
            'integer',
35✔
55
            'If set, selects only content from this page UID. Ignored when "contentUids" is specified.',
35✔
56
            false,
35✔
57
            0
35✔
58
        );
35✔
59
        $this->registerArgument(
35✔
60
            'contentUids',
35✔
61
            'array',
35✔
62
            'If used, replaces all conditions with an "uid IN (1,2,3)" style condition using the UID values from ' .
35✔
63
            'this array'
35✔
64
        );
35✔
65
        $this->registerArgument(
35✔
66
            'sectionIndexOnly',
35✔
67
            'boolean',
35✔
68
            'If TRUE, only renders/gets content that is marked as "include in section index"',
35✔
69
            false,
35✔
70
            false
35✔
71
        );
35✔
72
        $this->registerArgument('loadRegister', 'array', 'List of LOAD_REGISTER variable');
35✔
73
        $this->registerArgument('render', 'boolean', 'Render result', false, true);
35✔
74
        $this->registerArgument(
35✔
75
            'hideUntranslated',
35✔
76
            'boolean',
35✔
77
            'If FALSE, will NOT include elements which have NOT been translated, if current language is NOT the ' .
35✔
78
            'default language. Default is to show untranslated elements but never display the original if there is ' .
35✔
79
            'a translated version',
35✔
80
            false,
35✔
81
            false
35✔
82
        );
35✔
83
        $this->registerSlideArguments();
35✔
84
    }
85

86
    protected function getContentRecords(): array
87
    {
88
        /** @var int $limit */
89
        $limit = $this->arguments['limit'];
28✔
90
        /** @var int $slide */
91
        $slide = $this->arguments['slide'];
28✔
92

93
        $pageUid = $this->getPageUid();
28✔
94

95
        if ((integer) $slide === 0) {
28✔
96
            $contentRecords = $this->getSlideRecordsFromPage($pageUid, $limit);
28✔
97
        } else {
98
            $contentRecords = $this->getSlideRecords($pageUid, $limit);
×
99
        }
100

101
        if ($this->arguments['render']) {
28✔
102
            $contentRecords = $this->getRenderedRecords($contentRecords);
14✔
103
        }
104

105
        return $contentRecords;
28✔
106
    }
107

108
    protected function getSlideRecordsFromPage(int $pageUid, ?int $limit): array
109
    {
110
        /** @var string $direction */
111
        $direction = $this->arguments['sortDirection'];
28✔
112
        /** @var string $order */
113
        $order = $this->arguments['order'];
28✔
114
        if (!empty($order)) {
28✔
115
            $sortDirection = strtoupper(trim($direction));
28✔
116
            if ('ASC' !== $sortDirection && 'DESC' !== $sortDirection) {
28✔
117
                $sortDirection = 'ASC';
×
118
            }
119
            $order = $order . ' ' . $sortDirection;
28✔
120
        }
121

122
        $contentUids = $this->arguments['contentUids'];
28✔
123
        if (is_array($contentUids) && !empty($contentUids)) {
28✔
124
            return $GLOBALS['TSFE']->cObj->getRecords(
×
125
                'tt_content',
×
126
                [
×
127
                    'uidInList' => implode(',', $contentUids),
×
128
                    'orderBy' => $order,
×
129
                    'max' => $limit,
×
130
                    // Note: pidInList must not use $pageUid which defaults to current PID. Use argument-passed pageUid!
131
                    // A value of zero here removes the "pid" from the condition generated by ContentObjectRenderer.
132
                    'pidInList' => (integer)$pageUid,
×
133
                    'includeRecordsWithoutDefaultTranslation' => !$this->arguments['hideUntranslated']
×
134
                ]
×
135
            );
×
136
        }
137

138
        $conditions = '1=1';
28✔
139
        if (is_numeric($this->arguments['column'])) {
28✔
140
            $conditions = sprintf('colPos = %d', (integer) $this->arguments['column']);
×
141
        }
142
        if ($this->arguments['sectionIndexOnly']) {
28✔
143
            $conditions .= ' AND sectionIndex = 1';
×
144
        }
145

146
        $rows = $GLOBALS['TSFE']->cObj->getRecords(
28✔
147
            'tt_content',
28✔
148
            [
28✔
149
                'where' => $conditions,
28✔
150
                'orderBy' => $order,
28✔
151
                'max' => $limit,
28✔
152
                'pidInList' => $pageUid,
28✔
153
                'includeRecordsWithoutDefaultTranslation' => !$this->arguments['hideUntranslated']
28✔
154
            ]
28✔
155
        );
28✔
156

157
        return $rows;
28✔
158
    }
159

160
    /**
161
     * Gets the configured, or the current page UID if
162
     * none is configured in arguments and no content_from_pid
163
     * value exists in the current page record's attributes.
164
     */
165
    protected function getPageUid(): int
166
    {
167
        /** @var array|null $contentUids */
168
        $contentUids = $this->arguments['contentUids'] ?? null;
28✔
169

170
        if (!empty($contentUids)) {
28✔
171
            return 0;
×
172
        }
173

174
        /** @var int $pageUid */
175
        $pageUid = $this->arguments['pageUid'];
28✔
176

177
        $pageUid = (integer) $pageUid;
28✔
178
        if (1 > $pageUid) {
28✔
179
            $pageUid = (integer) ($GLOBALS['TSFE']->page['content_from_pid'] ?? 0);
28✔
180
        }
181
        if (1 > $pageUid) {
28✔
182
            $pageUid = (integer) ($GLOBALS['TSFE']->id ?? 0);
28✔
183
        }
184
        return $pageUid;
28✔
185
    }
186

187
    /**
188
     * This function renders an array of tt_content record into an array of rendered content
189
     * it returns a list of elements rendered by typoscript RECORD function
190
     */
191
    protected function getRenderedRecords(array $rows): array
192
    {
193
        $contentObject = ContentObjectFetcher::resolve($this->configurationManager);
14✔
194

195
        /** @var array $loadRegister */
196
        $loadRegister = $this->arguments['loadRegister'];
14✔
197
        if (!empty($loadRegister) && $contentObject !== null) {
14✔
198
            $contentObject->cObjGetSingle('LOAD_REGISTER', $loadRegister);
×
199
        }
200
        $elements = [];
14✔
201
        foreach ($rows as $row) {
14✔
202
            $elements[] = static::renderRecord($row);
×
203
        }
204
        if (!empty($loadRegister) && $contentObject !== null) {
14✔
205
            $contentObject->cObjGetSingle('RESTORE_REGISTER', []);
×
206
        }
207
        return $elements;
14✔
208
    }
209

210
    /**
211
     * This function renders a raw tt_content record into the corresponding
212
     * element by typoscript RENDER function. We keep track of already
213
     * rendered records to avoid rendering the same record twice inside the
214
     * same nested stack of content elements.
215
     */
216
    protected static function renderRecord(array $row): ?string
217
    {
218
        if (0 < ($GLOBALS['TSFE']->recordRegister['tt_content:' . $row['uid']] ?? 0)) {
7✔
219
            return null;
×
220
        }
221
        $conf = [
7✔
222
            'tables' => 'tt_content',
7✔
223
            'source' => $row['uid'],
7✔
224
            'dontCheckPid' => 1
7✔
225
        ];
7✔
226
        $parent = $GLOBALS['TSFE']->currentRecord;
7✔
227
        // If the currentRecord is set, we register, that this record has invoked this function.
228
        // It's should not be allowed to do this again then!!
229
        if (!empty($parent)) {
7✔
230
            if (isset($GLOBALS['TSFE']->recordRegister[$parent])) {
×
231
                ++$GLOBALS['TSFE']->recordRegister[$parent];
×
232
            } else {
233
                $GLOBALS['TSFE']->recordRegister[$parent] = 1;
×
234
            }
235
        }
236
        $html = $GLOBALS['TSFE']->cObj->cObjGetSingle('RECORDS', $conf);
7✔
237

238
        $GLOBALS['TSFE']->currentRecord = $parent;
7✔
239
        if (!empty($parent)) {
7✔
240
            --$GLOBALS['TSFE']->recordRegister[$parent];
×
241
        }
242
        return $html;
7✔
243
    }
244

245
    protected function executeSelectQuery(string $fields, string $condition, string $order, int $limit): array
246
    {
247
        $queryBuilder = (new ConnectionPool())->getConnectionForTable('tt_content')->createQueryBuilder();
×
248
        $queryBuilder->select($fields)->from('tt_content')->where($condition);
×
249
        if ($order) {
×
250
            $orderings = explode(' ', $order);
×
251
            $queryBuilder->orderBy($orderings[0], $orderings[1]);
×
252
        }
253
        if ($limit) {
×
254
            $queryBuilder->setMaxResults($limit);
×
255
        }
256
        $result = DoctrineQueryProxy::executeQueryOnQueryBuilder($queryBuilder);
×
257
        return DoctrineQueryProxy::fetchAllAssociative($result);
×
258
    }
259

260
    protected function generateSelectQuery(string $fields, string $condition): string
261
    {
262
        $queryBuilder = (new ConnectionPool())->getConnectionForTable('tt_content')->createQueryBuilder();
×
263
        $queryBuilder->select($fields)->from('tt_content')->where($condition);
×
264
        return $queryBuilder->getSQL();
×
265
    }
266
}
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