Passed
Push — master ( 060f73...1fe6e3 )
by
unknown
12:17
created

ActionController::sendZipFileToBrowserAndDelete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
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\Core\Core\Environment;
20
use TYPO3\CMS\Core\Http\Stream;
21
use TYPO3\CMS\Core\Messaging\FlashMessage;
22
use TYPO3\CMS\Core\Package\Exception;
23
use TYPO3\CMS\Core\Package\Exception\PackageStatesFileNotWritableException;
24
use TYPO3\CMS\Core\Registry;
25
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Core\Utility\PathUtility;
28
use TYPO3\CMS\Extbase\Http\ForwardResponse;
29
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
30
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
31
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
32
use TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService;
33
use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
34
35
/**
36
 * Controller for handling extension related actions like
37
 * installing, removing, downloading of data or files
38
 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
39
 */
40
class ActionController extends AbstractController
41
{
42
    /**
43
     * @var InstallUtility
44
     */
45
    protected $installUtility;
46
47
    /**
48
     * @var ExtensionManagementService
49
     */
50
    protected $managementService;
51
52
    /**
53
     * @param InstallUtility $installUtility
54
     */
55
    public function injectInstallUtility(InstallUtility $installUtility)
56
    {
57
        $this->installUtility = $installUtility;
58
    }
59
60
    /**
61
     * @param ExtensionManagementService $managementService
62
     */
63
    public function injectManagementService(ExtensionManagementService $managementService)
64
    {
65
        $this->managementService = $managementService;
66
    }
67
68
    /**
69
     * Toggle extension installation state action
70
     *
71
     * @param string $extensionKey
72
     */
73
    protected function toggleExtensionInstallationStateAction($extensionKey)
74
    {
75
        $installedExtensions = ExtensionManagementUtility::getLoadedExtensionListArray();
76
        try {
77
            if (in_array($extensionKey, $installedExtensions)) {
78
                // uninstall
79
                $this->installUtility->uninstall($extensionKey);
80
            } else {
81
                // install
82
                $extension = Extension::createFromExtensionArray(
83
                    $this->installUtility->enrichExtensionWithDetails($extensionKey, false)
84
                );
85
                if ($this->managementService->installExtension($extension) === false) {
86
                    $this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionKey]);
87
                }
88
            }
89
        } catch (ExtensionManagerException|PackageStatesFileNotWritableException $e) {
90
            $this->addFlashMessage($e->getMessage(), '', FlashMessage::ERROR);
91
        }
92
        $this->redirect('index', 'List', null, [
93
            self::TRIGGER_RefreshModuleMenu => true,
94
            self::TRIGGER_RefreshTopbar => true
95
        ]);
96
    }
97
98
    /**
99
     * Install an extension and omit dependency checking
100
     *
101
     * @param string $extensionKey
102
     */
103
    public function installExtensionWithoutSystemDependencyCheckAction($extensionKey): ResponseInterface
104
    {
105
        $this->managementService->setSkipDependencyCheck(true);
106
        return (new ForwardResponse('toggleExtensionInstallationState'))->withArguments(['extensionKey' => $extensionKey]);
107
    }
108
109
    /**
110
     * Remove an extension (if it is still installed, uninstall it first)
111
     *
112
     * @param string $extension
113
     * @return ResponseInterface
114
     */
115
    protected function removeExtensionAction($extension): ResponseInterface
116
    {
117
        try {
118
            if (Environment::isComposerMode()) {
119
                throw new ExtensionManagerException(
120
                    'The system is set to composer mode. You are not allowed to remove any extension.',
121
                    1590314046
122
                );
123
            }
124
125
            $this->installUtility->removeExtension($extension);
126
            $this->addFlashMessage(
127
                LocalizationUtility::translate(
128
                    'extensionList.remove.message',
129
                    'extensionmanager',
130
                    [
131
                        'extension' => $extension,
132
                    ]
133
                ) ?? ''
134
            );
135
        } catch (ExtensionManagerException|Exception $e) {
136
            $this->addFlashMessage($e->getMessage(), '', FlashMessage::ERROR);
137
        }
138
139
        return $this->htmlResponse('');
140
    }
141
142
    /**
143
     * Download an extension as a zip file
144
     *
145
     * @param string $extension
146
     * @return ResponseInterface
147
     */
148
    protected function downloadExtensionZipAction($extension): ResponseInterface
149
    {
150
        $fileName = $this->createZipFileFromExtension($extension);
151
        $body = new Stream('php://temp', 'rw');
152
        $body->write(file_get_contents($fileName));
153
154
        $response = $this->responseFactory
155
            ->createResponse()
156
            ->withAddedHeader('Content-Type', 'application/zip')
157
            ->withAddedHeader('Content-Length', (string)(filesize($fileName) ?: ''))
158
            ->withAddedHeader('Content-Disposition', 'attachment; filename="' . PathUtility::basename($fileName) . '"')
159
            ->withBody($body);
160
161
        unlink($fileName);
162
163
        return $response;
164
    }
165
166
    /**
167
     * Reloads the static SQL data of an extension
168
     *
169
     * @param string $extension
170
     */
171
    protected function reloadExtensionDataAction($extension)
172
    {
173
        $extension = $this->installUtility->enrichExtensionWithDetails($extension, false);
174
        $registryKey = PathUtility::stripPathSitePrefix($extension['packagePath']) . 'ext_tables_static+adt.sql';
175
176
        $registry = GeneralUtility::makeInstance(Registry::class);
177
        $registry->remove('extensionDataImport', $registryKey);
178
179
        $this->installUtility->processExtensionSetup($extension['key']);
180
181
        $this->redirect('index', 'List');
182
    }
183
184
    /**
185
     * Create a zip file from an extension
186
     *
187
     * @param string $extensionKey
188
     * @return string Name and path of create zip file
189
     */
190
    protected function createZipFileFromExtension(string $extensionKey): string
191
    {
192
        $extensionDetails = $this->installUtility->enrichExtensionWithDetails($extensionKey);
193
        $extensionPath = $extensionDetails['packagePath'];
194
195
        // Add trailing slash to the extension path, getAllFilesAndFoldersInPath explicitly requires that.
196
        $extensionPath = PathUtility::sanitizeTrailingSeparator($extensionPath);
197
198
        $version = (string)$extensionDetails['version'];
199
        if (empty($version)) {
200
            $version = '0.0.0';
201
        }
202
203
        $temporaryPath = Environment::getVarPath() . '/transient/';
204
        if (!@is_dir($temporaryPath)) {
205
            GeneralUtility::mkdir($temporaryPath);
206
        }
207
        $fileName = $temporaryPath . $extensionKey . '_' . $version . '_' . date('YmdHi', $GLOBALS['EXEC_TIME']) . '.zip';
208
209
        $zip = new \ZipArchive();
210
        $zip->open($fileName, \ZipArchive::CREATE);
211
212
        $excludePattern = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging'];
213
214
        // Get all the files of the extension, but exclude the ones specified in the excludePattern
215
        $files = GeneralUtility::getAllFilesAndFoldersInPath(
216
            [], // No files pre-added
217
            $extensionPath, // Start from here
218
            '', // Do not filter files by extension
219
            true, // Include subdirectories
220
            PHP_INT_MAX, // Recursion level
221
            $excludePattern        // Files and directories to exclude.
222
        );
223
224
        // Make paths relative to extension root directory.
225
        $files = GeneralUtility::removePrefixPathFromList($files, $extensionPath);
226
        $files = is_array($files) ? $files : [];
227
228
        // Remove the one empty path that is the extension dir itself.
229
        $files = array_filter($files);
230
231
        foreach ($files as $file) {
232
            $fullPath = $extensionPath . $file;
233
            // Distinguish between files and directories, as creation of the archive
234
            // fails on Windows when trying to add a directory with "addFile".
235
            if (is_dir($fullPath)) {
236
                $zip->addEmptyDir($file);
237
            } else {
238
                $zip->addFile($fullPath, $file);
239
            }
240
        }
241
242
        $zip->close();
243
        return $fileName;
244
    }
245
}
246