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