fe21dacae49647dbd9e4fdbd6602c2f4e30e4807
[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\Impexp\Utility\ImportExportUtility;
18
19 /**
20 * Extension Manager Install Utility
21 *
22 * @author Susanne Moog <susanne.moog@typo3.org>
23 */
24 class InstallUtility implements \TYPO3\CMS\Core\SingletonInterface {
25
26 /**
27 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
28 * @inject
29 */
30 public $objectManager;
31
32 /**
33 * @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService
34 * @inject
35 */
36 public $installToolSqlParser;
37
38 /**
39 * @var \TYPO3\CMS\Extensionmanager\Utility\DependencyUtility
40 * @inject
41 */
42 protected $dependencyUtility;
43
44 /**
45 * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
46 * @inject
47 */
48 protected $fileHandlingUtility;
49
50 /**
51 * @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
52 * @inject
53 */
54 protected $listUtility;
55
56 /**
57 * @var \TYPO3\CMS\Extensionmanager\Utility\DatabaseUtility
58 * @inject
59 */
60 protected $databaseUtility;
61
62 /**
63 * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
64 * @inject
65 */
66 public $extensionRepository;
67
68 /**
69 * @var \TYPO3\CMS\Core\Package\PackageManager
70 * @inject
71 */
72 protected $packageManager;
73
74 /**
75 * @var \TYPO3\CMS\Core\Cache\CacheManager
76 * @inject
77 */
78 protected $cacheManager;
79
80 /**
81 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
82 * @inject
83 */
84 protected $signalSlotDispatcher;
85
86 /**
87 * @var \TYPO3\CMS\Core\Registry
88 * @inject
89 */
90 protected $registry;
91
92 /**
93 * Helper function to install an extension
94 * also processes db updates and clears the cache if the extension asks for it
95 *
96 * @param string $extensionKey
97 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
98 * @return void
99 */
100 public function install($extensionKey) {
101 $extension = $this->enrichExtensionWithDetails($extensionKey);
102 $this->ensureConfiguredDirectoriesExist($extension);
103 if (!$this->isLoaded($extensionKey)) {
104 $this->loadExtension($extensionKey);
105 }
106 if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
107 $this->cacheManager->flushCaches();
108 } else {
109 $this->cacheManager->flushCachesInGroup('system');
110 }
111 $this->reloadCaches();
112
113 $this->importInitialFiles($extension['siteRelPath'], $extensionKey);
114 $this->processDatabaseUpdates($extension);
115 $this->processRuntimeDatabaseUpdates($extensionKey);
116 $this->saveDefaultConfiguration($extension['key']);
117
118 $this->emitAfterExtensionInstallSignal($extensionKey);
119 }
120
121 /**
122 * Helper function to uninstall an extension
123 *
124 * @param string $extensionKey
125 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
126 * @return void
127 */
128 public function uninstall($extensionKey) {
129 $dependentExtensions = $this->dependencyUtility->findInstalledExtensionsThatDependOnMe($extensionKey);
130 if (is_array($dependentExtensions) && count($dependentExtensions) > 0) {
131 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
132 \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate(
133 'extensionList.uninstall.dependencyError',
134 'extensionmanager',
135 array($extensionKey, implode(',', $dependentExtensions))
136 ),
137 1342554622
138 );
139 } else {
140 $this->unloadExtension($extensionKey);
141 }
142 }
143
144 /**
145 * Wrapper function to check for loaded extensions
146 *
147 * @param string $extensionKey
148 * @return bool TRUE if extension is loaded
149 */
150 public function isLoaded($extensionKey) {
151 return $this->packageManager->isPackageActive($extensionKey);
152 }
153
154 /**
155 * Wrapper function for loading extensions
156 *
157 * @param string $extensionKey
158 * @return void
159 */
160 protected function loadExtension($extensionKey) {
161 $this->packageManager->activatePackage($extensionKey);
162 }
163
164 /**
165 * Wrapper function for unloading extensions
166 *
167 * @param string $extensionKey
168 * @return void
169 */
170 protected function unloadExtension($extensionKey) {
171 $this->packageManager->deactivatePackage($extensionKey);
172 $this->emitAfterExtensionUninstallSignal($extensionKey);
173 $this->cacheManager->flushCachesInGroup('system');
174 }
175
176 /**
177 * Emits a signal after an extension has been installed
178 *
179 * @param string $extensionKey
180 */
181 protected function emitAfterExtensionInstallSignal($extensionKey) {
182 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionInstall', array($extensionKey, $this));
183 }
184
185 /**
186 * Emits a signal after an extension has been uninstalled
187 *
188 * @param string $extensionKey
189 */
190 protected function emitAfterExtensionUninstallSignal($extensionKey) {
191 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionUninstall', array($extensionKey, $this));
192 }
193
194 /**
195 * Checks if an extension is available in the system
196 *
197 * @param $extensionKey
198 * @return bool
199 */
200 public function isAvailable($extensionKey) {
201 return $this->packageManager->isPackageAvailable($extensionKey);
202 }
203
204 /**
205 * Fetch additional information for an extension key
206 *
207 * @param string $extensionKey
208 * @access private
209 * @return array
210 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
211 */
212 public function enrichExtensionWithDetails($extensionKey) {
213 $availableExtensions = $this->listUtility->getAvailableExtensions();
214 if (isset($availableExtensions[$extensionKey])) {
215 $extension = $availableExtensions[$extensionKey];
216 } else {
217 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1342864081);
218 }
219 $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation(array($extensionKey => $extension));
220
221 if (!isset($availableAndInstalledExtensions[$extensionKey])) {
222 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
223 'Please check your uploaded extension "' . $extensionKey . '". The configuration file "ext_emconf.php" seems to be invalid.',
224 1391432222
225 );
226 }
227
228 return $availableAndInstalledExtensions[$extensionKey];
229 }
230
231 /**
232 * Creates directories as requested in ext_emconf.php
233 *
234 * @param array $extension
235 */
236 protected function ensureConfiguredDirectoriesExist(array $extension) {
237 $this->fileHandlingUtility->ensureConfiguredDirectoriesExist($extension);
238 }
239
240 /**
241 * Gets the content of the ext_tables.sql and ext_tables_static+adt.sql files
242 * Additionally adds the table definitions for the cache tables
243 *
244 * @param array $extension
245 */
246 public function processDatabaseUpdates(array $extension) {
247 $extTablesSqlFile = PATH_site . $extension['siteRelPath'] . 'ext_tables.sql';
248 $extTablesSqlContent = '';
249 if (file_exists($extTablesSqlFile)) {
250 $extTablesSqlContent .= \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($extTablesSqlFile);
251 }
252 if ($extTablesSqlContent !== '') {
253 $this->updateDbWithExtTablesSql($extTablesSqlContent);
254 }
255
256 $this->importStaticSqlFile($extension['siteRelPath']);
257 $this->importT3DFile($extension['siteRelPath']);
258 }
259
260 /**
261 * Gets all database updates due to runtime configuration, like caching framework or
262 * category api for example
263 *
264 * @param string $extensionKey
265 */
266 protected function processRuntimeDatabaseUpdates($extensionKey) {
267 $sqlString = $this->emitTablesDefinitionIsBeingBuiltSignal($extensionKey);
268 if (!empty($sqlString)) {
269 $this->updateDbWithExtTablesSql(implode(LF . LF . LF . LF, $sqlString));
270 }
271 }
272
273 /**
274 * Emits a signal to manipulate the tables definitions
275 *
276 * @param string $extensionKey
277 * @return mixed
278 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
279 */
280 protected function emitTablesDefinitionIsBeingBuiltSignal($extensionKey) {
281 $signalReturn = $this->signalSlotDispatcher->dispatch(__CLASS__, 'tablesDefinitionIsBeingBuilt', array(array(), $extensionKey));
282 // This is important to support old associated returns
283 $signalReturn = array_values($signalReturn);
284 $sqlString = $signalReturn[0];
285 if (!is_array($sqlString)) {
286 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
287 sprintf(
288 'The signal %s of class %s returned a value of type %s, but array was expected.',
289 'tablesDefinitionIsBeingBuilt',
290 __CLASS__,
291 gettype($sqlString)
292 ),
293 1382360258
294 );
295 }
296 return $sqlString;
297 }
298
299 /**
300 * Reload Cache files and Typo3LoadedExtensions
301 *
302 * @return void
303 */
304 public function reloadCaches() {
305 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtLocalconf(FALSE);
306 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(FALSE);
307 }
308
309 /**
310 * Save default configuration of an extension
311 *
312 * @param string $extensionKey
313 * @return void
314 */
315 protected function saveDefaultConfiguration($extensionKey) {
316 /** @var $configUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
317 $configUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
318 $configUtility->saveDefaultConfiguration($extensionKey);
319 }
320
321 /**
322 * Update database / process db updates from ext_tables
323 *
324 * @param string $rawDefinitions The raw SQL statements from ext_tables.sql
325 * @return void
326 */
327 public function updateDbWithExtTablesSql($rawDefinitions) {
328 $fieldDefinitionsFromFile = $this->installToolSqlParser->getFieldDefinitions_fileContent($rawDefinitions);
329 if (count($fieldDefinitionsFromFile)) {
330 $fieldDefinitionsFromCurrentDatabase = $this->installToolSqlParser->getFieldDefinitions_database();
331 $diff = $this->installToolSqlParser->getDatabaseExtra($fieldDefinitionsFromFile, $fieldDefinitionsFromCurrentDatabase);
332 $updateStatements = $this->installToolSqlParser->getUpdateSuggestions($diff);
333 $db = $this->getDatabaseConnection();
334 foreach ((array)$updateStatements['add'] as $string) {
335 $db->admin_query($string);
336 }
337 foreach ((array)$updateStatements['change'] as $string) {
338 $db->admin_query($string);
339 }
340 foreach ((array)$updateStatements['create_table'] as $string) {
341 $db->admin_query($string);
342 }
343 }
344 }
345
346 /**
347 * Import static SQL data (normally used for ext_tables_static+adt.sql)
348 *
349 * @param string $rawDefinitions
350 * @return void
351 */
352 public function importStaticSql($rawDefinitions) {
353 $statements = $this->installToolSqlParser->getStatementarray($rawDefinitions, 1);
354 list($statementsPerTable, $insertCount) = $this->installToolSqlParser->getCreateTables($statements, 1);
355 $db = $this->getDatabaseConnection();
356 // Traverse the tables
357 foreach ($statementsPerTable as $table => $query) {
358 $db->admin_query('DROP TABLE IF EXISTS ' . $table);
359 $db->admin_query($query);
360 if ($insertCount[$table]) {
361 $insertStatements = $this->installToolSqlParser->getTableInsertStatements($statements, $table);
362 foreach ($insertStatements as $statement) {
363 $db->admin_query($statement);
364 }
365 }
366 }
367 }
368
369 /**
370 * Remove an extension (delete the directory)
371 *
372 * @param string $extension
373 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
374 * @return void
375 */
376 public function removeExtension($extension) {
377 $absolutePath = $this->fileHandlingUtility->getAbsoluteExtensionPath($extension);
378 if ($this->fileHandlingUtility->isValidExtensionPath($absolutePath)) {
379 if ($this->packageManager->isPackageAvailable($extension)) {
380 // Package manager deletes the extension and removes the entry from PackageStates.php
381 $this->packageManager->deletePackage($extension);
382 } else {
383 // The extension is not listed in PackageStates.php, we can safely remove it
384 $this->fileHandlingUtility->removeDirectory($absolutePath);
385 }
386 } else {
387 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('No valid extension path given.', 1342875724);
388 }
389 }
390
391 /**
392 * Get the data dump for an extension
393 *
394 * @param string $extension
395 * @return array
396 */
397 public function getExtensionSqlDataDump($extension) {
398 $extension = $this->enrichExtensionWithDetails($extension);
399 $filePrefix = PATH_site . $extension['siteRelPath'];
400 $sqlData['extTables'] = $this->getSqlDataDumpForFile($filePrefix . 'ext_tables.sql');
401 $sqlData['staticSql'] = $this->getSqlDataDumpForFile($filePrefix . 'ext_tables_static+adt.sql');
402 return $sqlData;
403 }
404
405 /**
406 * Gets the sql data dump for a specific sql file (for example ext_tables.sql)
407 *
408 * @param string $sqlFile
409 * @return string
410 */
411 protected function getSqlDataDumpForFile($sqlFile) {
412 $sqlData = '';
413 if (file_exists($sqlFile)) {
414 $sqlContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($sqlFile);
415 $fieldDefinitions = $this->installToolSqlParser->getFieldDefinitions_fileContent($sqlContent);
416 $sqlData = $this->databaseUtility->dumpStaticTables($fieldDefinitions);
417 }
418 return $sqlData;
419 }
420
421 /**
422 * Checks if an update for an extension is available
423 *
424 * @internal
425 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extensionData
426 * @return bool
427 */
428 public function isUpdateAvailable(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extensionData) {
429 $isUpdateAvailable = FALSE;
430 // Only check for update for TER extensions
431 $version = $extensionData->getIntegerVersion();
432 /** @var $highestTerVersionExtension \TYPO3\CMS\Extensionmanager\Domain\Model\Extension */
433 $highestTerVersionExtension = $this->extensionRepository->findHighestAvailableVersion($extensionData->getExtensionKey());
434 if ($highestTerVersionExtension instanceof \TYPO3\CMS\Extensionmanager\Domain\Model\Extension) {
435 $highestVersion = $highestTerVersionExtension->getIntegerVersion();
436 if ($highestVersion > $version) {
437 $this->dependencyUtility->checkDependencies($highestTerVersionExtension);
438 if (!$this->dependencyUtility->hasDependencyErrors()) {
439 $isUpdateAvailable = TRUE;
440 }
441 }
442 }
443 return $isUpdateAvailable;
444 }
445
446 /**
447 * Uses the export import extension to import a T3D or XML file to PID 0
448 * Execution state is saved in the this->registry, so it only happens once
449 *
450 * @param string $extensionSiteRelPath
451 * @return void
452 */
453 protected function importT3DFile($extensionSiteRelPath) {
454 $registryKeysToCheck = array(
455 $extensionSiteRelPath . 'Initialisation/data.t3d',
456 $extensionSiteRelPath . 'Initialisation/dataImported',
457 );
458 foreach ($registryKeysToCheck as $registryKeyToCheck) {
459 if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
460 // Data was imported before => early return
461 return;
462 }
463 }
464 $importFileToUse = NULL;
465 $possibleImportFiles = array(
466 $extensionSiteRelPath . 'Initialisation/data.t3d',
467 $extensionSiteRelPath . 'Initialisation/data.xml'
468 );
469 foreach ($possibleImportFiles as $possibleImportFile) {
470 if (!file_exists(PATH_site . $possibleImportFile)) {
471 continue;
472 }
473 $importFileToUse = $possibleImportFile;
474 }
475 if ($importFileToUse !== NULL) {
476 /** @var ImportExportUtility $importExportUtility */
477 $importExportUtility = $this->objectManager->get(\TYPO3\CMS\Impexp\Utility\ImportExportUtility::class);
478 try {
479 $importResult = $importExportUtility->importT3DFile(PATH_site . $importFileToUse, 0);
480 $this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
481 $this->emitAfterExtensionT3DImportSignal($importFileToUse, $importResult);
482 } catch (\ErrorException $e) {
483 /** @var \TYPO3\CMS\Core\Log\Logger $logger */
484 $logger = $this->objectManager->get(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
485 $logger->log(\TYPO3\CMS\Core\Log\LogLevel::WARNING, $e->getMessage());
486 }
487 }
488 }
489
490 /**
491 * Emits a signal after an t3d file was imported
492 *
493 * @param string $importFileToUse
494 * @param int $importResult
495 */
496 protected function emitAfterExtensionT3DImportSignal($importFileToUse, $importResult) {
497 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionT3DImport', array($importFileToUse, $importResult, $this));
498 }
499
500 /**
501 * Imports a static tables SQL File (ext_tables_static+adt)
502 * Execution state is saved in the this->registry, so it only happens once
503 *
504 * @param string $extensionSiteRelPath
505 * @return void
506 */
507 protected function importStaticSqlFile($extensionSiteRelPath) {
508 $extTablesStaticSqlRelFile = $extensionSiteRelPath . 'ext_tables_static+adt.sql';
509 if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
510 $extTablesStaticSqlFile = PATH_site . $extTablesStaticSqlRelFile;
511 if (file_exists($extTablesStaticSqlFile)) {
512 $extTablesStaticSqlContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($extTablesStaticSqlFile);
513 $this->importStaticSql($extTablesStaticSqlContent);
514 }
515 $this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, 1);
516 $this->emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile);
517 }
518 }
519
520 /**
521 * Emits a signal after a static sql file was imported
522 *
523 * @param string $extTablesStaticSqlRelFile
524 */
525 protected function emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile) {
526 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionStaticSqlImport', array($extTablesStaticSqlRelFile, $this));
527 }
528
529 /**
530 * Imports files from Initialisation/Files to fileadmin
531 * via lowlevel copy directory method
532 *
533 * @param string $extensionSiteRelPath relative path to extension dir
534 * @param string $extensionKey
535 */
536 protected function importInitialFiles($extensionSiteRelPath, $extensionKey) {
537 $importRelFolder = $extensionSiteRelPath . 'Initialisation/Files';
538 if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
539 $importFolder = PATH_site . $importRelFolder;
540 if (file_exists($importFolder)) {
541 $destinationRelPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey;
542 $destinationAbsolutePath = PATH_site . $destinationRelPath;
543 if (!file_exists($destinationAbsolutePath) &&
544 \TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
545 ) {
546 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($destinationAbsolutePath);
547 }
548 \TYPO3\CMS\Core\Utility\GeneralUtility::copyDirectory($importRelFolder, $destinationRelPath);
549 $this->registry->set('extensionDataImport', $importRelFolder, 1);
550 $this->emitAfterExtensionFileImportSignal($destinationAbsolutePath);
551 }
552 }
553 }
554
555 /**
556 * Emits a signal after extension files were imported
557 *
558 * @param string $destinationAbsolutePath
559 */
560 protected function emitAfterExtensionFileImportSignal($destinationAbsolutePath) {
561 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionFileImport', array($destinationAbsolutePath, $this));
562 }
563
564 /**
565 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
566 */
567 protected function getDatabaseConnection() {
568 return $GLOBALS['TYPO3_DB'];
569 }
570
571 }