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

FluidTYPO3 / flux / 17904759627

15 Sep 2025 08:47AM UTC coverage: 90.676% (-2.1%) from 92.767%
17904759627

push

github

NamelessCoder
[TASK] Set beta stability

6924 of 7636 relevant lines covered (90.68%)

9.49 hits per line

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

93.06
/Classes/Integration/HookSubscribers/ContentIcon.php
1
<?php
2
namespace FluidTYPO3\Flux\Integration\HookSubscribers;
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\Hooks\HookHandler;
12
use FluidTYPO3\Flux\Provider\Interfaces\GridProviderInterface;
13
use FluidTYPO3\Flux\Provider\ProviderResolver;
14
use FluidTYPO3\Flux\Proxy\IconFactoryProxy;
15
use TYPO3\CMS\Backend\Utility\BackendUtility;
16
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
17
use TYPO3\CMS\Backend\View\PageLayoutView;
18
use TYPO3\CMS\Core\Cache\CacheManager;
19
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
20
use TYPO3\CMS\Core\Imaging\Icon;
21
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
22
use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
23

24
/**
25
 * @deprecated Has no substitute functionality in TYPO3v12. To be removed when TYPO3v12 is minimum requirement.
26
 */
27
class ContentIcon
28
{
29
    protected array $templates = [
30
        'gridToggle' => '<div class="fluidcontent-toggler col-auto">
31
                            <div class="btn-group btn-group-sm" role="group">
32
                            <a class="btn btn-default %s" title="%s" data-toggler-uid="%s">%s</a> 
33
                        </div></div>',
34
        'legacyGridToggle' => '</div><div class="fluidcontent-toggler">
35
                            <div class="btn-group btn-group-sm" role="group">
36
                            <a class="btn btn-default %s" title="%s" data-toggler-uid="%s">%s</a>
37
                        </div></div><div>',
38
    ];
39

40
    protected ProviderResolver $providerResolver;
41
    protected IconFactoryProxy $iconFactory;
42
    protected FrontendInterface $cache;
43

44
    public function __construct(
45
        ProviderResolver $providerResolver,
46
        IconFactoryProxy $iconFactory,
47
        CacheManager $cacheManager
48
    ) {
49
        $this->providerResolver = $providerResolver;
10✔
50
        $this->iconFactory = $iconFactory;
10✔
51
        $this->cache = $cacheManager->getCache('flux');
10✔
52
    }
53

54
    /**
55
     * @param PageLayoutView|GridColumnItem|DatabaseRecordList $caller
56
     */
57
    public function addSubIcon(array $parameters, $caller = null): string
58
    {
59
        if (!($caller instanceof PageLayoutView || $caller instanceof GridColumnItem)) {
8✔
60
            return '';
1✔
61
        }
62
        [$table, $uid, $record] = $parameters;
7✔
63
        if ($table !== 'tt_content') {
7✔
64
            return '';
1✔
65
        }
66

67
        $provider = null;
6✔
68
        $iconMarkup = '';
6✔
69
        $record = null === $record && 0 < $uid ? BackendUtility::getRecord($table, $uid) : $record;
6✔
70
        $cacheIdentity = $table
6✔
71
            . $uid
6✔
72
            . sha1(serialize($record))
6✔
73
            . ($this->isRowCollapsed($record) ? 'collapsed' : 'expanded');
6✔
74
        // filter 1: icon must not already be cached and both record and caller must be provided.
75
        // we check the cache here because at this point, the cache key is decidedly
76
        // unique and we have not yet consulted the (potentially costly) Provider.
77
        /** @var string|false|null $cachedIconMarkup */
78
        $cachedIconMarkup = $this->cache->get($cacheIdentity);
6✔
79
        if ($cachedIconMarkup) {
6✔
80
            // both empty string and non-empty value means icon was generated and cached, we return
81
            // the result directly in both such cases, to prevent attempts to re-resolve provider etc.
82
            /** @var string $cachedIconMarkup */
83
            return $cachedIconMarkup;
1✔
84
        } elseif ($cachedIconMarkup !== '' && $record) {
5✔
85
            $field = $this->detectFirstFlexTypeFieldInTableFromPossibilities($table, array_keys($record));
3✔
86
            // filter 2: table must have one field defined as "flex" and record must include it.
87
            if ($field && array_key_exists($field, $record)) {
3✔
88
                /** @var GridProviderInterface $provider */
89
                $provider = $this->providerResolver->resolvePrimaryConfigurationProvider(
3✔
90
                    $table,
3✔
91
                    $field,
3✔
92
                    $record,
3✔
93
                    null,
3✔
94
                    [GridProviderInterface::class]
3✔
95
                );
3✔
96
                // filter 3: a Provider must be resolved for the record.
97
                if ($provider && $provider->getGrid($record)->hasChildren()) {
3✔
98
                    $iconMarkup = $this->drawGridToggle($record);
×
99
                }
100
            }
101
        }
102

103
        $this->cache->set($cacheIdentity, $iconMarkup);
5✔
104
        return $iconMarkup;
5✔
105
    }
106

107
    protected function drawGridToggle(array $row): string
108
    {
109
        $collapseIcon = $this->iconFactory->getIcon('actions-view-list-collapse', Icon::SIZE_SMALL)->render();
1✔
110
        $expandIcon = $this->iconFactory->getIcon('actions-view-list-expand', Icon::SIZE_SMALL)->render();
1✔
111
        $label = $this->translate('LLL:EXT:flux/Resources/Private/Language/locallang.xlf:toggle_content');
1✔
112
        $icon = $collapseIcon . $expandIcon;
1✔
113

114
        $template = version_compare(VersionNumberUtility::getCurrentTypo3Version(), '11', '<')
1✔
115
            ? $this->templates['legacyGridToggle']
×
116
            : $this->templates['gridToggle'];
1✔
117

118
        $rendered = sprintf(
1✔
119
            $template,
1✔
120
            $this->isRowCollapsed($row) ?  'toggler-expand' : 'toggler-collapse',
1✔
121
            $label,
1✔
122
            $row['uid'],
1✔
123
            $icon
1✔
124
        );
1✔
125

126
        return HookHandler::trigger(
1✔
127
            HookHandler::PREVIEW_GRID_TOGGLE_RENDERED,
1✔
128
            [
1✔
129
                'rendered' => $rendered,
1✔
130
                'iconCollapse' => $collapseIcon,
1✔
131
                'iconExpand' => $expandIcon,
1✔
132
                'label' => $label
1✔
133
            ]
1✔
134
        )['rendered'];
1✔
135
    }
136

137
    protected function isRowCollapsed(array $row): string
138
    {
139
        $collapsed = false;
7✔
140
        $cookie = $this->getCookie();
7✔
141
        if (null !== $cookie) {
7✔
142
            $cookie = json_decode(urldecode($cookie));
×
143
            $collapsed = in_array($row['uid'], (array) $cookie);
×
144
        }
145
        return HookHandler::trigger(
7✔
146
            HookHandler::PREVIEW_GRID_TOGGLE_STATUS_FETCHED,
7✔
147
            [
7✔
148
                'collapsed' => $collapsed,
7✔
149
                'record' => $row,
7✔
150
                'cookie' => $cookie
7✔
151
            ]
7✔
152
        )['collapsed'];
7✔
153
    }
154

155
    protected function detectFirstFlexTypeFieldInTableFromPossibilities(string $table, array $fields): ?string
156
    {
157
        foreach ($fields as $fieldName) {
3✔
158
            if (($GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['type'] ?? null) === 'flex') {
3✔
159
                return $fieldName;
3✔
160
            }
161
        }
162
        return null;
×
163
    }
164

165
    /**
166
     * @codeCoverageIgnore
167
     */
168
    protected function getCookie(): ?string
169
    {
170
        return true === isset($_COOKIE['fluxCollapseStates']) ? $_COOKIE['fluxCollapseStates'] : null;
171
    }
172

173
    /**
174
     * @codeCoverageIgnore
175
     */
176
    protected function translate(string $label): ?string
177
    {
178
        return $GLOBALS['LANG']->sL($label);
179
    }
180
}
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