[BUGFIX] ClearCacheOnLoad camelCased does not work
[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 (!empty($extension['clearcacheonload']) || !empty($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 // This is important to support old associated returns
259 $signalReturn = array_values($signalReturn);
260 $sqlString = $signalReturn[0];
261 if (!is_array($sqlString)) {
262 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
263 sprintf(
264 'The signal %s of class %s returned a value of type %s, but array was expected.',
265 'tablesDefinitionIsBeingBuilt',
266 __CLASS__,
267 gettype($sqlString)
268 ),
269 1382360258
270 );
271 }
272 return $sqlString;
273 }
274
275 /**
276 * Reload Cache files and Typo3LoadedExtensions
277 *
278 * @return void
279 */
280 public function reloadCaches() {
281 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtLocalconf(FALSE);
282 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(FALSE);
283 }
284
285 /**
286 * Save default configuration of an extension
287 *
288 * @param string $extensionKey
289 * @return void
290 */
291 protected function saveDefaultConfiguration($extensionKey) {
292 /** @var $configUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
293 $configUtility = $this->objectManager->get('TYPO3\\CMS\\Extensionmanager\\Utility\\ConfigurationUtility');
294 $configUtility->saveDefaultConfiguration($extensionKey);
295 }
296
297 /**
298 * Update database / process db updates from ext_tables
299 *
300 * @param string $rawDefinitions The raw SQL statements from ext_tables.sql
301 * @return void
302 */
303 public function updateDbWithExtTablesSql($rawDefinitions) {
304 $fieldDefinitionsFromFile = $this->installToolSqlParser->getFieldDefinitions_fileContent($rawDefinitions);
305 if (count($fieldDefinitionsFromFile)) {
306 $fieldDefinitionsFromCurrentDatabase = $this->installToolSqlParser->getFieldDefinitions_database();
307 $diff = $this->installToolSqlParser->getDatabaseExtra($fieldDefinitionsFromFile, $fieldDefinitionsFromCurrentDatabase);
308 $updateStatements = $this->installToolSqlParser->getUpdateSuggestions($diff);
309 $db = $this->getDatabaseConnection();
310 foreach ((array) $updateStatements['add'] as $string) {
311 $db->admin_query($string);
312 }
313 foreach ((array) $updateStatements['change'] as $string) {
314 $db->admin_query($string);
315 }
316 foreach ((array) $updateStatements['create_table'] as $string) {
317 $db->admin_query($string);
318 }
319 }
320 }
321
322 /**
323 * Import static SQL data (normally used for ext_tables_static+adt.sql)
324 *
325 * @param string $rawDefinitions
326 * @return void
327 */
328 public function importStaticSql($rawDefinitions) {
329 $statements = $this->installToolSqlParser->getStatementarray($rawDefinitions, 1);
330 list($statementsPerTable, $insertCount) = $this->installToolSqlParser->getCreateTables($statements, 1);
331 $db = $this->getDatabaseConnection();
332 // Traverse the tables
333 foreach ($statementsPerTable as $table => $query) {
334 $db->admin_query('DROP TABLE IF EXISTS ' . $table);
335 $db->admin_query($query);
336 if ($insertCount[$table]) {
337 $insertStatements = $this->installToolSqlParser->getTableInsertStatements($statements, $table);
338 foreach ($insertStatements as $statement) {
339 $db->admin_query($statement);
340 }
341 }
342 }
343 }
344
345 /**
346 * Remove an extension (delete the directory)
347 *
348 * @param string $extension
349 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
350 * @return void
351 */
352 public function removeExtension($extension) {
353 $absolutePath = $this->fileHandlingUtility->getAbsoluteExtensionPath($extension);
354 if ($this->fileHandlingUtility->isValidExtensionPath($absolutePath)) {
355 if ($this->packageManager->isPackageAvailable($extension)) {
356 // Package manager deletes the extension and removes the entry from PackageStates.php
357 $this->packageManager->deletePackage($extension);
358 } else {
359 // The extension is not listed in PackageStates.php, we can safely remove it
360 $this->fileHandlingUtility->removeDirectory($absolutePath);
361 }
362 } else {
363 throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('No valid extension path given.', 1342875724);
364 }
365 }
366
367 /**
368 * Get the data dump for an extension
369 *
370 * @param string $extension
371 * @return array
372 */
373 public function getExtensionSqlDataDump($extension) {
374 $extension = $this->enrichExtensionWithDetails($extension);
375 $filePrefix = PATH_site . $extension['siteRelPath'];
376 $sqlData['extTables'] = $this->getSqlDataDumpForFile($filePrefix . 'ext_tables.sql');
377 $sqlData['staticSql'] = $this->getSqlDataDumpForFile($filePrefix . 'ext_tables_static+adt.sql');
378 return $sqlData;
379 }
380
381 /**
382 * Gets the sql data dump for a specific sql file (for example ext_tables.sql)
383 *
384 * @param string $sqlFile
385 * @return string
386 */
387 protected function getSqlDataDumpForFile($sqlFile) {
388 $sqlData = '';
389 if (file_exists($sqlFile)) {
390 $sqlContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($sqlFile);
391 $fieldDefinitions = $this->installToolSqlParser->getFieldDefinitions_fileContent($sqlContent);
392 $sqlData = $this->databaseUtility->dumpStaticTables($fieldDefinitions);
393 }
394 return $sqlData;
395 }
396
397 /**
398 * Checks if an update for an extension is available
399 *
400 * @internal
401 * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extensionData
402 * @return boolean
403 */
404 public function isUpdateAvailable(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extensionData) {
405 $isUpdateAvailable = FALSE;
406 // Only check for update for TER extensions
407 $version = $extensionData->getIntegerVersion();
408 /** @var $highestTerVersionExtension \TYPO3\CMS\Extensionmanager\Domain\Model\Extension */
409 $highestTerVersionExtension = $this->extensionRepository->findHighestAvailableVersion($extensionData->getExtensionKey());
410 if ($highestTerVersionExtension instanceof \TYPO3\CMS\Extensionmanager\Domain\Model\Extension) {
411 $highestVersion = $highestTerVersionExtension->getIntegerVersion();
412 if ($highestVersion > $version) {
413 $this->dependencyUtility->checkDependencies($highestTerVersionExtension);
414 if (!$this->dependencyUtility->hasDependencyErrors()) {
415 $isUpdateAvailable = TRUE;
416 }
417 }
418 }
419 return $isUpdateAvailable;
420 }
421
422 /**
423 * Uses the export import extension to import a T3D or XML file to PID 0
424 * Execution state is saved in the this->registry, so it only happens once
425 *
426 * @param string $extensionSiteRelPath
427 * @return void
428 */
429 protected function importT3DFile($extensionSiteRelPath) {
430 $registryKeysToCheck = array(
431 $extensionSiteRelPath . 'Initialisation/data.t3d',
432 $extensionSiteRelPath . 'Initialisation/dataImported',
433 );
434 foreach ($registryKeysToCheck as $registryKeyToCheck) {
435 if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
436 // Data was imported before => early return
437 return;
438 }
439 }
440 $importFileToUse = NULL;
441 $possibleImportFiles = array(
442 $extensionSiteRelPath . 'Initialisation/data.t3d',
443 $extensionSiteRelPath . 'Initialisation/data.xml'
444 );
445 foreach ($possibleImportFiles as $possibleImportFile) {
446 if (!file_exists(PATH_site . $possibleImportFile)) {
447 continue;
448 }
449 $importFileToUse = $possibleImportFile;
450 }
451 if ($importFileToUse !== NULL) {
452 $importExportUtility = $this->objectManager->get('TYPO3\\CMS\\Impexp\\Utility\\ImportExportUtility');
453 try {
454 $importResult = $importExportUtility->importT3DFile(PATH_site . $importFileToUse, 0);
455 $this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
456 $this->emitAfterExtensionT3DImportSignal($importFileToUse, $importResult);
457 } catch (\ErrorException $e) {
458 /** @var \TYPO3\CMS\Core\Log\Logger $logger */
459 $logger = $this->objectManager->get('TYPO3\\CMS\\Core\\Log\\LogManager')->getLogger(__CLASS__);
460 $logger->log(\TYPO3\CMS\Core\Log\LogLevel::WARNING, $e->getMessage());
461 }
462 }
463 }
464
465 /**
466 * Emits a signal after an t3d file was imported
467 *
468 * @param string $importFileToUse
469 * @param array $importResult
470 */
471 protected function emitAfterExtensionT3DImportSignal($importFileToUse, array $importResult) {
472 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionT3DImport', array($importFileToUse, $importResult, $this));
473 }
474
475 /**
476 * Imports a static tables SQL File (ext_tables_static+adt)
477 * Execution state is saved in the this->registry, so it only happens once
478 *
479 * @param string $extensionSiteRelPath
480 * @return void
481 */
482 protected function importStaticSqlFile($extensionSiteRelPath) {
483 $extTablesStaticSqlRelFile = $extensionSiteRelPath . 'ext_tables_static+adt.sql';
484 if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
485 $extTablesStaticSqlFile = PATH_site . $extTablesStaticSqlRelFile;
486 if (file_exists($extTablesStaticSqlFile)) {
487 $extTablesStaticSqlContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($extTablesStaticSqlFile);
488 $this->importStaticSql($extTablesStaticSqlContent);
489 }
490 $this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, 1);
491 $this->emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile);
492 }
493 }
494
495 /**
496 * Emits a signal after a static sql file was imported
497 *
498 * @param string $extTablesStaticSqlRelFile
499 */
500 protected function emitAfterExtensionStaticSqlImportSignal($extTablesStaticSqlRelFile) {
501 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionStaticSqlImport', array($extTablesStaticSqlRelFile, $this));
502 }
503
504 /**
505 * Imports files from Initialisation/Files to fileadmin
506 * via lowlevel copy directory method
507 *
508 * @param string $extensionSiteRelPath relative path to extension dir
509 * @param string $extensionKey
510 */
511 protected function importInitialFiles($extensionSiteRelPath, $extensionKey) {
512 $importRelFolder = $extensionSiteRelPath . 'Initialisation/Files';
513 if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
514 $importFolder = PATH_site . $importRelFolder;
515 if (file_exists($importFolder)) {
516 $destinationRelPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey;
517 $destinationAbsolutePath = PATH_site . $destinationRelPath;
518 if (!file_exists($destinationAbsolutePath) &&
519 \TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
520 ) {
521 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($destinationAbsolutePath);
522 }
523 \TYPO3\CMS\Core\Utility\GeneralUtility::copyDirectory($importRelFolder, $destinationRelPath);
524 $this->registry->set('extensionDataImport', $importRelFolder, 1);
525 $this->emitAfterExtensionFileImportSignal($destinationAbsolutePath);
526 }
527 }
528 }
529
530 /**
531 * Emits a signal after extension files were imported
532 *
533 * @param string $destinationAbsolutePath
534 */
535 protected function emitAfterExtensionFileImportSignal($destinationAbsolutePath) {
536 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterExtensionFileImport', array($destinationAbsolutePath, $this));
537 }
538
539 /**
540 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
541 */
542 protected function getDatabaseConnection() {
543 return $GLOBALS['TYPO3_DB'];
544 }
545 }