Passed
Push — master ( fcbfb0...64542d )
by
unknown
13:36
created

ListUtility::getUpdateableVersion()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 1
dl 0
loc 20
rs 9.9
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\Utility;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use TYPO3\CMS\Core\Core\Environment;
20
use TYPO3\CMS\Core\Package\Event\PackagesMayHaveChangedEvent;
21
use TYPO3\CMS\Core\Package\PackageInterface;
22
use TYPO3\CMS\Core\Package\PackageManager;
23
use TYPO3\CMS\Core\SingletonInterface;
24
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Core\Utility\PathUtility;
27
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
28
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
29
use TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository;
30
31
/**
32
 * Utility for dealing with extension list related functions
33
 *
34
 * @TODO: Refactor this API class:
35
 * - The methods depend on each other, they take each others result, that could be done internally
36
 * - There is no good wording to distinguish existing and loaded extensions
37
 * - The name 'listUtility' is not good, the methods could be moved to some 'extensionInformationUtility', or a repository?
38
 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
39
 */
40
class ListUtility implements SingletonInterface
41
{
42
    /**
43
     * @var EmConfUtility
44
     */
45
    protected $emConfUtility;
46
47
    /**
48
     * @var ExtensionRepository
49
     */
50
    protected $extensionRepository;
51
52
    /**
53
     * @var PackageManager
54
     */
55
    protected $packageManager;
56
57
    /**
58
     * @var array
59
     */
60
    protected $availableExtensions;
61
62
    /**
63
     * @var EventDispatcherInterface
64
     */
65
    protected $eventDispatcher;
66
67
    /**
68
     * @var DependencyUtility
69
     */
70
    protected $dependencyUtility;
71
72
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
73
    {
74
        $this->eventDispatcher = $eventDispatcher;
75
    }
76
77
    /**
78
     * @param EmConfUtility $emConfUtility
79
     */
80
    public function injectEmConfUtility(EmConfUtility $emConfUtility)
81
    {
82
        $this->emConfUtility = $emConfUtility;
83
    }
84
85
    /**
86
     * @param ExtensionRepository $extensionRepository
87
     */
88
    public function injectExtensionRepository(ExtensionRepository $extensionRepository)
89
    {
90
        $this->extensionRepository = $extensionRepository;
91
    }
92
93
    /**
94
     * @param PackageManager $packageManager
95
     */
96
    public function injectPackageManager(PackageManager $packageManager)
97
    {
98
        $this->packageManager = $packageManager;
99
    }
100
101
    /**
102
     * @param DependencyUtility $dependencyUtility
103
     */
104
    public function injectDependencyUtility(DependencyUtility $dependencyUtility)
105
    {
106
        $this->dependencyUtility = $dependencyUtility;
107
    }
108
109
    /**
110
     * Returns the list of available, but not necessarily loaded extensions
111
     *
112
     * @param string $filter
113
     * @return array[] All extensions with info
114
     */
115
    public function getAvailableExtensions(string $filter = ''): array
116
    {
117
        if ($this->availableExtensions === null) {
118
            $this->availableExtensions = [];
119
            $this->eventDispatcher->dispatch(new PackagesMayHaveChangedEvent());
120
            foreach ($this->packageManager->getAvailablePackages() as $package) {
121
                $installationType = $this->getInstallTypeForPackage($package);
122
                if ($filter === '' || $filter === $installationType) {
123
                    $this->availableExtensions[$package->getPackageKey()] = [
124
                        'siteRelPath' => str_replace(Environment::getPublicPath() . '/', '', $package->getPackagePath()),
125
                        'type' => $installationType,
126
                        'key' => $package->getPackageKey(),
127
                        'icon' => PathUtility::getAbsoluteWebPath($package->getPackagePath() . ExtensionManagementUtility::getExtensionIcon($package->getPackagePath())),
128
                    ];
129
                }
130
            }
131
        }
132
133
        return $this->availableExtensions;
134
    }
135
136
    /**
137
     * Reset and reload the available extensions
138
     */
139
    public function reloadAvailableExtensions()
140
    {
141
        $this->availableExtensions = null;
142
        $this->packageManager->scanAvailablePackages();
143
        $this->getAvailableExtensions();
144
    }
145
146
    /**
147
     * @param string $extensionKey
148
     * @return \TYPO3\CMS\Core\Package\PackageInterface
149
     * @throws \TYPO3\CMS\Core\Package\Exception\UnknownPackageException if the specified package is unknown
150
     */
151
    public function getExtension($extensionKey)
152
    {
153
        return $this->packageManager->getPackage($extensionKey);
154
    }
155
156
    /**
157
     * Returns "System", "Global" or "Local" based on extension position in filesystem.
158
     *
159
     * @param PackageInterface $package
160
     * @return string
161
     */
162
    protected function getInstallTypeForPackage(PackageInterface $package)
163
    {
164
        foreach (Extension::returnInstallPaths() as $installType => $installPath) {
165
            if (GeneralUtility::isFirstPartOfStr($package->getPackagePath(), $installPath)) {
166
                return $installType;
167
            }
168
        }
169
        return '';
170
    }
171
172
    /**
173
     * Enrich the output of getAvailableExtensions() with an array key installed = 1 if an extension is loaded.
174
     *
175
     * @param array $availableExtensions
176
     * @return array
177
     */
178
    public function getAvailableAndInstalledExtensions(array $availableExtensions)
179
    {
180
        foreach ($this->packageManager->getActivePackages() as $extKey => $_) {
181
            if (isset($availableExtensions[$extKey])) {
182
                $availableExtensions[$extKey]['installed'] = true;
183
            }
184
        }
185
        return $availableExtensions;
186
    }
187
188
    /**
189
     * Adds the information from the emconf array to the extension information
190
     *
191
     * @param array $extensions
192
     * @return array
193
     */
194
    public function enrichExtensionsWithEmConfInformation(array $extensions)
195
    {
196
        foreach ($extensions as $extensionKey => $properties) {
197
            $emconf = $this->emConfUtility->includeEmConf($extensionKey, $properties);
198
            if (is_array($emconf)) {
199
                $extensions[$extensionKey] = array_merge($emconf, $properties);
200
            } else {
201
                unset($extensions[$extensionKey]);
202
            }
203
        }
204
        return $extensions;
205
    }
206
207
    /**
208
     * Adds the information from the emconf array and TER to the extension information
209
     *
210
     * @param array $extensions
211
     * @return array
212
     */
213
    public function enrichExtensionsWithEmConfAndTerInformation(array $extensions)
214
    {
215
        $extensions = $this->enrichExtensionsWithEmConfInformation($extensions);
216
        foreach ($extensions as $extensionKey => $properties) {
217
            $terObject = $this->getExtensionTerData($extensionKey, $extensions[$extensionKey]['version'] ?? '');
218
            if ($terObject instanceof Extension) {
219
                $extensions[$extensionKey]['terObject'] = $terObject;
220
                $extensions[$extensionKey]['updateAvailable'] = false;
221
                $extensions[$extensionKey]['updateToVersion'] = null;
222
                $extensionToUpdate = $this->getUpdateableVersion($terObject);
223
                if ($extensionToUpdate instanceof Extension) {
224
                    $extensions[$extensionKey]['updateAvailable'] = true;
225
                    $extensions[$extensionKey]['updateToVersion'] = $extensionToUpdate;
226
                }
227
            }
228
        }
229
        return $extensions;
230
    }
231
232
    /**
233
     * Tries to find given extension with given version in TER data.
234
     * If extension is found but not the given version, we return TER data from highest version with version data set to
235
     * given one.
236
     *
237
     * @param string $extensionKey Key of the extension
238
     * @param string $version String representation of version number
239
     * @return Extension|null Extension TER object or NULL if nothing found
240
     */
241
    protected function getExtensionTerData($extensionKey, $version): ?Extension
242
    {
243
        $terObject = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionKey, $version);
244
        if (!$terObject instanceof Extension) {
245
            // Version unknown in TER data, try to find extension
246
            $terObject = $this->extensionRepository->findHighestAvailableVersion($extensionKey);
247
            if ($terObject instanceof Extension) {
0 ignored issues
show
introduced by
$terObject is always a sub-type of TYPO3\CMS\Extensionmanager\Domain\Model\Extension.
Loading history...
248
                // Found in TER now, set version information to the known ones, so we can look if there is a newer one
249
                // Use a cloned object, otherwise wrong information is stored in persistenceManager
250
                $terObject = clone $terObject;
251
                $terObject->setVersion($version);
252
                $terObject->setIntegerVersion(
253
                    VersionNumberUtility::convertVersionNumberToInteger($terObject->getVersion())
254
                );
255
            } else {
256
                $terObject = null;
257
            }
258
        }
259
260
        return $terObject;
261
    }
262
263
    /**
264
     * Gets all available and installed extension with additional information
265
     * from em_conf and TER (if available)
266
     *
267
     * @param string $filter
268
     * @return array
269
     */
270
    public function getAvailableAndInstalledExtensionsWithAdditionalInformation(string $filter = ''): array
271
    {
272
        $availableExtensions = $this->getAvailableExtensions($filter);
273
        $availableAndInstalledExtensions = $this->getAvailableAndInstalledExtensions($availableExtensions);
274
        return $this->enrichExtensionsWithEmConfAndTerInformation($availableAndInstalledExtensions);
275
    }
276
277
    /**
278
     * Returns the updateable version for an extension which also resolves dependencies.
279
     *
280
     * @param Extension $extensionData
281
     * @return Extension|null null if no update available otherwise latest possible update
282
     */
283
    protected function getUpdateableVersion(Extension $extensionData): ?Extension
284
    {
285
        // Only check for update for TER extensions
286
        $version = $extensionData->getIntegerVersion();
287
        $extensionUpdates = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
288
            $extensionData->getExtensionKey(),
289
            $version,
290
            0,
291
            false
292
        );
293
        if ($extensionUpdates->count() > 0) {
294
            foreach ($extensionUpdates as $extensionUpdate) {
295
                /** @var Extension $extensionUpdate */
296
                $this->dependencyUtility->checkDependencies($extensionUpdate);
297
                if (!$this->dependencyUtility->hasDependencyErrors()) {
298
                    return $extensionUpdate;
299
                }
300
            }
301
        }
302
        return null;
303
    }
304
}
305