Passed
Push — master ( c1d429...2049da )
by
unknown
16:56
created

InstallUtility::isValidExtensionPath()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
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 Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use Symfony\Component\Finder\Finder;
22
use TYPO3\CMS\Core\Cache\CacheManager;
23
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
24
use TYPO3\CMS\Core\Configuration\SiteConfiguration;
25
use TYPO3\CMS\Core\Core\Bootstrap;
26
use TYPO3\CMS\Core\Core\Environment;
27
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
28
use TYPO3\CMS\Core\Database\Schema\SqlReader;
29
use TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent;
30
use TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent;
31
use TYPO3\CMS\Core\Package\PackageManager;
32
use TYPO3\CMS\Core\Registry;
33
use TYPO3\CMS\Core\Service\OpcodeCacheService;
34
use TYPO3\CMS\Core\SingletonInterface;
35
use TYPO3\CMS\Core\Site\Entity\Site;
36
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Core\Utility\PathUtility;
39
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
40
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
41
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionDatabaseContentHasBeenImportedEvent;
42
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionFilesHaveBeenImportedEvent;
43
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenImportedEvent;
44
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
45
use TYPO3\CMS\Impexp\Import;
46
use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
47
use TYPO3\CMS\Install\Service\LateBootService;
48
49
/**
50
 * Extension Manager Install Utility
51
 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
52
 */
53
class InstallUtility implements SingletonInterface, LoggerAwareInterface
54
{
55
    use LoggerAwareTrait;
56
57
    /**
58
     * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
59
     */
60
    protected $fileHandlingUtility;
61
62
    /**
63
     * @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
64
     */
65
    protected $listUtility;
66
67
    /**
68
     * @var \TYPO3\CMS\Core\Package\PackageManager
69
     */
70
    protected $packageManager;
71
72
    /**
73
     * @var \TYPO3\CMS\Core\Cache\CacheManager
74
     */
75
    protected $cacheManager;
76
77
    /**
78
     * @var \TYPO3\CMS\Core\Registry
79
     */
80
    protected $registry;
81
82
    /**
83
     * @var EventDispatcherInterface
84
     */
85
    protected $eventDispatcher;
86
87
    /**
88
     * @var LateBootService
89
     */
90
    protected $lateBootService;
91
92
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
93
    {
94
        $this->eventDispatcher = $eventDispatcher;
95
    }
96
97
    /**
98
     * @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
99
     */
100
    public function injectFileHandlingUtility(FileHandlingUtility $fileHandlingUtility)
101
    {
102
        $this->fileHandlingUtility = $fileHandlingUtility;
103
    }
104
105
    /**
106
     * @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
107
     */
108
    public function injectListUtility(ListUtility $listUtility)
109
    {
110
        $this->listUtility = $listUtility;
111
    }
112
113
    /**
114
     * @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
115
     */
116
    public function injectPackageManager(PackageManager $packageManager)
117
    {
118
        $this->packageManager = $packageManager;
119
    }
120
121
    /**
122
     * @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
123
     */
124
    public function injectCacheManager(CacheManager $cacheManager)
125
    {
126
        $this->cacheManager = $cacheManager;
127
    }
128
129
    /**
130
     * @param \TYPO3\CMS\Core\Registry $registry
131
     */
132
    public function injectRegistry(Registry $registry)
133
    {
134
        $this->registry = $registry;
135
    }
136
137
    /**
138
     * @param  LateBootService $lateBootService
139
     */
140
    public function injectLateBootService(LateBootService $lateBootService)
141
    {
142
        $this->lateBootService = $lateBootService;
143
    }
144
145
    /**
146
     * Helper function to install an extension
147
     * also processes db updates and clears the cache if the extension asks for it
148
     *
149
     * @param string ...$extensionKeys
150
     * @throws ExtensionManagerException
151
     */
152
    public function install(...$extensionKeys)
153
    {
154
        $flushCaches = false;
155
        foreach ($extensionKeys as $extensionKey) {
156
            $this->loadExtension($extensionKey);
157
            $extension = $this->enrichExtensionWithDetails($extensionKey, false);
158
            $this->saveDefaultConfiguration($extensionKey);
159
            if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
160
                $flushCaches = true;
161
            }
162
        }
163
164
        if ($flushCaches) {
165
            $this->cacheManager->flushCaches();
166
        } else {
167
            $this->cacheManager->flushCachesInGroup('system');
168
        }
169
170
        // Load a new container as reloadCaches will load ext_localconf
171
        $container = $this->lateBootService->getContainer();
172
        $backup = $this->lateBootService->makeCurrent($container);
173
174
        $this->reloadCaches();
175
        $this->updateDatabase();
176
177
        foreach ($extensionKeys as $extensionKey) {
178
            $this->processExtensionSetup($extensionKey);
179
            $container->get(EventDispatcherInterface::class)->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this));
180
        }
181
182
        // Reset to the original container instance
183
        $this->lateBootService->makeCurrent(null, $backup);
184
    }
185
186
    /**
187
     * @param string $extensionKey
188
     */
189
    public function processExtensionSetup(string $extensionKey): void
190
    {
191
        $extension = $this->enrichExtensionWithDetails($extensionKey, false);
192
        $this->importInitialFiles($extension['packagePath'], $extensionKey);
193
        $this->importStaticSqlFile($extensionKey, $extension['packagePath']);
194
        $import = $this->importT3DFile($extensionKey, $extension['packagePath']);
195
        $this->importSiteConfiguration($extensionKey, $extension['packagePath'], $import);
196
    }
197
198
    /**
199
     * Helper function to uninstall an extension
200
     *
201
     * @param string $extensionKey
202
     * @throws ExtensionManagerException
203
     */
204
    public function uninstall($extensionKey)
205
    {
206
        $dependentExtensions = $this->findInstalledExtensionsThatDependOnExtension((string)$extensionKey);
207
        if (!empty($dependentExtensions)) {
208
            throw new ExtensionManagerException(
209
                LocalizationUtility::translate(
210
                    'extensionList.uninstall.dependencyError',
211
                    'extensionmanager',
212
                    [$extensionKey, implode(',', $dependentExtensions)]
213
                ) ?? '',
214
                1342554622
215
            );
216
        }
217
        $this->unloadExtension($extensionKey);
218
    }
219
220
    /**
221
     * Find installed extensions which depend on the given extension.
222
     * This is used at extension uninstall to stop the process if an installed
223
     * extension depends on the extension to be uninstalled.
224
     *
225
     * @param string $extensionKey
226
     * @return array
227
     */
228
    protected function findInstalledExtensionsThatDependOnExtension(string $extensionKey): array
229
    {
230
        $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation();
231
        $dependentExtensions = [];
232
        foreach ($availableAndInstalledExtensions as $availableAndInstalledExtensionKey => $availableAndInstalledExtension) {
233
            if (isset($availableAndInstalledExtension['installed']) && $availableAndInstalledExtension['installed'] === true) {
234
                if (is_array($availableAndInstalledExtension['constraints']) && is_array($availableAndInstalledExtension['constraints']['depends']) && array_key_exists($extensionKey, $availableAndInstalledExtension['constraints']['depends'])) {
235
                    $dependentExtensions[] = $availableAndInstalledExtensionKey;
236
                }
237
            }
238
        }
239
        return $dependentExtensions;
240
    }
241
242
    /**
243
     * Reset and reload the available extensions
244
     */
245
    public function reloadAvailableExtensions()
246
    {
247
        $this->listUtility->reloadAvailableExtensions();
248
    }
249
250
    /**
251
     * Wrapper function for loading extensions
252
     *
253
     * @param string $extensionKey
254
     */
255
    protected function loadExtension($extensionKey)
256
    {
257
        $this->packageManager->activatePackage($extensionKey);
258
    }
259
260
    /**
261
     * Wrapper function for unloading extensions
262
     *
263
     * @param string $extensionKey
264
     */
265
    protected function unloadExtension($extensionKey)
266
    {
267
        $this->packageManager->deactivatePackage($extensionKey);
268
        $this->eventDispatcher->dispatch(new AfterPackageDeactivationEvent($extensionKey, 'typo3-cms-extension', $this));
269
        $this->cacheManager->flushCachesInGroup('system');
270
    }
271
272
    /**
273
     * Checks if an extension is available in the system
274
     *
275
     * @param string $extensionKey
276
     * @return bool
277
     */
278
    public function isAvailable($extensionKey)
279
    {
280
        return $this->packageManager->isPackageAvailable($extensionKey);
281
    }
282
283
    /**
284
     * Reloads the package information, if the package is already registered
285
     *
286
     * @param string $extensionKey
287
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
288
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
289
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
290
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
291
     */
292
    public function reloadPackageInformation($extensionKey)
293
    {
294
        if ($this->packageManager->isPackageAvailable($extensionKey)) {
295
            $this->reloadOpcache();
296
            $this->packageManager->reloadPackageInformation($extensionKey);
297
        }
298
    }
299
300
    /**
301
     * Fetch additional information for an extension key
302
     *
303
     * @param string $extensionKey
304
     * @param bool $loadTerInformation
305
     * @return array
306
     * @throws ExtensionManagerException
307
     * @internal
308
     */
309
    public function enrichExtensionWithDetails($extensionKey, $loadTerInformation = true)
310
    {
311
        $extension = $this->getExtensionArray($extensionKey);
312
        if (!$loadTerInformation) {
313
            $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfInformation([$extensionKey => $extension]);
314
        } else {
315
            $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation([$extensionKey => $extension]);
316
        }
317
318
        if (!isset($availableAndInstalledExtensions[$extensionKey])) {
319
            throw new ExtensionManagerException(
320
                'Please check your uploaded extension "' . $extensionKey . '". The configuration file "ext_emconf.php" seems to be invalid.',
321
                1391432222
322
            );
323
        }
324
325
        return $availableAndInstalledExtensions[$extensionKey];
326
    }
327
328
    /**
329
     * @param string $extensionKey
330
     * @return array
331
     * @throws ExtensionManagerException
332
     */
333
    protected function getExtensionArray($extensionKey)
334
    {
335
        $availableExtensions = $this->listUtility->getAvailableExtensions();
336
        if (isset($availableExtensions[$extensionKey])) {
337
            return $availableExtensions[$extensionKey];
338
        }
339
        throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1342864081);
340
    }
341
342
    /**
343
     * Reload Cache files and Typo3LoadedExtensions
344
     */
345
    public function reloadCaches()
346
    {
347
        $this->reloadOpcache();
348
        ExtensionManagementUtility::loadExtLocalconf(false);
349
        Bootstrap::loadBaseTca(false);
350
        Bootstrap::loadExtTables(false);
351
    }
352
353
    /**
354
     * Reloads PHP opcache
355
     */
356
    protected function reloadOpcache()
357
    {
358
        GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
359
    }
360
361
    /**
362
     * Executes all safe database statements.
363
     * Tables and fields are created and altered. Nothing gets deleted or renamed here.
364
     */
365
    protected function updateDatabase()
366
    {
367
        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
368
        $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
369
        $sqlStatements = [];
370
        $sqlStatements[] = $sqlReader->getTablesDefinitionString();
371
        $sqlStatements = $sqlReader->getCreateTableStatementArray(implode(LF . LF, array_filter($sqlStatements)));
372
        $updateStatements = $schemaMigrator->getUpdateSuggestions($sqlStatements);
373
374
        $updateStatements = array_merge_recursive(...array_values($updateStatements));
375
        $selectedStatements = [];
376
        foreach (['add', 'change', 'create_table', 'change_table'] as $action) {
377
            if (empty($updateStatements[$action])) {
378
                continue;
379
            }
380
381
            $statements = array_combine(array_keys($updateStatements[$action]), array_fill(0, count($updateStatements[$action]), true));
382
            $statements = is_array($statements) ? $statements : [];
383
            $selectedStatements = array_merge(
384
                $selectedStatements,
385
                $statements
386
            );
387
        }
388
389
        $schemaMigrator->migrate($sqlStatements, $selectedStatements);
390
    }
391
392
    /**
393
     * Save default configuration of an extension
394
     *
395
     * @param string $extensionKey
396
     */
397
    protected function saveDefaultConfiguration($extensionKey)
398
    {
399
        $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
400
        $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfiguration($extensionKey);
401
    }
402
403
    /**
404
     * Import static SQL data (normally used for ext_tables_static+adt.sql)
405
     *
406
     * @param string $rawDefinitions
407
     */
408
    public function importStaticSql($rawDefinitions)
409
    {
410
        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
411
        $statements = $sqlReader->getStatementArray($rawDefinitions);
412
413
        $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
414
        $schemaMigrationService->importStaticData($statements, true);
415
    }
416
417
    /**
418
     * Remove an extension (delete the directory)
419
     *
420
     * @param string $extension
421
     * @throws ExtensionManagerException
422
     */
423
    public function removeExtension($extension)
424
    {
425
        $absolutePath = $this->enrichExtensionWithDetails($extension)['packagePath'];
426
        if ($this->isValidExtensionPath($absolutePath)) {
427
            if ($this->packageManager->isPackageAvailable($extension)) {
428
                // Package manager deletes the extension and removes the entry from PackageStates.php
429
                $this->packageManager->deletePackage($extension);
430
            } else {
431
                // The extension is not listed in PackageStates.php, we can safely remove it
432
                $this->fileHandlingUtility->removeDirectory($absolutePath);
433
            }
434
        } else {
435
            throw new ExtensionManagerException('No valid extension path given.', 1342875724);
436
        }
437
    }
438
439
    /**
440
     * Uses the export import extension to import a T3D or XML file to PID 0
441
     * Execution state is saved in the this->registry, so it only happens once
442
     *
443
     * @param string $extensionKey
444
     * @param string $packagePath
445
     * @return Import|null
446
     */
447
    protected function importT3DFile($extensionKey, $packagePath): ?Import
448
    {
449
        $extensionSiteRelPath = PathUtility::stripPathSitePrefix($packagePath);
450
        $registryKeysToCheck = [
451
            $extensionSiteRelPath . 'Initialisation/data.t3d',
452
            $extensionSiteRelPath . 'Initialisation/dataImported',
453
        ];
454
        foreach ($registryKeysToCheck as $registryKeyToCheck) {
455
            if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
456
                // Data was imported before => early return
457
                return null;
458
            }
459
        }
460
        $importFileToUse = null;
461
        $possibleImportFiles = [
462
            $packagePath . 'Initialisation/data.t3d',
463
            $packagePath . 'Initialisation/data.xml'
464
        ];
465
        foreach ($possibleImportFiles as $possibleImportFile) {
466
            if (!file_exists($possibleImportFile)) {
467
                continue;
468
            }
469
            $importFileToUse = $possibleImportFile;
470
        }
471
        if ($importFileToUse !== null) {
472
            $importExportUtility = GeneralUtility::makeInstance(ImportExportUtility::class);
473
            try {
474
                $importResult = $importExportUtility->importT3DFile($importFileToUse, 0);
475
                $this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
476
                $this->eventDispatcher->dispatch(new AfterExtensionDatabaseContentHasBeenImportedEvent($extensionKey, $importFileToUse, $importResult, $this));
477
                return $importExportUtility->getImport();
478
            } catch (\ErrorException $e) {
479
                $this->logger->warning($e->getMessage(), ['exception' => $e]);
480
            }
481
        }
482
        return null;
483
    }
484
485
    /**
486
     * Imports a static tables SQL File (ext_tables_static+adt)
487
     * Execution state is saved in the this->registry, so it only happens once
488
     *
489
     * @param string $extensionKey
490
     * @param string $packagePath
491
     */
492
    protected function importStaticSqlFile(string $extensionKey, $packagePath)
493
    {
494
        $extTablesStaticSqlFile = $packagePath . 'ext_tables_static+adt.sql';
495
        $extTablesStaticSqlRelFile = PathUtility::stripPathSitePrefix($extTablesStaticSqlFile);
496
        if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
497
            $shortFileHash = '';
498
            if (file_exists($extTablesStaticSqlFile)) {
499
                $extTablesStaticSqlContent = (string)file_get_contents($extTablesStaticSqlFile);
500
                $shortFileHash = md5($extTablesStaticSqlContent);
501
                $this->importStaticSql($extTablesStaticSqlContent);
502
            }
503
            $this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, $shortFileHash);
504
            $this->eventDispatcher->dispatch(new AfterExtensionStaticDatabaseContentHasBeenImportedEvent($extensionKey, $extTablesStaticSqlFile, $this));
505
        }
506
    }
507
508
    /**
509
     * Imports files from Initialisation/Files to fileadmin
510
     * via lowlevel copy directory method
511
     *
512
     * @param string $packagePath absolute path to extension dir
513
     * @param string $extensionKey
514
     */
515
    protected function importInitialFiles($packagePath, $extensionKey)
516
    {
517
        $importFolder = $packagePath . 'Initialisation/Files';
518
        $importRelFolder = PathUtility::stripPathSitePrefix($importFolder);
519
        if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
520
            if (file_exists($importFolder)) {
521
                $destinationAbsolutePath = GeneralUtility::getFileAbsFileName($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey);
522
                if (!file_exists($destinationAbsolutePath) &&
523
                    GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
524
                ) {
525
                    GeneralUtility::mkdir($destinationAbsolutePath);
526
                }
527
                GeneralUtility::copyDirectory($importFolder, $destinationAbsolutePath);
528
                $this->registry->set('extensionDataImport', $importRelFolder, 1);
529
                $this->eventDispatcher->dispatch(new AfterExtensionFilesHaveBeenImportedEvent($extensionKey, $destinationAbsolutePath, $this));
530
            }
531
        }
532
    }
533
534
    /**
535
     * @param string $extensionKey
536
     * @param string $packagePath
537
     * @param Import|null $import
538
     */
539
    protected function importSiteConfiguration(string $extensionKey, string $packagePath, Import $import = null): void
540
    {
541
        $importAbsFolder = $packagePath . 'Initialisation/Site';
542
        $destinationFolder = Environment::getConfigPath() . '/sites';
543
544
        if (!is_dir($importAbsFolder)) {
545
            return;
546
        }
547
548
        $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class);
549
        $existingSites = $siteConfiguration->resolveAllExistingSites(false);
550
551
        GeneralUtility::mkdir($destinationFolder);
552
        $finder = GeneralUtility::makeInstance(Finder::class);
553
        $finder->directories()->in($importAbsFolder);
554
        if ($finder->hasResults()) {
555
            foreach ($finder as $siteConfigDirectory) {
556
                $siteIdentifier = $siteConfigDirectory->getBasename();
557
                if (isset($existingSites[$siteIdentifier])) {
558
                    $this->logger->warning(
559
                        sprintf(
560
                            'Skipped importing site configuration from %s due to existing site identifier %s',
561
                            $extensionKey,
562
                            $siteIdentifier
563
                        )
564
                    );
565
                    continue;
566
                }
567
                $targetDir = $destinationFolder . '/' . $siteIdentifier;
568
                if (!$this->registry->get('siteConfigImport', $siteIdentifier) && !is_dir($targetDir)) {
569
                    GeneralUtility::mkdir($targetDir);
570
                    GeneralUtility::copyDirectory($siteConfigDirectory->getPathname(), $targetDir);
571
                    $this->registry->set('siteConfigImport', $siteIdentifier, 1);
572
                }
573
            }
574
        }
575
576
        /** @var Site[] $newSites */
577
        $newSites = array_diff_key($siteConfiguration->resolveAllExistingSites(false), $existingSites);
578
        $importedPages = $import->import_mapId['pages'] ?? null;
579
580
        foreach ($newSites as $newSite) {
581
            $exportedPageId = $newSite->getRootPageId();
582
            $importedPageId = $importedPages[$exportedPageId] ?? null;
583
            if ($importedPageId === null) {
584
                $this->logger->warning(
585
                    sprintf(
586
                        'Imported site configuration with identifier %s could not be mapped to imported page id',
587
                        $newSite->getIdentifier()
588
                    )
589
                );
590
                continue;
591
            }
592
            $configuration = $siteConfiguration->load($newSite->getIdentifier());
593
            $configuration['rootPageId'] = $importedPageId;
594
            $siteConfiguration->write($newSite->getIdentifier(), $configuration);
595
        }
596
    }
597
598
    /**
599
     * Is the given path a valid path for extension installation
600
     *
601
     * @param string $path the absolute (!) path in question
602
     * @return bool
603
     */
604
    protected function isValidExtensionPath($path): bool
605
    {
606
        $allowedPaths = Extension::returnAllowedInstallPaths();
607
        foreach ($allowedPaths as $allowedPath) {
608
            if (GeneralUtility::isFirstPartOfStr($path, $allowedPath)) {
609
                return true;
610
            }
611
        }
612
        return false;
613
    }
614
}
615