1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Backend\Form\NodeExpansion;
19
20use TYPO3\CMS\Backend\Form\AbstractNode;
21use TYPO3\CMS\Core\Imaging\Icon;
22use TYPO3\CMS\Core\Imaging\IconFactory;
23use TYPO3\CMS\Core\Localization\LanguageService;
24use TYPO3\CMS\Core\Service\DependencyOrderingService;
25use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27/**
28 * Field controls are additional HTML on a single element level that are typically
29 * shown right aside the main element HTML.
30 *
31 * They are restricted to only allow an icon as output.
32 *
33 * The "link popup" button next to the input field of a renderType "inputLink"
34 * is an example of such an additional control.
35 *
36 * The element itself must position any field controls at an appropriate place.
37 * For instance the "group" element shows them in a row vertically, while others
38 * display single controls next to each other.
39 */
40class FieldControl extends AbstractNode
41{
42    /**
43     * Order the list of field wizards to be rendered with the ordering service,
44     * then call each wizard element through the node factory and merge their
45     * results.
46     *
47     * @return array Result array
48     */
49    public function render(): array
50    {
51        $result = $this->initializeResultArray();
52        if (!isset($this->data['renderData']['fieldControl'])) {
53            return $result;
54        }
55
56        $languageService = $this->getLanguageService();
57
58        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
59        $fieldControl = $this->data['renderData']['fieldControl'];
60        $orderingService = GeneralUtility::makeInstance(DependencyOrderingService::class);
61        $orderedFieldControl = $orderingService->orderByDependencies($fieldControl, 'before', 'after');
62
63        foreach ($orderedFieldControl as $anOrderedFieldControl => $orderedFieldControlConfiguration) {
64            if (isset($orderedFieldControlConfiguration['disabled']) && $orderedFieldControlConfiguration['disabled']
65                || !isset($fieldControl[$anOrderedFieldControl]['renderType'])
66            ) {
67                // Don't consider this control if disabled.
68                // Also ignore if renderType is not given.
69                // Missing renderType may happen if an element registers a default field control
70                // as disabled, and TCA enabled that. If then additionally for instance the
71                // element renderType is changed to an element that doesn't register the control
72                // by default anymore, this would then fatal if we don't continue here.
73                // @todo: the above scenario indicates a small configuration flaw, maybe log an error somewhere?
74                continue;
75            }
76
77            $options = $this->data;
78            $options['renderType'] = $fieldControl[$anOrderedFieldControl]['renderType'];
79            $options['renderData']['fieldControlOptions'] = $orderedFieldControlConfiguration['options'] ?? [];
80            $controlResult = $this->nodeFactory->create($options)->render();
81
82            if (!is_array($controlResult)) {
83                throw new \RuntimeException(
84                    'Field controls must return an array',
85                    1484838560
86                );
87            }
88
89            // If the controlResult is empty (this control rendered nothing), continue to next one
90            if (empty($controlResult)) {
91                continue;
92            }
93
94            if (empty($controlResult['iconIdentifier'])) {
95                throw new \RuntimeException(
96                    'Field controls must return an iconIdentifier',
97                    1483890332
98                );
99            }
100            if (empty($controlResult['title'])) {
101                throw new \RuntimeException(
102                    'Field controls must return a title',
103                    1483890482
104                );
105            }
106            if (empty($controlResult['linkAttributes'])) {
107                throw new \RuntimeException(
108                    'Field controls must return link attributes',
109                    1483891272
110                );
111            }
112
113            $icon = $controlResult['iconIdentifier'];
114            $title = $languageService->sL($controlResult['title']);
115            $linkAttributes = $controlResult['linkAttributes'];
116            if (!isset($linkAttributes['class'])) {
117                $linkAttributes['class'] = 'btn btn-default';
118            } else {
119                $linkAttributes['class'] .= ' btn btn-default';
120            }
121            if (!isset($linkAttributes['href'])) {
122                $linkAttributes['href'] = '#';
123            }
124
125            unset($controlResult['iconIdentifier']);
126            unset($controlResult['title']);
127            unset($controlResult['linkAttributes']);
128
129            $html = [];
130            $html[] = '<a ' . GeneralUtility::implodeAttributes($linkAttributes, true) . '>';
131            $html[] =   '<span title="' . htmlspecialchars($title) . '">';
132            $html[] =       $iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render();
133            $html[] =   '</span>';
134            $html[] = '</a>';
135
136            $finalControlResult = $this->initializeResultArray();
137            $finalControlResult = array_merge($finalControlResult, $controlResult);
138            $finalControlResult['html'] = implode(LF, $html);
139
140            $result = $this->mergeChildReturnIntoExistingResult($result, $finalControlResult);
141        }
142        return $result;
143    }
144
145    /**
146     * @return LanguageService
147     */
148    protected function getLanguageService()
149    {
150        return $GLOBALS['LANG'];
151    }
152}
153