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

FluidTYPO3 / flux / 27757675993

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

push

github

NamelessCoder
[TASK] Address last phpstan warnings

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

98.47
/Classes/Form/Field/ControllerActions.php
1
<?php
2
namespace FluidTYPO3\Flux\Form\Field;
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\Utility\ExtensionNamingUtility;
12
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
13
use TYPO3\CMS\Core\Utility\GeneralUtility;
14

15
class ControllerActions extends Select
16
{
17
    /**
18
     * Name of the Extbase extension that contains the Controller
19
     * to parse, ex. MyExtension. In vendor based extensions use
20
     * dot, ex. Vendor.MyExtension.
21
     */
22
    protected string $controllerExtensionName = '';
23

24
    /**
25
     * Name of the Extbase plugin that contains Controller
26
     * definitions to parse, ex. MyPluginName.
27
     */
28
    protected string $pluginName = '';
29

30
    /**
31
     * Optional extra limiting of actions displayed - if used,
32
     * field only displays actions for this controller name - ex
33
     * Article(Controller) or FrontendUser(Controller) - the
34
     * Controller part is implied.
35
     */
36
    protected string $controllerName = '';
37

38
    /**
39
     * Array of "ControllerName" => "csv,of,actions" which are
40
     * allowed. If used, does not require the use of an
41
     * ExtensionName and PluginName (will use the one specified
42
     * in your current plugin automatically).
43
     */
44
    protected array $actions = [];
45

46
    /**
47
     * Array of "ControllerName" => "csv,of,actions" which must
48
     * be excluded.
49
     */
50
    protected array $excludeActions = [];
51

52
    /**
53
     * A short string denoting that the method takes arguments,
54
     * ex * (which should then be explained in the documentation
55
     * for your extension about how to setup your plugins.
56
     */
57
    protected string $prefixOnRequiredArguments = '*';
58

59
    /**
60
     * Separator for non-LLL action labels which have no manual
61
     * label set.
62
     */
63
    protected string $separator = '->';
64

65
    /**
66
     * Array of also allowed actions which will be allowed when
67
     * a particular Controller+action is selected - but will not
68
     * be displayed as selectable options.
69
     *
70
     * Example: defining an array such as this one:
71
     *
72
     * [
73
     *      'News' => [
74
     *          'list' => 'update,delete
75
     *      ),
76
     *      'Category' => [
77
     *          'new' => 'create',
78
     *          'list' => 'filter,search'
79
     *      )
80
     * );
81
     *
82
     * Indicates that whenever the selector field is set to use
83
     * the "News->list" SwitchableControllerAction, Extbase will
84
     * in addition also allow the "News->update" and "News->delete"
85
     * sub-actions, but will not display them. And when the value
86
     * is "Category->new", Extbase will also allow "Category->create",
87
     * and finally when value is "Category->list" the additional
88
     * actions "Category->filter" and "Category->search" are allowed
89
     * but not displayed as select options.
90
     *
91
     * This behavior can be compared to small "sub-plugins"; regular
92
     * Extbase plugins will have a default action and additional
93
     * allowed "Controller->action" combinations - whereas this
94
     * method uses the selected value as a sort of "default" action
95
     * and uses these sub actions much the same way an Extbase plugin
96
     * would use the not-default actions configured in a plugin.
97
     *
98
     * Use of this feature is necessary if one or more of your
99
     * SwitchableControllerAction selection values must allow more
100
     * than one action to be used (which would be the case in for
101
     * example a new/create, edit/update pair of actions).
102
     */
103
    protected array $subActions = [];
104

105
    /**
106
     * Overridden getter: the name of a SwitchableControllerActions
107
     * field is enforced - the TYPO3 core depends on this name.
108
     */
109
    public function getName(): string
110
    {
111
        return 'switchableControllerActions';
24✔
112
    }
113

114
    /**
115
     * Overridden setter: ignores any attempt to set another name
116
     * for this field.
117
     */
118
    public function setName(string $name): self
119
    {
120
        // intentional intermediate; avoids "unused argument"
121
        $name = 'switchableControllerActions';
64✔
122
        $this->name = $name;
64✔
123
        return $this;
64✔
124
    }
125

126
    public function setActions(array $actions): self
127
    {
128
        $this->actions = $actions;
68✔
129
        return $this;
68✔
130
    }
131

132
    public function getActions(): array
133
    {
134
        return $this->actions;
32✔
135
    }
136

137
    public function setControllerName(string $controllerName): self
138
    {
139
        $this->controllerName = $controllerName;
84✔
140
        return $this;
84✔
141
    }
142

143
    public function getControllerName(): string
144
    {
145
        return $this->controllerName;
32✔
146
    }
147

148
    public function setExcludeActions(array $excludeActions): self
149
    {
150
        $this->excludeActions = $excludeActions;
56✔
151
        return $this;
56✔
152
    }
153

154
    public function getExcludeActions(): array
155
    {
156
        return $this->excludeActions;
32✔
157
    }
158

159
    public function setControllerExtensionName(string $extensionName): self
160
    {
161
        $this->controllerExtensionName = $extensionName;
108✔
162
        return $this;
108✔
163
    }
164

165
    public function getControllerExtensionName(): string
166
    {
167
        return $this->controllerExtensionName;
68✔
168
    }
169

170
    public function setPluginName(string $pluginName): self
171
    {
172
        $this->pluginName = $pluginName;
80✔
173
        return $this;
80✔
174
    }
175

176
    public function getPluginName(): string
177
    {
178
        return $this->pluginName;
60✔
179
    }
180

181
    public function setSeparator(string $separator): self
182
    {
183
        $this->separator = $separator;
8✔
184
        return $this;
8✔
185
    }
186

187
    public function getSeparator(): string
188
    {
189
        return $this->separator;
56✔
190
    }
191

192
    public function setPrefixOnRequiredArguments(string $prefixOnRequiredArguments): self
193
    {
194
        $this->prefixOnRequiredArguments = $prefixOnRequiredArguments;
52✔
195
        return $this;
52✔
196
    }
197

198
    public function getPrefixOnRequiredArguments(): string
199
    {
200
        return $this->prefixOnRequiredArguments;
16✔
201
    }
202

203
    public function setSubActions(array $subActions): self
204
    {
205
        $this->subActions = $subActions;
56✔
206
        return $this;
56✔
207
    }
208

209
    public function getSubActions(): array
210
    {
211
        return $this->subActions;
32✔
212
    }
213

214
    public function getItems(): array
215
    {
216
        $basicItems = parent::getItems();
12✔
217
        if (0 < count($basicItems)) {
12✔
218
            return $basicItems;
4✔
219
        } else {
220
            $actions = $this->getActions();
8✔
221
            if (0 === count($actions)) {
8✔
222
                $actions = $this->getActionsForExtensionNameAndPluginName();
8✔
223
            }
224
            return $this->buildItemsForActions($actions);
8✔
225
        }
226
    }
227

228
    protected function getActionsForExtensionNameAndPluginName(): array
229
    {
230
        $extensionName = $this->getControllerExtensionName();
16✔
231
        $extensionName = ExtensionNamingUtility::getExtensionName($extensionName);
16✔
232
        $pluginName = $this->getPluginName();
16✔
233
        $actions = (array) ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions']
16✔
234
            [$extensionName]['plugins'][$pluginName]['controllers'] ?? []);
16✔
235
        foreach ($actions as $controllerName => $definitions) {
16✔
236
            $actions[$controllerName] = $definitions['actions'];
4✔
237
        }
238
        return $actions;
16✔
239
    }
240

241
    protected function buildExpectedAndExistingControllerClassName(string $controllerName): ?string
242
    {
243
        $extensionName = $this->getControllerExtensionName();
48✔
244
        [$vendorName, $extensionName] = ExtensionNamingUtility::getVendorNameAndExtensionName($extensionName);
48✔
245
        if (class_exists($controllerName)) {
48✔
246
            $controllerClassName = $controllerName;
×
247
        } elseif (null !== $vendorName) {
48✔
248
            $controllerClassName = sprintf(
44✔
249
                '%s\\%s\\Controller\\%sController',
44✔
250
                $vendorName,
44✔
251
                $extensionName,
44✔
252
                $controllerName
44✔
253
            );
44✔
254
        } else {
255
            $controllerClassName = $extensionName . '\\Controller\\' . $controllerName . 'Controller';
4✔
256
            if (false === class_exists($controllerClassName)) {
4✔
257
                $controllerClassName = 'Tx_' . $extensionName . '_Controller_' . $controllerName . 'Controller';
4✔
258
            }
259
        }
260
        if (!class_exists($controllerClassName)) {
48✔
261
            $controllerClassName = null;
8✔
262
        }
263
        return $controllerClassName;
48✔
264
    }
265

266
    protected function getLabelForControllerAction(string $controllerName, string $actionName): string
267
    {
268
        $localLanguageFileRelativePath = $this->getLocalLanguageFileRelativePath();
40✔
269
        $extensionName = $this->getControllerExtensionName();
40✔
270
        $extensionKey = ExtensionNamingUtility::getExtensionKey($extensionName);
40✔
271
        $pluginName = $this->getPluginName();
40✔
272
        $separator = $this->getSeparator();
40✔
273
        $controllerClassName = $this->buildExpectedAndExistingControllerClassName($controllerName);
40✔
274
        if ($controllerClassName === null) {
40✔
275
            return 'INVALID: ' . $controllerName . '->' . $actionName;
×
276
        }
277
        $disableLocalLanguageLabels = $this->getDisableLocalLanguageLabels();
40✔
278
        $labelPath = strtolower($pluginName . '.' . $controllerName . '.' . $actionName);
40✔
279
        $hasLocalLanguageFile = file_exists(
40✔
280
            $this->resolvePathToFileInExtension($extensionKey, $localLanguageFileRelativePath)
40✔
281
        );
40✔
282
        $label = $actionName . $separator . $controllerName;
40✔
283
        if (false === $disableLocalLanguageLabels && true === $hasLocalLanguageFile) {
40✔
284
            $label = 'LLL:EXT:' . $extensionKey . $localLanguageFileRelativePath . ':' . $labelPath;
24✔
285
        } elseif (method_exists($controllerClassName, $actionName . 'Action') && true === $disableLocalLanguageLabels) {
16✔
286
            $methodReflection = $this->reflectAction($controllerName, $actionName);
8✔
287
            $parts = explode("\n", trim((string) $methodReflection->getDocComment(), "/*\n"));
8✔
288
            $line = array_shift($parts);
8✔
289
            $line = trim(trim($line), '* ');
8✔
290
            if (substr($line, 0, 1) !== '@') {
8✔
291
                $label = $line;
4✔
292
            }
293
        }
294
        return $label;
40✔
295
    }
296

297
    protected function reflectAction(string $controllerName, string $actionName): \ReflectionMethod
298
    {
299
        /** @var class-string $controllerClassName */
300
        $controllerClassName = $this->buildExpectedAndExistingControllerClassName($controllerName);
16✔
301
        $controllerClassReflection = new \ReflectionClass($controllerClassName);
16✔
302
        /** @var \ReflectionMethod $methodReflection */
303
        $methodReflection = $controllerClassReflection->getMethod($actionName . 'Action');
16✔
304
        return $methodReflection;
16✔
305
    }
306

307
    protected function prefixLabel(string $controllerName, string $actionName, string $label): string
308
    {
309
        $controllerClassName = $this->buildExpectedAndExistingControllerClassName($controllerName);
24✔
310
        if (null === $controllerClassName || false === method_exists($controllerClassName, $actionName . 'Action')) {
24✔
311
            return $label;
12✔
312
        }
313
        $methodReflection = $this->reflectAction($controllerName, $actionName);
12✔
314
        $hasRequiredArguments = (bool) ($methodReflection->getNumberOfRequiredParameters() > 0);
12✔
315
        $prefixOnRequiredArguments = $this->getPrefixOnRequiredArguments();
12✔
316
        $prefix = !empty($prefixOnRequiredArguments) && $hasRequiredArguments ? $prefixOnRequiredArguments : null;
12✔
317
        if (null !== $prefix) {
12✔
318
            $label = $prefix . ' ' . $label;
4✔
319
        }
320
        return $label;
12✔
321
    }
322

323
    /**
324
     * @param mixed $actionList
325
     */
326
    protected function convertActionListToArray($actionList): array
327
    {
328
        if (is_scalar($actionList)) {
24✔
329
            return GeneralUtility::trimExplode(',', (string) $actionList, true);
20✔
330
        }
331
        return (array) $actionList;
4✔
332
    }
333

334
    /**
335
     * Renders the TCA-style items array based on the Extbase FlexForm-style
336
     * definitions of selectable actions (specified manually or read based on
337
     * ViewHelper arguments).
338
     */
339
    protected function buildItemsForActions(array $actions): array
340
    {
341
        $separator = $this->getSeparator();
28✔
342
        $subActions = $this->getSubActions();
28✔
343
        $exclusions = $this->getExcludeActions();
28✔
344
        foreach ($exclusions as $controllerName => $controllerActionList) {
28✔
345
            $exclusions[$controllerName] = $this->convertActionListToArray($controllerActionList);
4✔
346
        }
347
        $items = [];
28✔
348
        $limitByControllerName = $this->getControllerName();
28✔
349
        foreach ($actions as $controllerName => $controllerActionList) {
28✔
350
            $controllerActions = $this->convertActionListToArray($controllerActionList);
20✔
351
            $controllerClassName = $this->buildExpectedAndExistingControllerClassName($controllerName);
20✔
352
            if (null === $controllerClassName) {
20✔
353
                continue;
4✔
354
            }
355
            foreach ($controllerActions as $actionName) {
20✔
356
                if (in_array($actionName, $exclusions[$controllerName] ?? [])) {
20✔
357
                    continue;
4✔
358
                } elseif ($limitByControllerName && $controllerName !== $limitByControllerName) {
20✔
359
                    continue;
4✔
360
                }
361
                $label = $this->getLabelForControllerAction($controllerName, $actionName);
20✔
362
                $label = $this->prefixLabel($controllerName, $actionName, $label);
20✔
363
                $actionKey = [$controllerName . $separator . $actionName];
20✔
364
                if (isset($subActions[$controllerName][$actionName])) {
20✔
365
                    $subActionsArray = $this->convertActionListToArray($subActions[$controllerName][$actionName]);
4✔
366
                    foreach ($subActionsArray as $allowedActionName) {
4✔
367
                        $actionKey[] = $controllerName . $separator . $allowedActionName;
4✔
368
                    }
369
                }
370
                $values = [
20✔
371
                    $label,
20✔
372
                    implode(';', $actionKey),
20✔
373
                ];
20✔
374
                array_push($items, $values);
20✔
375
            }
376
        }
377
        return $items;
28✔
378
    }
379

380
    /**
381
     * @codeCoverageIgnore
382
     */
383
    protected function resolvePathToFileInExtension(string $extensionKey, string $path): string
384
    {
385
        return ExtensionManagementUtility::extPath($extensionKey, $path);
386
    }
387
}
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