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

TYPO3-Headless / headless / 9855957232

09 Jul 2024 11:10AM UTC coverage: 65.779% (-0.4%) from 66.175%
9855957232

push

github

web-flow
[FEATURE] `sortByBackendLayout` for CONTENT_JSON (#746)

New option allows to sort colPos by used backend layout on page

Example usage:
```
lib.content = CONTENT_JSON
lib.content {
    table = tt_content
    select {
        orderBy = sorting
    }
    sortByBackendLayout = 1
 }
```
Resolves: #745

0 of 10 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

988 of 1502 relevant lines covered (65.78%)

2.8 hits per line

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

1.89
/Classes/ContentObject/JsonContentContentObject.php
1
<?php
2

3
/*
4
 * This file is part of the "headless" Extension for TYPO3 CMS.
5
 *
6
 * For the full copyright and license information, please read the
7
 * LICENSE.md file that was distributed with this source code.
8
 */
9

10
declare(strict_types=1);
11

12
namespace FriendsOfTYPO3\Headless\ContentObject;
13

14
use FriendsOfTYPO3\Headless\Json\JsonEncoder;
15
use FriendsOfTYPO3\Headless\Json\JsonEncoderInterface;
16
use FriendsOfTYPO3\Headless\Utility\HeadlessUserInt;
17
use TYPO3\CMS\Backend\View\BackendLayoutView;
18
use TYPO3\CMS\Core\Utility\GeneralUtility;
19
use TYPO3\CMS\Frontend\ContentObject\ContentContentObject;
20
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
21

22
use function array_merge;
23
use function count;
24
use function is_array;
25
use function json_decode;
26
use function str_contains;
27
use function trim;
28

29
use const JSON_FORCE_OBJECT;
30

31
/**
32
 * This cObject basically behaves like TYPO3's CONTENT,
33
 * the main difference is that content elements are
34
 * grouped by colPol & encoded into JSON by default.
35
 *
36
 * CONTENT_JSON has the same options as CONTENT but also
37
 * offers two new options for edge cases in json context.
38
 *
39
 * ** merge ** option
40
 * This option allows to generate another CONTENT_JSON call
41
 * in one definition & then merge both results into one
42
 * dataset (useful for handling slide feature of CONTENT cObject).
43
 *
44
 * for example:
45
 *
46
 * lib.content = CONTENT_JSON
47
 * lib.content {
48
 *    table = tt_content
49
 *    select {
50
 *        orderBy = sorting
51
 *        where = {#colPos} != 1
52
 *    }
53
 *    merge {
54
 *        table = tt_content
55
 *        select {
56
 *           orderBy = sorting
57
 *           where = {#colPos} = 1
58
 *       }
59
 *       slide = -1
60
 *    }
61
 *  }
62
 *
63
 * ** doNotGroupByColPos = 0(default)|1 **
64
 * This option allows to return a flat array (without grouping
65
 * by colPos) but still encoded into JSON.
66
 *
67
 * lib.content = CONTENT_JSON
68
 * lib.content {
69
 *    table = tt_content
70
 *    select {
71
 *        orderBy = sorting
72
 *        where = {#colPos} != 1
73
 *    }
74
 *    doNotGroupByColPos = 1
75
 * }
76
 *
77
 * * ** sortByBackendLayout = 0(default)|1 **
78
 * This option allows to return sorted CE by colPos with order by used backendLayout
79
 *
80
 * lib.content = CONTENT_JSON
81
 * lib.content {
82
 *    table = tt_content
83
 *    select {
84
 *        orderBy = sorting
85
 *    }
86
 *    sortByBackendLayout = 1
87
 * }
88
 */
89
class JsonContentContentObject extends ContentContentObject
90
{
91
    private HeadlessUserInt $headlessUserInt;
92
    private JsonEncoderInterface $jsonEncoder;
93

94
    public function __construct()
95
    {
96
        $this->headlessUserInt = GeneralUtility::makeInstance(HeadlessUserInt::class);
31✔
97
        $this->jsonEncoder = GeneralUtility::makeInstance(JsonEncoder::class);
31✔
98
    }
99

100
    /**
101
     * @param array<string,mixed> $conf
102
     */
103
    public function render($conf = []): string
104
    {
105
        if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
×
106
            return '';
×
107
        }
108

109
        $theValue = $this->prepareValue($conf);
×
110

111
        if (isset($conf['merge.']) && is_array($conf['merge.'])) {
×
112
            $theValue = array_merge($theValue, $this->prepareValue($conf['merge.']));
×
113
        }
114

115
        $encodeFlags = 0;
×
116

117
        if ($theValue === [] && $this->isColPolsGroupingEnabled($conf)) {
×
118
            $encodeFlags |= JSON_FORCE_OBJECT;
×
119
        }
120

121
        $theValue = $this->jsonEncoder->encode($theValue, $encodeFlags);
×
122

123
        $wrap = $this->cObj->stdWrapValue('wrap', $conf ?? []);
×
124
        if ($wrap) {
×
125
            $theValue = $this->cObj->wrap($theValue, $wrap);
×
126
        }
127
        if (isset($conf['stdWrap.'])) {
×
128
            $theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']);
×
129
        }
130

131
        return $theValue;
×
132
    }
133

134
    /**
135
     * @param array<string, mixed> $contentElements
136
     * @param array<string, mixed> $conf
137
     * @return array<string,<array<int, mixed>>
138
     */
139
    protected function groupContentElementsByColPos(array $contentElements, array $conf): array
140
    {
141
        $data = [];
×
142

NEW
143
        $groupingEnabled = $this->isColPolsGroupingEnabled($conf);
×
144

145
        foreach ($contentElements as $element) {
×
146
            if ($element === '' || str_contains($element, 'Oops, an error occurred!')) {
×
147
                continue;
×
148
            }
149

150
            if (str_contains($element, '<!--INT_SCRIPT') && !str_contains($element, HeadlessUserInt::STANDARD)) {
×
151
                $element = $this->headlessUserInt->wrap($element);
×
152
            }
153

154
            $element = json_decode($element, true);
×
155

156
            if ($element === []) {
×
157
                continue;
×
158
            }
159

NEW
160
            if ($groupingEnabled && ($element['colPos'] ?? 0) >= 0) {
×
161
                $data['colPos' . $element['colPos']][] = $element;
×
162
            } else {
163
                $data[] = $element;
×
164
            }
165
        }
166

NEW
167
        if ($groupingEnabled && $this->isSortByBackendLayoutEnabled($conf)) {
×
NEW
168
            $backendLayoutView = GeneralUtility::makeInstance(BackendLayoutView::class);
×
NEW
169
            $backendLayout = $backendLayoutView->getSelectedBackendLayout($this->request->getAttribute('routing')->getPageId());
×
170

NEW
171
            $sorted = [];
×
NEW
172
            foreach ($backendLayout['__colPosList'] ?? [] as $value) {
×
NEW
173
                $sorted['colPos' . $value] = $data['colPos' . $value];
×
174
            }
175

NEW
176
            $data = $sorted;
×
177
        }
178

UNCOV
179
        return $data;
×
180
    }
181

182
    /**
183
     * @param array<string, mixed> $conf
184
     * @return array<string, mixed>
185
     */
186
    private function prepareValue(array $conf): array
187
    {
188
        $frontendController = $this->getTypoScriptFrontendController();
×
189
        $theValue = [];
×
190
        $originalRec = $frontendController->currentRecord;
×
191
        // If the currentRecord is set, we register, that this record has invoked this function.
192
        // It should not be allowed to do this again then!!
193
        if ($originalRec) {
×
194
            if (isset($frontendController->recordRegister[$originalRec])) {
×
195
                ++$frontendController->recordRegister[$originalRec];
×
196
            } else {
197
                $frontendController->recordRegister[$originalRec] = 1;
×
198
            }
199
        }
200
        $conf['table'] = trim((string)$this->cObj->stdWrapValue('table', $conf ?? []));
×
201
        $conf['select.'] = !empty($conf['select.']) ? $conf['select.'] : [];
×
202
        $renderObjName = ($conf['renderObj'] ?? false) ? $conf['renderObj'] : '<' . $conf['table'];
×
203
        $renderObjKey = ($conf['renderObj'] ?? false) ? 'renderObj' : '';
×
204
        $renderObjConf = $conf['renderObj.'] ?? [];
×
205
        $slide = (int)$this->cObj->stdWrapValue('slide', $conf ?? []);
×
206
        if (!$slide) {
×
207
            $slide = 0;
×
208
        }
209
        $slideCollect = (int)$this->cObj->stdWrapValue('collect', $conf['slide.'] ?? []);
×
210
        if (!$slideCollect) {
×
211
            $slideCollect = 0;
×
212
        }
213
        $slideCollectReverse = (bool)$this->cObj->stdWrapValue('collectReverse', $conf['slide.'] ?? []);
×
214
        $slideCollectFuzzy = (bool)$this->cObj->stdWrapValue('collectFuzzy', $conf['slide.'] ?? []);
×
215
        if (!$slideCollect) {
×
216
            $slideCollectFuzzy = true;
×
217
        }
218
        $again = false;
×
219
        $tmpValue = '';
×
220

221
        do {
222
            $records = $this->cObj->getRecords($conf['table'], $conf['select.']);
×
223
            $cobjValue = [];
×
224
            if (!empty($records)) {
×
225
                $this->getTimeTracker()->setTSlogMessage('NUMROWS: ' . count($records));
×
226

227
                $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class, $frontendController);
×
228
                $cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
×
229
                $this->cObj->currentRecordNumber = 0;
×
230

231
                foreach ($records as $row) {
×
232
                    // Call hook for possible manipulation of database row for cObj->data
233
                    foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] ?? [] as $className) {
×
234
                        $_procObj = GeneralUtility::makeInstance($className);
×
235
                        $_procObj->modifyDBRow($row, $conf['table']);
×
236
                    }
237
                    $registerField = $conf['table'] . ':' . ($row['uid'] ?? 0);
×
238
                    if (!($frontendController->recordRegister[$registerField] ?? false)) {
×
239
                        $this->cObj->currentRecordNumber++;
×
240
                        $cObj->parentRecordNumber = $this->cObj->currentRecordNumber;
×
241
                        $frontendController->currentRecord = $registerField;
×
242
                        $this->cObj->lastChanged($row['tstamp'] ?? 0);
×
243
                        $cObj->setRequest($this->request);
×
244
                        $cObj->start($row, $conf['table']);
×
245
                        $tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey);
×
246
                        $cobjValue[] = $tmpValue;
×
247
                    }
248
                }
249
            }
250
            if ($slideCollectReverse) {
×
251
                $theValue = array_merge($cobjValue, $theValue);
×
252
            } else {
253
                $theValue = array_merge($theValue, $cobjValue);
×
254
            }
255
            if ($slideCollect > 0) {
×
256
                $slideCollect--;
×
257
            }
258
            if ($slide) {
×
259
                if ($slide > 0) {
×
260
                    $slide--;
×
261
                }
262
                $conf['select.']['pidInList'] = $this->cObj->getSlidePids(
×
263
                    $conf['select.']['pidInList'] ?? '',
×
264
                    $conf['select.']['pidInList.'] ?? [],
×
265
                );
×
266
                if (isset($conf['select.']['pidInList.'])) {
×
267
                    unset($conf['select.']['pidInList.']);
×
268
                }
269
                $again = (string)$conf['select.']['pidInList'] !== '';
×
270
            }
271
        } while ($again && $slide && ((string)$tmpValue === '' && $slideCollectFuzzy || $slideCollect));
×
272

273
        $theValue = $this->groupContentElementsByColPos($theValue, $conf);
×
274
        // Restore
275
        $frontendController->currentRecord = $originalRec;
×
276
        if ($originalRec) {
×
277
            --$frontendController->recordRegister[$originalRec];
×
278
        }
279

280
        return $theValue;
×
281
    }
282

283
    private function isSortByBackendLayoutEnabled(array $conf): bool
284
    {
NEW
285
        return isset($conf['sortByBackendLayout']) && (int)$conf['sortByBackendLayout'] === 1;
×
286
    }
287

288
    private function isColPolsGroupingEnabled(array $conf): bool
289
    {
290
        return !isset($conf['doNotGroupByColPos']) || (int)$conf['doNotGroupByColPos'] === 0;
×
291
    }
292
}
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

© 2025 Coveralls, Inc