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