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