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