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