[!!!][TASK] Extension manager: Drop "Download SQL Dump"
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Utility / InstallUtility.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
18 use TYPO3\CMS\Core\Database\Schema\SqlReader;
19 use TYPO3\CMS\Core\Service\OpcodeCacheService;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
22 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
23 use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
24
25 /**
26 * Extension Manager Install Utility
27 */
28 class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface
29 {
30 /**
31 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
32 */
33 public $objectManager;
34
35 /**
36 * @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
37 */
38 protected $dependencyUtility;
39
40 /**
41 * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
42 */
43 protected $fileHandlingUtility;
44
45 /**
46 * @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
47 */
48 protected $listUtility;
49
50 /**
51 * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
52 */
53 public $extensionRepository;
54
55 /**
56 * @var \TYPO3\CMS\Core\Package\PackageManager
57 */
58 protected $packageManager;
59
60 /**
61 * @var \TYPO3\CMS\Core\Cache\CacheManager
62 */
63 protected $cacheManager;
64
65 /**
66 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
67 */
68 protected $signalSlotDispatcher;
69
70 /**
71 * @var \TYPO3\CMS\Core\Registry
72 */
73 protected $registry;
74
75 /**
76 * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
77 */
78 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
79 {
80 $this->objectManager = $objectManager;
81 }
82
83 /**
84 * @param \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility
85 */
86 public function injectDependencyUtility(\TYPO3\CMS\Extensionmanager\Utility\DependencyUtility $dependencyUtility)
87 {
88 $this->dependencyUtility = $dependencyUtility;
89 }
90
91 /**
92 * @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
93 */
94 public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
95 {
96 $this->fileHandlingUtility = $fileHandlingUtility;
97 }
98
99 /**
100 * @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
101 */
102 public function injectListUtility(\TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility)
103 {
104 $this->listUtility = $listUtility;
105 }
106
107 /**
108 * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
109 */
110 public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
111 {
112 $this->extensionRepository = $extensionRepository;
113 }
114
115 /**
116 * @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
117 */
118 public function injectPackageManager(\TYPO3\CMS\Core\Package\PackageManager $packageManager)
119 {
120 $this->packageManager = $packageManager;
121 }
122
123 /**
124 * @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
125 */
126 public function injectCacheManager(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager)
127 {
128 $this->cacheManager = $cacheManager;
129 }
130
131 /**
132 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
133 */
134 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
135 {
136 $this->signalSlotDispatcher = $signalSlotDispatcher;
137 }
138
139 /**
140 * @param \TYPO3\CMS\Core\Registry $registry
141 */
142 public function injectRegistry(\TYPO3\CMS\Core\Registry $registry)
143 {
144 $this->registry = $registry;
145 }
146
147 /**
148 * Helper function to install an extension
149 * also processes db updates and clears the cache if the extension asks for it
150 *
151 * @param string $extensionKey
152 * @throws ExtensionManagerException
153 */
154 public function install($extensionKey)
155 {
156 $extension = $this->enrichExtensionWithDetails($extensionKey, false);
157 $this->loadExtension($extensionKey);
158 if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
159 $this->cacheManager->flushCaches();
160 } else {
161 $this->cacheManager->flushCachesInGroup('system');
162 }
163 $this->reloadCaches();
164 $this->processExtensionSetup($extensionKey);
165
166 $this->emitAfterExtensionInstallSignal($extensionKey);
167 }
168
169 /**
170 * @param string $extensionKey
171 */
172 public function processExtensionSetup($extensionKey)
173 {
174 $extension = $this->enrichExtensionWithDetails($extensionKey, false);
175 $this->ensureConfiguredDirectoriesExist($extension);
176 $this->importInitialFiles($extension['siteRelPath'], $extensionKey);
177 $this->processDatabaseUpdates($extension);
178 $this->processRuntimeDatabaseUpdates($extensionKey);
179 $this->saveDefaultConfiguration($extensionKey);
180 }
181
182 /**
183 * Helper function to uninstall an extension
184 *
185 * @param string $extensionKey
186 * @throws ExtensionManagerException
187 */
188 public function uninstall($extensionKey)
189 {
190 $dependentExtensions = $this->dependencyUtility->findInstalledExtensionsThatDependOnMe($extensionKey);
191 if (is_array($dependentExtensions) && !empty($dependentExtensions)) {
192 throw new ExtensionManagerException(
193 \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
194 'extensionList.uninstall.dependencyError',
195 'extensionmanager',
196 [$extensionKey, implode(',', $dependentExtensions)]
197 ),
198 1342554622
199 );
200 } else {
201 $this->unloadExtension($extensionKey);
202 }
203 }
204
205 /**
206 * Wrapper function to check for loaded extensions
207 *
208 * @param string $extensionKey
209 * @return bool TRUE if extension is loaded
210 */
211 public function isLoaded($extensionKey)
212 {
213 return $this->packageManager->isPackageActive($extensionKey);
214 }
215
216 /**
217 * Reset and reload the available extensions
218 */
219 public function reloadAvailableExtensions()
220 {
221 $this->listUtility->reloadAvailableExtensions();
222 }
223
224 /**
225 * Wrapper function for loading extensions
226 *
227 * @param string $extensionKey
228 */
229 protected function loadExtension($extensionKey)
230 {
231 $this->packageManager->activatePackage($extensionKey);
232 }
233
234 /**
235 * Wrapper function for unloading extensions
236 *
237 * @param string $extensionKey
238 */
239 protected function unloadExtension($extensionKey)
240 {
241 $this->packageManager->deactivatePackage($extensionKey);
242 $this->emitAfterExtensionUninstallSignal($extensionKey);
243 $this->cacheManager->flushCachesInGroup('system');
244 }
245
246 /**
247 * Emits a signal after an extension has been installed
248 *
249 * @param string $extensionKey
250 */
251 protected function emitAfterExtensionInstallSignal($extensionKey)
252 {
253 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionInstall', [$extensionKey, $this]);
254 }
255
256 /**
257 * Emits a signal after an extension has been uninstalled
258 *
259 * @param string $extensionKey
260 */
261 protected function emitAfterExtensionUninstallSignal($extensionKey)
262 {
263 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionUninstall', [$extensionKey, $this]);
264 }
265
266 /**
267 * Checks if an extension is available in the system
268 *
269 * @param string $extensionKey
270 * @return bool
271 */
272 public function isAvailable($extensionKey)
273 {
274 return $this->packageManager->isPackageAvailable($extensionKey);
275 }
276
277 /**
278 * Reloads the package information, if the package is already registered
279 *
280 * @param string $extensionKey
281 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
282 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
283 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
284 * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
285 */
286 public function reloadPackageInformation($extensionKey)
287 {
288 if ($this->packageManager->isPackageAvailable($extensionKey)) {
289 $this->reloadOpcache();
290 $this->packageManager->reloadPackageInformation($extensionKey);
291 }
292 }
293
294 /**
295 * Fetch additional information for an extension key
296 *
297 * @param string $extensionKey
298 * @param bool $loadTerInformation
299 * @access private
300 * @return array
301 * @throws ExtensionManagerException
302 */
303 public function enrichExtensionWithDetails($extensionKey, $loadTerInformation = true)
304 {
305 $extension = $this->getExtensionArray($extensionKey);
306 if (!$loadTerInformation) {
307 $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfInformation([$extensionKey => $extension]);
308 } else {
309 $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation([$extensionKey => $extension]);
310 }
311
312 if (!isset($availableAndInstalledExtensions[$extensionKey])) {
313 throw new ExtensionManagerException(
314 'Please check your uploaded extension "' . $extensionKey . '". The configuration file "ext_emconf.php" seems to be invalid.',
315 1391432222
316 );
317 }
318
319 return $availableAndInstalledExtensions[$extensionKey];
320 }
321
322 /**
323 * @param string $extensionKey
324 * @return array
325 * @throws ExtensionManagerException
326 */
327 protected function getExtensionArray($extensionKey)
328 {
329 $availableExtensions = $this->listUtility->getAvailableExtensions();
330 if (isset($availableExtensions[$extensionKey])) {
331 return $availableExtensions[$extensionKey];
332 } else {
333 throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1342864081);
334 }
335 }
336
337 /**
338 * Creates directories as requested in ext_emconf.php
339 *
340 * @param array $extension
341 */
342 protected function ensureConfiguredDirectoriesExist(array $extension)
343 {
344 $this->fileHandlingUtility->ensureConfiguredDirectoriesExist($extension);
345 }
346
347 /**
348 * Gets the content of the ext_tables.sql and ext_tables_static+adt.sql files
349 * Additionally adds the table definitions for the cache tables
350 *
351 * @param array $extension
352 */
353 public function processDatabaseUpdates(array $extension)
354 {
355 $extTablesSqlFile = PATH_site . $extension['siteRelPath'] . 'ext_tables.sql';
356 $extTablesSqlContent = '';
357 if (file_exists($extTablesSqlFile)) {
358 $extTablesSqlContent .= file_get_contents($extTablesSqlFile);
359 }
360 if ($extTablesSqlContent !== '') {
361 try {
362 $this->updateDbWithExtTablesSql($extTablesSqlContent);
363 } catch (\TYPO3\CMS\Core\Database\Schema\Exception\StatementException $e) {
364 throw new ExtensionManagerException(
365 $e->getMessage(),
366 1476340371
367 );
368 }
369 }
370
371 $this->importStaticSqlFile($extension['siteRelPath']);
372 $this->importT3DFile($extension['siteRelPath']);
373 }
374
375 /**
376 * Gets all database updates due to runtime configuration, like caching framework or
377 * category api for example
378 *
379 * @param string $extensionKey
380 */
381 protected function processRuntimeDatabaseUpdates($extensionKey)
382 {
383 $sqlString = $this->emitTablesDefinitionIsBeingBuiltSignal($extensionKey);
384 if (!empty($sqlString)) {
385 $this->updateDbWithExtTablesSql(implode(LF . LF . LF . LF, $sqlString));
386 }
387 }
388
389 /**
390 * Emits a signal to manipulate the tables definitions
391 *
392 * @param string $extensionKey
393 * @return mixed
394 * @throws ExtensionManagerException
395 */
396 protected function emitTablesDefinitionIsBeingBuiltSignal($extensionKey)
397 {
398 $signalReturn = $this->signalSlotDispatcher->dispatch(__CLASS__, 'tablesDefinitionIsBeingBuilt', [[], $extensionKey]);
399 // This is important to support old associated returns
400 $signalReturn = array_values($signalReturn);
401 $sqlString = $signalReturn[0];
402 if (!is_array($sqlString)) {
403 throw new ExtensionManagerException(
404 sprintf(
405 'The signal %s of class %s returned a value of type %s, but array was expected.',
406 'tablesDefinitionIsBeingBuilt',
407 __CLASS__,
408 gettype($sqlString)
409 ),
410 1382360258
411 );
412 }
413 return $sqlString;
414 }
415
416 /**
417 * Reload Cache files and Typo3LoadedExtensions
418 */
419 public function reloadCaches()
420 {
421 $this->reloadOpcache();
422 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtLocalconf(false);
423 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
424 ->loadBaseTca(false)
425 ->loadExtTables(false);
426 }
427
428 /**
429 * Reloads PHP opcache
430 */
431 protected function reloadOpcache()
432 {
433 GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
434 }
435
436 /**
437 * Save default configuration of an extension
438 *
439 * @param string $extensionKey
440 */
441 protected function saveDefaultConfiguration($extensionKey)
442 {
443 /** @var $configUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
444 $configUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
445 $configUtility->saveDefaultConfiguration($extensionKey);
446 }
447
448 /**
449 * Update database / process db updates from ext_tables
450 *
451 * @param string $rawDefinitions The raw SQL statements from ext_tables.sql
452 */
453 public function updateDbWithExtTablesSql($rawDefinitions)
454 {
455 $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
456 $statements = $sqlReader->getCreateTableStatementArray($rawDefinitions);
457 if (count($statements) !== 0) {
458 $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
459 $schemaMigrationService->install($statements);
460 }
461 }
462
463 /**
464 * Import static SQL data (normally used for ext_tables_static+adt.sql)
465 *
466 * @param string $rawDefinitions
467 */
468 public function importStaticSql($rawDefinitions)
469 {
470 $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
471 $statements = $sqlReader->getStatementArray($rawDefinitions);
472
473 $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
474 $schemaMigrationService->importStaticData($statements, true);
475 }
476
477 /**
478 * Remove an extension (delete the directory)
479 *
480 * @param string $extension
481 * @throws ExtensionManagerException
482 */
483 public function removeExtension($extension)
484 {
485 $absolutePath = $this->fileHandlingUtility->getAbsoluteExtensionPath($extension);
486 if ($this->fileHandlingUtility->isValidExtensionPath($absolutePath)) {
487 if ($this->packageManager->isPackageAvailable($extension)) {
488 // Package manager deletes the extension and removes the entry from PackageStates.php
489 $this->packageManager->deletePackage($extension);
490 } else {
491 // The extension is not listed in PackageStates.php, we can safely remove it
492 $this->fileHandlingUtility->removeDirectory($absolutePath);
493 }
494 } else {
495 throw new ExtensionManagerException('No valid extension path given.', 1342875724);
496 }
497 }
498
499 /**
500 * Checks if an update for an extension is available which also resolves dependencies.
501 *
502 * @internal
503 * @param Extension $extensionData
504 * @return bool
505 */
506 public function isUpdateAvailable(Extension $extensionData)
507 {
508 return (bool)$this->getUpdateableVersion($extensionData);
509 }
510
511 /**
512 * Returns the updateable version for an extension which also resolves dependencies.
513 *
514 * @internal
515 * @param Extension $extensionData
516 * @return bool|Extension FALSE if no update available otherwise latest possible update
517 */
518 public function getUpdateableVersion(Extension $extensionData)
519 {
520 // Only check for update for TER extensions
521 $version = $extensionData->getIntegerVersion();
522
523 /** @var $extensionUpdates[] \TYPO3\CMS\Extensionmanager\Domain\Model\Extension */
524 $extensionUpdates = $this->extensionRepository->findByVersionRangeAndExtensionKeyOrderedByVersion(
525 $extensionData->getExtensionKey(),
526 $version,
527 0,
528 false
529 );
530 if ($extensionUpdates->count() > 0) {
531 foreach ($extensionUpdates as $extensionUpdate) {
532 try {
533 $this->dependencyUtility->checkDependencies($extensionUpdate);
534 if (!$this->dependencyUtility->hasDependencyErrors()) {
535 return $extensionUpdate;
536 }
537 } catch (ExtensionManagerException $e) {
538 }
539 }
540 }
541 return false;
542 }
543
544 /**
545 * Uses the export import extension to import a T3D or XML file to PID 0
546 * Execution state is saved in the this->registry, so it only happens once
547 *
548 * @param string $extensionSiteRelPath
549 */
550 protected function importT3DFile($extensionSiteRelPath)
551 {
552 $registryKeysToCheck = [
553 $extensionSiteRelPath . 'Initialisation/data.t3d',
554 $extensionSiteRelPath . 'Initialisation/dataImported',
555 ];
556 foreach ($registryKeysToCheck as $registryKeyToCheck) {
557 if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
558 // Data was imported before => early return
559 return;
560 }
561 }
562 $importFileToUse = null;
563 $possibleImportFiles = [
564 $extensionSiteRelPath . 'Initialisation/data.t3d',
565 $extensionSiteRelPath . 'Initialisation/data.xml'
566 ];
567 foreach ($possibleImportFiles as $possibleImportFile) {
568 if (!file_exists(PATH_site . $possibleImportFile)) {
569 continue;
570 }
571 $importFileToUse = $possibleImportFile;
572 }
573 if ($importFileToUse !== null) {
574 /** @var ImportExportUtility $importExportUtility */
575 $importExportUtility = $this->objectManager->get(ImportExportUtility::class);
576 try {
577 $importResult = $importExportUtility->importT3DFile(PATH_site . $importFileToUse, 0);
578 $this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
579 $this->emitAfterExtensionT3DImportSignal($importFileToUse, $importResult);
580 } catch (\ErrorException $e) {
581 /** @var \TYPO3\CMS\Core\Log\Logger $logger */
582 $logger = $this->objectManager->get(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
583 $logger->log(\TYPO3\CMS\Core\Log\LogLevel::WARNING, $e->getMessage());
584 }
585 }
586 }
587
588 /**
589 * Emits a signal after an t3d file was imported
590 *
591 * @param string $importFileToUse
592 * @param int $importResult
593 */
594 protected function emitAfterExtensionT3DImportSignal($importFileToUse, $importResult)
595 {
596 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionT3DImport', [$importFileToUse, $importResult, $this]);
597 }
598
599 /**
600 * Imports a static tables SQL File (ext_tables_static+adt)
601 * Execution state is saved in the this->registry, so it only happens once
602 *
603 * @param string $extensionSiteRelPath
604 */
605 protected function importStaticSqlFile($extensionSiteRelPath)
606 {
607 $extTablesStaticSqlRelFile = $extensionSiteRelPath . 'ext_tables_static+adt.sql';
608 if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
609 $extTablesStaticSqlFile = PATH_site . $extTablesStaticSqlRelFile;
610 $shortFileHash = '';
611 if (file_exists($extTablesStaticSqlFile)) {
612 $extTablesStaticSqlContent = file_get_contents($extTablesStaticSqlFile);
613 $shortFileHash = md5($extTablesStaticSqlContent);
614 $this->importStaticSql($extTablesStaticSqlContent);
615 }
616 $this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, $shortFileHash);
617 $this->emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile);
618 }
619 }
620
621 /**
622 * Emits a signal after a static sql file was imported
623 *
624 * @param string $extTablesStaticSqlRelFile
625 */
626 protected function emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile)
627 {
628 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionStaticSqlImport', [$extTablesStaticSqlRelFile, $this]);
629 }
630
631 /**
632 * Imports files from Initialisation/Files to fileadmin
633 * via lowlevel copy directory method
634 *
635 * @param string $extensionSiteRelPath relative path to extension dir
636 * @param string $extensionKey
637 */
638 protected function importInitialFiles($extensionSiteRelPath, $extensionKey)
639 {
640 $importRelFolder = $extensionSiteRelPath . 'Initialisation/Files';
641 if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
642 $importFolder = PATH_site . $importRelFolder;
643 if (file_exists($importFolder)) {
644 $destinationRelPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey;
645 $destinationAbsolutePath = PATH_site . $destinationRelPath;
646 if (!file_exists($destinationAbsolutePath) &&
647 GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
648 ) {
649 GeneralUtility::mkdir($destinationAbsolutePath);
650 }
651 GeneralUtility::copyDirectory($importRelFolder, $destinationRelPath);
652 $this->registry->set('extensionDataImport', $importRelFolder, 1);
653 $this->emitAfterExtensionFileImportSignal($destinationAbsolutePath);
654 }
655 }
656 }
657
658 /**
659 * Emits a signal after extension files were imported
660 *
661 * @param string $destinationAbsolutePath
662 */
663 protected function emitAfterExtensionFileImportSignal($destinationAbsolutePath)
664 {
665 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionFileImport', [$destinationAbsolutePath, $this]);
666 }
667 }