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

FluidTYPO3 / vhs / 30001290505

23 Jul 2026 10:57AM UTC coverage: 70.309% (-1.7%) from 72.022%
30001290505

push

github

NamelessCoder
[TER] 8.0.0

4819 of 6854 relevant lines covered (70.31%)

6.54 hits per line

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

55.68
/Classes/ViewHelpers/Iterator/SortViewHelper.php
1
<?php
2
namespace FluidTYPO3\Vhs\ViewHelpers\Iterator;
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\Core\ViewHelper\AbstractViewHelper;
12
use FluidTYPO3\Vhs\Traits\ArrayConsumingViewHelperTrait;
13
use FluidTYPO3\Vhs\Traits\TemplateVariableViewHelperTrait;
14
use FluidTYPO3\Vhs\Utility\ErrorUtility;
15
use TYPO3\CMS\Core\Utility\GeneralUtility;
16
use TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage;
17
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
18
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
19
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
20
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
21

22
/**
23
 * Sorts an instance of ObjectStorage, an Iterator implementation,
24
 * an Array or a QueryResult (including Lazy counterparts).
25
 *
26
 * Can be used inline, i.e.:
27
 *
28
 * ```
29
 * <f:for each="{dataset -> v:iterator.sort(sortBy: 'name')}" as="item">
30
 *     // iterating data which is ONLY sorted while rendering this particular loop
31
 * </f:for>
32
 * ```
33
 */
34
class SortViewHelper extends AbstractViewHelper
35
{
36
    use TemplateVariableViewHelperTrait;
37
    use ArrayConsumingViewHelperTrait;
38

39
    /**
40
     * @var boolean
41
     */
42
    protected $escapeChildren = false;
43

44
    /**
45
     * @var boolean
46
     */
47
    protected $escapeOutput = false;
48

49
    /**
50
     * Contains all flags that are allowed to be used
51
     * with the sorting functions
52
     *
53
     * @var array
54
     */
55
    protected static $allowedSortFlags = [
56
        'SORT_REGULAR',
57
        'SORT_STRING',
58
        'SORT_NUMERIC',
59
        'SORT_NATURAL',
60
        'SORT_LOCALE_STRING',
61
        'SORT_FLAG_CASE'
62
    ];
63

64
    public function initializeArguments(): void
65
    {
66
        $this->registerArgument('subject', 'mixed', 'The array/Traversable instance to sort');
3✔
67
        $this->registerArgument(
3✔
68
            'sortBy',
3✔
69
            'string',
3✔
70
            'Which property/field to sort by - leave out for numeric sorting based on indexes(keys)'
3✔
71
        );
3✔
72
        $this->registerArgument(
3✔
73
            'order',
3✔
74
            'string',
3✔
75
            'ASC, DESC, RAND or SHUFFLE. RAND preserves keys, SHUFFLE does not - but SHUFFLE is faster',
3✔
76
            false,
3✔
77
            'ASC'
3✔
78
        );
3✔
79
        $this->registerArgument(
3✔
80
            'sortFlags',
3✔
81
            'string',
3✔
82
            'Constant name from PHP for `SORT_FLAGS`: `SORT_REGULAR`, `SORT_STRING`, `SORT_NUMERIC`, ' .
3✔
83
            '`SORT_NATURAL`, `SORT_LOCALE_STRING` or `SORT_FLAG_CASE`. You can provide a comma seperated list or ' .
3✔
84
            'array to use a combination of flags.',
3✔
85
            false,
3✔
86
            'SORT_REGULAR'
3✔
87
        );
3✔
88
        $this->registerAsArgument();
3✔
89
    }
90

91
    /**
92
     * "Render" method - sorts a target list-type target. Either $array or
93
     * $objectStorage must be specified. If both are, ObjectStorage takes precedence.
94
     *
95
     * Returns the same type as $subject. Ignores NULL values which would be
96
     * OK to use in an f:for (empty loop as result)
97
     *
98
     * @return mixed
99
     */
100
    public static function renderStatic(
101
        array $arguments,
102
        \Closure $renderChildrenClosure,
103
        RenderingContextInterface $renderingContext
104
    ) {
105
        /** @var string|null $as */
106
        $as = $arguments['as'] ?? null;
3✔
107
        $candidate = $arguments['subject'] ?? $renderChildrenClosure();
3✔
108
        if ($candidate instanceof ObjectStorage) {
3✔
109
            $sorted = static::sortObjectStorage($candidate, $arguments);
×
110
        } elseif (!is_iterable($candidate)) {
3✔
111
            throw new Exception(
×
112
                'v:iterator.sort requires an "iterable" object or array, as "subject" argument or as child node.',
×
113
                1690469444
×
114
            );
×
115
        } else {
116
            $subject = static::arrayFromArrayOrTraversableOrCSVStatic($candidate);
3✔
117
            $sorted = static::sortArray($subject, $arguments);
3✔
118
        }
119

120
        return static::renderChildrenWithVariableOrReturnInputStatic(
3✔
121
            $sorted,
3✔
122
            $as,
3✔
123
            $renderingContext,
3✔
124
            $renderChildrenClosure
3✔
125
        );
3✔
126
    }
127

128
    /**
129
     * Sort an array
130
     *
131
     * @param array|\Iterator $array
132
     * @param array $arguments
133
     * @return array
134
     */
135
    protected static function sortArray($array, $arguments)
136
    {
137
        $sorted = [];
3✔
138
        foreach ($array as $index => $object) {
3✔
139
            if (isset($arguments['sortBy'])) {
3✔
140
                $index = static::getSortValue($object, $arguments);
×
141
            }
142
            while (isset($sorted[$index])) {
3✔
143
                $index .= '.1';
×
144
            }
145
            $sorted[$index] = $object;
3✔
146
        }
147
        if ('ASC' === $arguments['order']) {
3✔
148
            ksort($sorted, static::getSortFlags($arguments));
3✔
149
        } elseif ('RAND' === $arguments['order']) {
×
150
            $sortedKeys = array_keys($sorted);
×
151
            shuffle($sortedKeys);
×
152
            $backup = $sorted;
×
153
            $sorted = [];
×
154
            foreach ($sortedKeys as $sortedKey) {
×
155
                $sorted[$sortedKey] = $backup[$sortedKey];
×
156
            }
157
        } elseif ('SHUFFLE' === $arguments['order']) {
×
158
            shuffle($sorted);
×
159
        } else {
160
            krsort($sorted, static::getSortFlags($arguments));
×
161
        }
162
        return $sorted;
3✔
163
    }
164

165
    /**
166
     * Sort an ObjectStorage instance
167
     *
168
     * @param ObjectStorage<object> $storage
169
     * @param array $arguments
170
     * @return ObjectStorage
171
     */
172
    protected static function sortObjectStorage($storage, $arguments)
173
    {
174
        /** @var ObjectStorage $temp */
175
        $temp = GeneralUtility::makeInstance(ObjectStorage::class);
×
176
        foreach ($storage as $item) {
×
177
            $temp->attach($item);
×
178
        }
179
        $sorted = static::sortArray($storage, $arguments);
×
180
        /** @var ObjectStorage $storage */
181
        $storage = GeneralUtility::makeInstance(ObjectStorage::class);
×
182
        foreach ($sorted as $item) {
×
183
            $storage->attach($item);
×
184
        }
185
        return $storage;
×
186
    }
187

188
    /**
189
     * Gets the value to use as sorting value from $object
190
     *
191
     * @param array|object $object
192
     * @param array $arguments
193
     * @return mixed
194
     */
195
    protected static function getSortValue($object, $arguments)
196
    {
197
        $field = $arguments['sortBy'];
×
198
        $value = ObjectAccess::getPropertyPath($object, $field);
×
199
        if ($value instanceof \DateTimeInterface) {
×
200
            $value = (int) $value->format('U');
×
201
        } elseif ($value instanceof ObjectStorage || $value instanceof LazyObjectStorage) {
×
202
            $value = $value->count();
×
203
        } elseif (is_array($value)) {
×
204
            $value = count($value);
×
205
        }
206
        return $value;
×
207
    }
208

209
    /**
210
     * Parses the supplied flags into the proper value for the sorting
211
     * function.
212
     *
213
     * @param array $arguments
214
     * @return int
215
     * @throws Exception
216
     */
217
    protected static function getSortFlags($arguments)
218
    {
219
        $constants = static::arrayFromArrayOrTraversableOrCSVStatic($arguments['sortFlags']);
3✔
220
        $flags = 0;
3✔
221
        foreach ($constants as $constant) {
3✔
222
            if (!in_array($constant, static::$allowedSortFlags)) {
3✔
223
                ErrorUtility::throwViewHelperException(
×
224
                    'The constant "' . $constant . '" you\'re trying to use as a sortFlag is not allowed. Allowed ' .
×
225
                    'constants are: ' . implode(', ', static::$allowedSortFlags) . '.',
×
226
                    1404220538
×
227
                );
×
228
            }
229
            $flags = $flags | constant(trim($constant));
3✔
230
        }
231
        return $flags;
3✔
232
    }
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc