Passed
Push — master ( fcbfb0...64542d )
by
unknown
13:36
created

InstallUtility::getUpdateableVersion()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 3
nop 1
dl 0
loc 24
rs 9.5222
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extensionmanager\Utility;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use Symfony\Component\Finder\Finder;
22
use TYPO3\CMS\Core\Cache\CacheManager;
23
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
24
use TYPO3\CMS\Core\Configuration\SiteConfiguration;
25
use TYPO3\CMS\Core\Core\Bootstrap;
26
use TYPO3\CMS\Core\Core\Environment;
27
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
28
use TYPO3\CMS\Core\Database\Schema\SqlReader;
29
use TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent;
30
use TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent;
31
use TYPO3\CMS\Core\Package\PackageManager;
32
use TYPO3\CMS\Core\Registry;
33
use TYPO3\CMS\Core\Service\OpcodeCacheService;
34
use TYPO3\CMS\Core\SingletonInterface;
35
use TYPO3\CMS\Core\Site\Entity\Site;
36
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
39
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionDatabaseContentHasBeenImportedEvent;
40
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionFilesHaveBeenImportedEvent;
41
use TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenImportedEvent;
42
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
43
use TYPO3\CMS\Impexp\Import;
44
use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
45
use TYPO3\CMS\Install\Service\LateBootService;
46
47
/**
48
 * Extension Manager Install Utility
49
 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
50
 */
51
class InstallUtility implements SingletonInterface, LoggerAwareInterface
52
{
53
    use LoggerAwareTrait;
54
55
    /**
56
     * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
57
     */
58
    protected $fileHandlingUtility;
59
60
    /**
61
     * @var \TYPO3\CMS\Extensionmanager\Utility\ListUtility
62
     */
63
    protected $listUtility;
64
65
    /**
66
     * @var \TYPO3\CMS\Core\Package\PackageManager
67
     */
68
    protected $packageManager;
69
70
    /**
71
     * @var \TYPO3\CMS\Core\Cache\CacheManager
72
     */
73
    protected $cacheManager;
74
75
    /**
76
     * @var \TYPO3\CMS\Core\Registry
77
     */
78
    protected $registry;
79
80
    /**
81
     * @var EventDispatcherInterface
82
     */
83
    protected $eventDispatcher;
84
85
    /**
86
     * @var LateBootService
87
     */
88
    protected $lateBootService;
89
90
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
91
    {
92
        $this->eventDispatcher = $eventDispatcher;
93
    }
94
95
    /**
96
     * @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
97
     */
98
    public function injectFileHandlingUtility(FileHandlingUtility $fileHandlingUtility)
99
    {
100
        $this->fileHandlingUtility = $fileHandlingUtility;
101
    }
102
103
    /**
104
     * @param \TYPO3\CMS\Extensionmanager\Utility\ListUtility $listUtility
105
     */
106
    public function injectListUtility(ListUtility $listUtility)
107
    {
108
        $this->listUtility = $listUtility;
109
    }
110
111
    /**
112
     * @param \TYPO3\CMS\Core\Package\PackageManager $packageManager
113
     */
114
    public function injectPackageManager(PackageManager $packageManager)
115
    {
116
        $this->packageManager = $packageManager;
117
    }
118
119
    /**
120
     * @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
121
     */
122
    public function injectCacheManager(CacheManager $cacheManager)
123
    {
124
        $this->cacheManager = $cacheManager;
125
    }
126
127
    /**
128
     * @param \TYPO3\CMS\Core\Registry $registry
129
     */
130
    public function injectRegistry(Registry $registry)
131
    {
132
        $this->registry = $registry;
133
    }
134
135
    /**
136
     * @param  LateBootService $lateBootService
137
     */
138
    public function injectLateBootService(LateBootService $lateBootService)
139
    {
140
        $this->lateBootService = $lateBootService;
141
    }
142
143
    /**
144
     * Helper function to install an extension
145
     * also processes db updates and clears the cache if the extension asks for it
146
     *
147
     * @param string ...$extensionKeys
148
     * @throws ExtensionManagerException
149
     */
150
    public function install(...$extensionKeys)
151
    {
152
        $flushCaches = false;
153
        foreach ($extensionKeys as $extensionKey) {
154
            $this->loadExtension($extensionKey);
155
            $extension = $this->enrichExtensionWithDetails($extensionKey, false);
156
            $this->saveDefaultConfiguration($extensionKey);
157
            if (!empty($extension['clearcacheonload']) || !empty($extension['clearCacheOnLoad'])) {
158
                $flushCaches = true;
159
            }
160
        }
161
162
        if ($flushCaches) {
163
            $this->cacheManager->flushCaches();
164
        } else {
165
            $this->cacheManager->flushCachesInGroup('system');
166
        }
167
168
        // Load a new container as reloadCaches will load ext_localconf
169
        $container = $this->lateBootService->getContainer();
170
        $backup = $this->lateBootService->makeCurrent($container);
171
172
        $this->reloadCaches();
173
        $this->updateDatabase();
174
175
        foreach ($extensionKeys as $extensionKey) {
176
            $this->processExtensionSetup($extensionKey);
177
            $container->get(EventDispatcherInterface::class)->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this));
178
        }
179
180
        // Reset to the original container instance
181
        $this->lateBootService->makeCurrent(null, $backup);
182
    }
183
184
    /**
185
     * @param string $extensionKey
186
     */
187
    public function processExtensionSetup(string $extensionKey): void
188
    {
189
        $extension = $this->enrichExtensionWithDetails($extensionKey, false);
190
        $this->importInitialFiles($extension['siteRelPath'] ?? '', $extensionKey);
191
        $this->importStaticSqlFile($extensionKey, $extension['siteRelPath']);
192
        $import = $this->importT3DFile($extensionKey, $extension['siteRelPath']);
193
        $this->importSiteConfiguration($extension['siteRelPath'], $import);
194
    }
195
196
    /**
197
     * Helper function to uninstall an extension
198
     *
199
     * @param string $extensionKey
200
     * @throws ExtensionManagerException
201
     */
202
    public function uninstall($extensionKey)
203
    {
204
        $dependentExtensions = $this->findInstalledExtensionsThatDependOnExtension((string)$extensionKey);
205
        if (!empty($dependentExtensions)) {
206
            throw new ExtensionManagerException(
207
                LocalizationUtility::translate(
208
                    'extensionList.uninstall.dependencyError',
209
                    'extensionmanager',
210
                    [$extensionKey, implode(',', $dependentExtensions)]
211
                ) ?? '',
212
                1342554622
213
            );
214
        }
215
        $this->unloadExtension($extensionKey);
216
    }
217
218
    /**
219
     * Find installed extensions which depend on the given extension.
220
     * This is used at extension uninstall to stop the process if an installed
221
     * extension depends on the extension to be uninstalled.
222
     *
223
     * @param string $extensionKey
224
     * @return array
225
     */
226
    protected function findInstalledExtensionsThatDependOnExtension(string $extensionKey): array
227
    {
228
        $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation();
229
        $dependentExtensions = [];
230
        foreach ($availableAndInstalledExtensions as $availableAndInstalledExtensionKey => $availableAndInstalledExtension) {
231
            if (isset($availableAndInstalledExtension['installed']) && $availableAndInstalledExtension['installed'] === true) {
232
                if (is_array($availableAndInstalledExtension['constraints']) && is_array($availableAndInstalledExtension['constraints']['depends']) && array_key_exists($extensionKey, $availableAndInstalledExtension['constraints']['depends'])) {
233
                    $dependentExtensions[] = $availableAndInstalledExtensionKey;
234
                }
235
            }
236
        }
237
        return $dependentExtensions;
238
    }
239
240
    /**
241
     * Reset and reload the available extensions
242
     */
243
    public function reloadAvailableExtensions()
244
    {
245
        $this->listUtility->reloadAvailableExtensions();
246
    }
247
248
    /**
249
     * Wrapper function for loading extensions
250
     *
251
     * @param string $extensionKey
252
     */
253
    protected function loadExtension($extensionKey)
254
    {
255
        $this->packageManager->activatePackage($extensionKey);
256
    }
257
258
    /**
259
     * Wrapper function for unloading extensions
260
     *
261
     * @param string $extensionKey
262
     */
263
    protected function unloadExtension($extensionKey)
264
    {
265
        $this->packageManager->deactivatePackage($extensionKey);
266
        $this->eventDispatcher->dispatch(new AfterPackageDeactivationEvent($extensionKey, 'typo3-cms-extension', $this));
267
        $this->cacheManager->flushCachesInGroup('system');
268
    }
269
270
    /**
271
     * Checks if an extension is available in the system
272
     *
273
     * @param string $extensionKey
274
     * @return bool
275
     */
276
    public function isAvailable($extensionKey)
277
    {
278
        return $this->packageManager->isPackageAvailable($extensionKey);
279
    }
280
281
    /**
282
     * Reloads the package information, if the package is already registered
283
     *
284
     * @param string $extensionKey
285
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException if the package isn't available
286
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
287
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException if an invalid package path was passed
288
     * @throws \TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException if no extension configuration file could be found
289
     */
290
    public function reloadPackageInformation($extensionKey)
291
    {
292
        if ($this->packageManager->isPackageAvailable($extensionKey)) {
293
            $this->reloadOpcache();
294
            $this->packageManager->reloadPackageInformation($extensionKey);
295
        }
296
    }
297
298
    /**
299
     * Fetch additional information for an extension key
300
     *
301
     * @param string $extensionKey
302
     * @param bool $loadTerInformation
303
     * @return array
304
     * @throws ExtensionManagerException
305
     * @internal
306
     */
307
    public function enrichExtensionWithDetails($extensionKey, $loadTerInformation = true)
308
    {
309
        $extension = $this->getExtensionArray($extensionKey);
310
        if (!$loadTerInformation) {
311
            $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfInformation([$extensionKey => $extension]);
312
        } else {
313
            $availableAndInstalledExtensions = $this->listUtility->enrichExtensionsWithEmConfAndTerInformation([$extensionKey => $extension]);
314
        }
315
316
        if (!isset($availableAndInstalledExtensions[$extensionKey])) {
317
            throw new ExtensionManagerException(
318
                'Please check your uploaded extension "' . $extensionKey . '". The configuration file "ext_emconf.php" seems to be invalid.',
319
                1391432222
320
            );
321
        }
322
323
        return $availableAndInstalledExtensions[$extensionKey];
324
    }
325
326
    /**
327
     * @param string $extensionKey
328
     * @return array
329
     * @throws ExtensionManagerException
330
     */
331
    protected function getExtensionArray($extensionKey)
332
    {
333
        $availableExtensions = $this->listUtility->getAvailableExtensions();
334
        if (isset($availableExtensions[$extensionKey])) {
335
            return $availableExtensions[$extensionKey];
336
        }
337
        throw new ExtensionManagerException('Extension ' . $extensionKey . ' is not available', 1342864081);
338
    }
339
340
    /**
341
     * Reload Cache files and Typo3LoadedExtensions
342
     */
343
    public function reloadCaches()
344
    {
345
        $this->reloadOpcache();
346
        ExtensionManagementUtility::loadExtLocalconf(false);
347
        Bootstrap::loadBaseTca(false);
348
        Bootstrap::loadExtTables(false);
349
    }
350
351
    /**
352
     * Reloads PHP opcache
353
     */
354
    protected function reloadOpcache()
355
    {
356
        GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
357
    }
358
359
    /**
360
     * Executes all safe database statements.
361
     * Tables and fields are created and altered. Nothing gets deleted or renamed here.
362
     */
363
    protected function updateDatabase()
364
    {
365
        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
366
        $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
367
        $sqlStatements = [];
368
        $sqlStatements[] = $sqlReader->getTablesDefinitionString();
369
        $sqlStatements = $sqlReader->getCreateTableStatementArray(implode(LF . LF, array_filter($sqlStatements)));
370
        $updateStatements = $schemaMigrator->getUpdateSuggestions($sqlStatements);
371
372
        $updateStatements = array_merge_recursive(...array_values($updateStatements));
373
        $selectedStatements = [];
374
        foreach (['add', 'change', 'create_table', 'change_table'] as $action) {
375
            if (empty($updateStatements[$action])) {
376
                continue;
377
            }
378
379
            $statements = array_combine(array_keys($updateStatements[$action]), array_fill(0, count($updateStatements[$action]), true));
380
            $statements = is_array($statements) ? $statements : [];
381
            $selectedStatements = array_merge(
382
                $selectedStatements,
383
                $statements
384
            );
385
        }
386
387
        $schemaMigrator->migrate($sqlStatements, $selectedStatements);
388
    }
389
390
    /**
391
     * Save default configuration of an extension
392
     *
393
     * @param string $extensionKey
394
     */
395
    protected function saveDefaultConfiguration($extensionKey)
396
    {
397
        $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
398
        $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfiguration($extensionKey);
399
    }
400
401
    /**
402
     * Import static SQL data (normally used for ext_tables_static+adt.sql)
403
     *
404
     * @param string $rawDefinitions
405
     */
406
    public function importStaticSql($rawDefinitions)
407
    {
408
        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
409
        $statements = $sqlReader->getStatementArray($rawDefinitions);
410
411
        $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
412
        $schemaMigrationService->importStaticData($statements, true);
413
    }
414
415
    /**
416
     * Remove an extension (delete the directory)
417
     *
418
     * @param string $extension
419
     * @throws ExtensionManagerException
420
     */
421
    public function removeExtension($extension)
422
    {
423
        $absolutePath = $this->fileHandlingUtility->getAbsoluteExtensionPath($extension);
424
        if ($this->fileHandlingUtility->isValidExtensionPath($absolutePath)) {
425
            if ($this->packageManager->isPackageAvailable($extension)) {
426
                // Package manager deletes the extension and removes the entry from PackageStates.php
427
                $this->packageManager->deletePackage($extension);
428
            } else {
429
                // The extension is not listed in PackageStates.php, we can safely remove it
430
                $this->fileHandlingUtility->removeDirectory($absolutePath);
431
            }
432
        } else {
433
            throw new ExtensionManagerException('No valid extension path given.', 1342875724);
434
        }
435
    }
436
437
    /**
438
     * Uses the export import extension to import a T3D or XML file to PID 0
439
     * Execution state is saved in the this->registry, so it only happens once
440
     *
441
     * @param string $extensionKey
442
     * @param string $extensionSiteRelPath
443
     * @return Import|null
444
     */
445
    protected function importT3DFile($extensionKey, $extensionSiteRelPath): ?Import
446
    {
447
        $registryKeysToCheck = [
448
            $extensionSiteRelPath . 'Initialisation/data.t3d',
449
            $extensionSiteRelPath . 'Initialisation/dataImported',
450
        ];
451
        foreach ($registryKeysToCheck as $registryKeyToCheck) {
452
            if ($this->registry->get('extensionDataImport', $registryKeyToCheck)) {
453
                // Data was imported before => early return
454
                return null;
455
            }
456
        }
457
        $importFileToUse = null;
458
        $possibleImportFiles = [
459
            $extensionSiteRelPath . 'Initialisation/data.t3d',
460
            $extensionSiteRelPath . 'Initialisation/data.xml'
461
        ];
462
        foreach ($possibleImportFiles as $possibleImportFile) {
463
            if (!file_exists(Environment::getPublicPath() . '/' . $possibleImportFile)) {
464
                continue;
465
            }
466
            $importFileToUse = $possibleImportFile;
467
        }
468
        if ($importFileToUse !== null) {
469
            $importExportUtility = GeneralUtility::makeInstance(ImportExportUtility::class);
470
            try {
471
                $importResult = $importExportUtility->importT3DFile(Environment::getPublicPath() . '/' . $importFileToUse, 0);
472
                $this->registry->set('extensionDataImport', $extensionSiteRelPath . 'Initialisation/dataImported', 1);
473
                $this->eventDispatcher->dispatch(new AfterExtensionDatabaseContentHasBeenImportedEvent($extensionKey, $importFileToUse, $importResult, $this));
474
                return $importExportUtility->getImport();
475
            } catch (\ErrorException $e) {
476
                $this->logger->warning($e->getMessage(), ['exception' => $e]);
477
            }
478
        }
479
        return null;
480
    }
481
482
    /**
483
     * Imports a static tables SQL File (ext_tables_static+adt)
484
     * Execution state is saved in the this->registry, so it only happens once
485
     *
486
     * @param string $extensionKey
487
     * @param string $extensionSiteRelPath
488
     */
489
    protected function importStaticSqlFile(string $extensionKey, $extensionSiteRelPath)
490
    {
491
        $extTablesStaticSqlRelFile = $extensionSiteRelPath . 'ext_tables_static+adt.sql';
492
        if (!$this->registry->get('extensionDataImport', $extTablesStaticSqlRelFile)) {
493
            $extTablesStaticSqlFile = Environment::getPublicPath() . '/' . $extTablesStaticSqlRelFile;
494
            $shortFileHash = '';
495
            if (file_exists($extTablesStaticSqlFile)) {
496
                $extTablesStaticSqlContent = (string)file_get_contents($extTablesStaticSqlFile);
497
                $shortFileHash = md5($extTablesStaticSqlContent);
498
                $this->importStaticSql($extTablesStaticSqlContent);
499
            }
500
            $this->registry->set('extensionDataImport', $extTablesStaticSqlRelFile, $shortFileHash);
501
            $this->eventDispatcher->dispatch(new AfterExtensionStaticDatabaseContentHasBeenImportedEvent($extensionKey, $extTablesStaticSqlRelFile, $this));
502
        }
503
    }
504
505
    /**
506
     * Imports files from Initialisation/Files to fileadmin
507
     * via lowlevel copy directory method
508
     *
509
     * @param string $extensionSiteRelPath relative path to extension dir
510
     * @param string $extensionKey
511
     */
512
    protected function importInitialFiles($extensionSiteRelPath, $extensionKey)
513
    {
514
        $importRelFolder = $extensionSiteRelPath . 'Initialisation/Files';
515
        if (!$this->registry->get('extensionDataImport', $importRelFolder)) {
516
            $importFolder = Environment::getPublicPath() . '/' . $importRelFolder;
517
            if (file_exists($importFolder)) {
518
                $destinationRelPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . $extensionKey;
519
                $destinationAbsolutePath = Environment::getPublicPath() . '/' . $destinationRelPath;
520
                if (!file_exists($destinationAbsolutePath) &&
521
                    GeneralUtility::isAllowedAbsPath($destinationAbsolutePath)
522
                ) {
523
                    GeneralUtility::mkdir($destinationAbsolutePath);
524
                }
525
                GeneralUtility::copyDirectory($importRelFolder, $destinationRelPath);
526
                $this->registry->set('extensionDataImport', $importRelFolder, 1);
527
                $this->eventDispatcher->dispatch(new AfterExtensionFilesHaveBeenImportedEvent($extensionKey, $destinationAbsolutePath, $this));
528
            }
529
        }
530
    }
531
532
    /**
533
     * @param string $extensionSiteRelPath
534
     * @param Import|null $import
535
     */
536
    protected function importSiteConfiguration(string $extensionSiteRelPath, Import $import = null): void
537
    {
538
        $importRelFolder = $extensionSiteRelPath . 'Initialisation/Site';
539
        $importAbsFolder = Environment::getPublicPath() . '/' . $importRelFolder;
540
        $destinationFolder = Environment::getConfigPath() . '/sites';
541
542
        if (!is_dir($importAbsFolder)) {
543
            return;
544
        }
545
546
        $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class);
547
        $existingSites = $siteConfiguration->resolveAllExistingSites(false);
548
549
        GeneralUtility::mkdir($destinationFolder);
550
        $finder = GeneralUtility::makeInstance(Finder::class);
551
        $finder->directories()->in($importAbsFolder);
552
        if ($finder->hasResults()) {
553
            foreach ($finder as $siteConfigDirectory) {
554
                $siteIdentifier = $siteConfigDirectory->getBasename();
555
                if (isset($existingSites[$siteIdentifier])) {
556
                    $this->logger->warning(
557
                        sprintf(
558
                            'Skipped importing site configuration from %s due to existing site identifier %s',
559
                            $extensionSiteRelPath,
560
                            $siteIdentifier
561
                        )
562
                    );
563
                    continue;
564
                }
565
                $targetDir = $destinationFolder . '/' . $siteIdentifier;
566
                if (!$this->registry->get('siteConfigImport', $siteIdentifier) && !is_dir($targetDir)) {
567
                    GeneralUtility::mkdir($targetDir);
568
                    GeneralUtility::copyDirectory($siteConfigDirectory->getPathname(), $targetDir);
569
                    $this->registry->set('siteConfigImport', $siteIdentifier, 1);
570
                }
571
            }
572
        }
573
574
        /** @var Site[] $newSites */
575
        $newSites = array_diff_key($siteConfiguration->resolveAllExistingSites(false), $existingSites);
576
        $importedPages = $import->import_mapId['pages'] ?? null;
577
578
        foreach ($newSites as $newSite) {
579
            $exportedPageId = $newSite->getRootPageId();
580
            $importedPageId = $importedPages[$exportedPageId] ?? null;
581
            if ($importedPageId === null) {
582
                $this->logger->warning(
583
                    sprintf(
584
                        'Imported site configuration with identifier %s could not be mapped to imported page id',
585
                        $newSite->getIdentifier()
586
                    )
587
                );
588
                continue;
589
            }
590
            $configuration = $siteConfiguration->load($newSite->getIdentifier());
591
            $configuration['rootPageId'] = $importedPageId;
592
            $siteConfiguration->write($newSite->getIdentifier(), $configuration);
593
        }
594
    }
595
}
596