Passed
Push — master ( 15944c...19c318 )
by
unknown
38:22 queued 21:58
created

enrichExtensionsWithViewInformation()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 9.6111
c 0
b 0
f 0
cc 5
nc 7
nop 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extensionmanager\Controller;
17
18
use Psr\Http\Message\ResponseInterface;
19
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
20
use TYPO3\CMS\Backend\View\BackendTemplateView;
21
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
23
use TYPO3\CMS\Core\Core\Environment;
24
use TYPO3\CMS\Core\Imaging\Icon;
25
use TYPO3\CMS\Core\Messaging\FlashMessage;
26
use TYPO3\CMS\Core\Page\PageRenderer;
27
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
28
use TYPO3\CMS\Core\Pagination\SimplePagination;
29
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
32
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
33
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
34
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
35
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
36
use TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository;
37
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
38
use TYPO3\CMS\Extensionmanager\Remote\RemoteRegistry;
39
use TYPO3\CMS\Extensionmanager\Utility\DependencyUtility;
40
use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
41
42
/**
43
 * Controller for extension listings (TER or local extensions)
44
 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
45
 */
46
class ListController extends AbstractModuleController
47
{
48
    /**
49
     * @var ExtensionRepository
50
     */
51
    protected $extensionRepository;
52
53
    /**
54
     * @var ListUtility
55
     */
56
    protected $listUtility;
57
58
    /**
59
     * @var PageRenderer
60
     */
61
    protected $pageRenderer;
62
63
    /**
64
     * @var DependencyUtility
65
     */
66
    protected $dependencyUtility;
67
68
    /**
69
     * @param ExtensionRepository $extensionRepository
70
     */
71
    public function injectExtensionRepository(ExtensionRepository $extensionRepository)
72
    {
73
        $this->extensionRepository = $extensionRepository;
74
    }
75
76
    /**
77
     * @param ListUtility $listUtility
78
     */
79
    public function injectListUtility(ListUtility $listUtility)
80
    {
81
        $this->listUtility = $listUtility;
82
    }
83
84
    /**
85
     * @param PageRenderer $pageRenderer
86
     */
87
    public function injectPageRenderer(PageRenderer $pageRenderer)
88
    {
89
        $this->pageRenderer = $pageRenderer;
90
    }
91
92
    /**
93
     * @param DependencyUtility $dependencyUtility
94
     */
95
    public function injectDependencyUtility(DependencyUtility $dependencyUtility)
96
    {
97
        $this->dependencyUtility = $dependencyUtility;
98
    }
99
100
    /**
101
     * Add the needed JavaScript files for all actions
102
     */
103
    public function initializeAction()
104
    {
105
        $this->pageRenderer->addInlineLanguageLabelFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
106
        $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
107
        if ($isAutomaticInstallationEnabled) {
108
            $this->settings['offlineMode'] = true;
109
        }
110
    }
111
112
    /**
113
     * Set up the doc header properly here
114
     *
115
     * @param ViewInterface $view
116
     */
117
    protected function initializeView(ViewInterface $view)
118
    {
119
        if ($view instanceof BackendTemplateView) {
120
            /** @var BackendTemplateView $view */
121
            parent::initializeView($view);
122
            $this->generateMenu();
123
            $this->registerDocHeaderButtons();
124
        }
125
    }
126
127
    /**
128
     * Adds an information about composer mode
129
     */
130
    protected function addComposerModeNotification()
131
    {
132
        if (Environment::isComposerMode()) {
133
            $this->addFlashMessage(
134
                LocalizationUtility::translate(
135
                    'composerMode.message',
136
                    'extensionmanager'
137
                ) ?? '',
138
                LocalizationUtility::translate(
139
                    'composerMode.title',
140
                    'extensionmanager'
141
                ) ?? '',
142
                FlashMessage::INFO
143
            );
144
        }
145
    }
146
147
    /**
148
     * Shows list of extensions present in the system
149
     */
150
    public function indexAction(): ResponseInterface
151
    {
152
        if ($this->request->hasArgument('filter') && is_string($this->request->getArgument('filter'))) {
153
            $filter = $this->request->getArgument('filter');
154
            $this->saveBackendUserFilter($filter);
155
        } else {
156
            $filter = $this->getBackendUserFilter();
157
        }
158
159
        $this->addComposerModeNotification();
160
        $isComposerMode = Environment::isComposerMode();
161
        $availableAndInstalledExtensions = $this->enrichExtensionsWithViewInformation(
162
            $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation($filter),
163
            $isComposerMode
164
        );
165
        ksort($availableAndInstalledExtensions);
166
        $this->view->assignMultiple(
167
            [
168
                'extensions' => $availableAndInstalledExtensions,
169
                'isComposerMode' => $isComposerMode,
170
                'typeFilter' => $filter ?: 'All',
171
                // Sort extension by update state. This is only automatically set for non-composer
172
                // mode and only takes effect if at least one extension can be updated.
173
                'sortByUpdate' => $this->extensionsWithUpdate($availableAndInstalledExtensions) !== [] && !$isComposerMode
174
            ]
175
        );
176
        $this->handleTriggerArguments();
177
178
        return $this->htmlResponse();
179
    }
180
181
    /**
182
     * Shows a list of unresolved dependency errors with the possibility to bypass the dependency check
183
     *
184
     * @param string $extensionKey
185
     * @return ResponseInterface
186
     */
187
    public function unresolvedDependenciesAction($extensionKey): ResponseInterface
188
    {
189
        $availableExtensions = $this->listUtility->getAvailableExtensions();
190
        if (isset($availableExtensions[$extensionKey])) {
191
            $extensionArray = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation(
192
                [
193
                    $extensionKey => $availableExtensions[$extensionKey]
194
                ]
195
            );
196
            $extension = Extension::createFromExtensionArray($extensionArray[$extensionKey]);
197
        } else {
198
            throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1402421007);
199
        }
200
        $this->dependencyUtility->checkDependencies($extension);
201
        $this->view->assign('extension', $extension);
202
        $this->view->assign('unresolvedDependencies', $this->dependencyUtility->getDependencyErrors());
203
204
        return $this->htmlResponse();
205
    }
206
207
    /**
208
     * Shows extensions from TER
209
     * Either all extensions or depending on a search param
210
     *
211
     * @param string $search
212
     * @param int $currentPage
213
     * @return ResponseInterface
214
     */
215
    public function terAction($search = '', int $currentPage = 1): ResponseInterface
216
    {
217
        $this->addComposerModeNotification();
218
        $search = trim($search);
219
        if (!empty($search)) {
220
            $extensions = $this->extensionRepository->findByTitleOrAuthorNameOrExtensionKey($search);
221
            $paginator = new ArrayPaginator($extensions, $currentPage);
222
            $tableId = 'terSearchTable';
223
        } else {
224
            /** @var QueryResultInterface $extensions */
225
            $extensions = $this->extensionRepository->findAll();
226
            $paginator = new QueryResultPaginator($extensions, $currentPage);
227
            $tableId = 'terTable';
228
        }
229
        $pagination = new SimplePagination($paginator);
230
        $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensions($this->listUtility->getAvailableExtensions());
231
        $this->view->assignMultiple([
232
            'extensions' => $extensions,
233
            'paginator' => $paginator,
234
            'pagination' => $pagination,
235
            'search' => $search,
236
            'availableAndInstalled' => $availableAndInstalledExtensions,
237
            'actionName' => 'ter',
238
            'tableId' => $tableId,
239
        ]);
240
241
        return $this->htmlResponse();
242
    }
243
244
    /**
245
     * Action for listing all possible distributions
246
     *
247
     * @param bool $showUnsuitableDistributions
248
     * @return ResponseInterface
249
     */
250
    public function distributionsAction($showUnsuitableDistributions = false): ResponseInterface
251
    {
252
        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Extensionmanager/DistributionImage');
253
        $this->addComposerModeNotification();
254
        $importExportInstalled = ExtensionManagementUtility::isLoaded('impexp');
255
        if ($importExportInstalled) {
256
            try {
257
                $remoteRegistry = GeneralUtility::makeInstance(RemoteRegistry::class);
258
                foreach ($remoteRegistry->getListableRemotes() as $remote) {
259
                    $remote->getAvailablePackages();
260
                }
261
            } catch (ExtensionManagerException $e) {
262
                $this->addFlashMessage($e->getMessage(), $e->getCode(), FlashMessage::ERROR);
263
            }
264
265
            $officialDistributions = $this->extensionRepository->findAllOfficialDistributions($showUnsuitableDistributions);
266
            $communityDistributions = $this->extensionRepository->findAllCommunityDistributions($showUnsuitableDistributions);
267
268
            $this->view->assign('officialDistributions', $officialDistributions);
269
            $this->view->assign('communityDistributions', $communityDistributions);
270
        }
271
        $this->view->assign('enableDistributionsView', $importExportInstalled);
272
        $this->view->assign('showUnsuitableDistributions', $showUnsuitableDistributions);
273
274
        return $this->htmlResponse();
275
    }
276
277
    /**
278
     * Shows all versions of a specific extension
279
     *
280
     * @param string $extensionKey
281
     * @return ResponseInterface
282
     */
283
    public function showAllVersionsAction($extensionKey): ResponseInterface
284
    {
285
        $currentVersion = $this->extensionRepository->findOneByCurrentVersionByExtensionKey($extensionKey);
286
        $extensions = $this->extensionRepository->findByExtensionKeyOrderedByVersion($extensionKey);
287
288
        $this->view->assignMultiple(
289
            [
290
                'extensionKey' => $extensionKey,
291
                'currentVersion' => $currentVersion,
292
                'extensions' => $extensions
293
            ]
294
        );
295
296
        return $this->htmlResponse();
297
    }
298
299
    /**
300
     * Registers the Icons into the docheader
301
     *
302
     * @throws \InvalidArgumentException
303
     */
304
    protected function registerDocHeaderButtons()
305
    {
306
        if (Environment::isComposerMode()) {
307
            return;
308
        }
309
310
        if (!in_array($this->actionMethodName, ['indexAction', 'terAction', 'showAllVersionsAction'], true)) {
311
            return;
312
        }
313
314
        /** @var ButtonBar $buttonBar */
315
        $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
316
317
        if ($this->actionMethodName === 'showAllVersionsAction') {
318
            $uri = $this->uriBuilder->reset()->uriFor('ter', [], 'List');
319
            $title = $this->translate('extConfTemplate.backToList');
320
            $icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
321
            $classes = '';
322
        } else {
323
            $uri = $this->uriBuilder->reset()->uriFor('form', [], 'UploadExtensionFile');
324
            $title = $this->translate('extensionList.uploadExtension');
325
            $icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-upload', Icon::SIZE_SMALL);
326
            $classes = 't3js-upload';
327
        }
328
        $button = $buttonBar->makeLinkButton()
329
            ->setHref($uri)
330
            ->setTitle($title)
331
            ->setClasses($classes)
332
            ->setIcon($icon);
333
        $buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
334
    }
335
336
    protected function getBackendUserFilter(): string
337
    {
338
        return (string)($this->getBackendUserAuthentication()->getModuleData('ExtensionManager')['filter'] ?? '');
339
    }
340
341
    protected function saveBackendUserFilter(string $filter): void
342
    {
343
        $this->getBackendUserAuthentication()->pushModuleData('ExtensionManager', ['filter' => $filter]);
344
    }
345
346
    protected function enrichExtensionsWithViewInformation(array $availableAndInstalledExtensions, bool $isComposerMode): array
347
    {
348
        $isOfflineMode = (bool)($this->settings['offlineMode'] ?? false);
349
350
        foreach ($availableAndInstalledExtensions as &$extension) {
351
            $extension['updateIsBlocked'] = $isComposerMode || $isOfflineMode || ($extension['state'] ?? '') === 'excludeFromUpdates';
352
            $extension['sortUpdate'] = 2;
353
            if ($extension['updateAvailable'] ?? false) {
354
                $extension['sortUpdate'] = (int)$extension['updateIsBlocked'];
355
            }
356
        }
357
358
        return $availableAndInstalledExtensions;
359
    }
360
361
    protected function extensionsWithUpdate(array $availableAndInstalledExtensions): array
362
    {
363
        return array_filter($availableAndInstalledExtensions, static function ($extension) {
364
            return $extension['updateAvailable'] ?? false;
365
        });
366
    }
367
368
    protected function getBackendUserAuthentication(): BackendUserAuthentication
369
    {
370
        return $GLOBALS['BE_USER'];
371
    }
372
}
373