/** * Set the routes configuration for the Neos setup and configures the routing component * to skip initialisation, which would overwrite the specific settings again. * * @param ComponentContext $componentContext * @return void */ public function handle(ComponentContext $componentContext) { $configurationSource = $this->objectManager->get(\Neos\Flow\Configuration\Source\YamlSource::class); $routesConfiguration = $configurationSource->load($this->packageManager->getPackage('Neos.Setup')->getConfigurationPath() . ConfigurationManager::CONFIGURATION_TYPE_ROUTES); $this->router->setRoutesConfiguration($routesConfiguration); $componentContext->setParameter(\Neos\Flow\Mvc\Routing\RoutingComponent::class, 'skipRouterInitialization', true); }
public function initializeObject($reason) { if ($reason === ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED) { return; } /** @var Package $package */ foreach ($this->packageManager->getActivePackages() as $package) { foreach ($package->getNamespaces() as $namespace) { $this->addNamespace(strtolower($package->getPackageKey()), $namespace . '\\ViewHelpers'); } } }
/** * Kickstart a new site package * * This command generates a new site package with basic TypoScript and Sites.xml * * @param string $packageKey The packageKey for your site * @param string $siteName The siteName of your site * @return string */ public function siteCommand($packageKey, $siteName) { if (!$this->packageManager->isPackageKeyValid($packageKey)) { $this->outputLine('Package key "%s" is not valid. Only UpperCamelCase in the format "Vendor.PackageKey", please!', array($packageKey)); $this->quit(1); } if ($this->packageManager->isPackageAvailable($packageKey)) { $this->outputLine('Package "%s" already exists.', array($packageKey)); $this->quit(1); } $generatedFiles = $this->generatorService->generateSitePackage($packageKey, $siteName); $this->outputLine(implode(PHP_EOL, $generatedFiles)); }
/** * Fetches the site with the given name and exports it into XML in the given package. * * @param array<Site> $sites * @param boolean $tidy Whether to export formatted XML * @param string $packageKey Package key where the export output should be saved to * @param string $nodeTypeFilter Filter the node type of the nodes, allows complex expressions (e.g. "Neos.Neos:Page", "!Neos.Neos:Page,Neos.Neos:Text") * @return void * @throws NeosException */ public function exportToPackage(array $sites, $tidy = false, $packageKey, $nodeTypeFilter = null) { if (!$this->packageManager->isPackageActive($packageKey)) { throw new NeosException(sprintf('Error: Package "%s" is not active.', $packageKey), 1404375719); } $contentPathAndFilename = sprintf('resource://%s/Private/Content/Sites.xml', $packageKey); $this->resourcesPath = Files::concatenatePaths(array(dirname($contentPathAndFilename), 'Resources')); Files::createDirectoryRecursively($this->resourcesPath); $this->xmlWriter = new \XMLWriter(); $this->xmlWriter->openUri($contentPathAndFilename); $this->xmlWriter->setIndent($tidy); $this->exportSites($sites, $nodeTypeFilter); $this->xmlWriter->flush(); }
/** * Index action * * @return void */ public function indexAction() { $this->view->assign('flowPathRoot', realpath(FLOW_PATH_ROOT)); $this->view->assign('flowPathWeb', realpath(FLOW_PATH_WEB)); $this->view->assign('isMyPackageActive', $this->packageManager->isPackageActive('MyCompany.MyPackage')); $baseUri = $this->request->getHttpRequest()->getBaseUri(); $this->view->assign('baseUri', $baseUri); $this->view->assign('isWindows', DIRECTORY_SEPARATOR !== '/'); $flowPackage = $this->packageManager->getPackage('Neos.Flow'); $version = $flowPackage->getInstalledVersion(); $this->view->assign('version', $version); $activePackages = $this->packageManager->getActivePackages(); $this->view->assign('activePackages', $activePackages); $this->view->assign('notDevelopmentContext', !$this->objectManager->getContext()->isDevelopment()); }
/** * Flush all caches * * The flush command flushes all caches (including code caches) which have been * registered with Flow's Cache Manager. It also removes any session data. * * If fatal errors caused by a package prevent the compile time bootstrap * from running, the removal of any temporary data can be forced by specifying * the option <b>--force</b>. * * This command does not remove the precompiled data provided by frozen * packages unless the <b>--force</b> option is used. * * @param boolean $force Force flushing of any temporary data * @return void * @see neos.flow:cache:warmup * @see neos.flow:package:freeze * @see neos.flow:package:refreeze */ public function flushCommand($force = false) { // Internal note: the $force option is evaluated early in the Flow // bootstrap in order to reliably flush the temporary data before any // other code can cause fatal errors. $this->cacheManager->flushCaches(); $this->outputLine('Flushed all caches for "' . $this->bootstrap->getContext() . '" context.'); if ($this->lockManager->isSiteLocked()) { $this->lockManager->unlockSite(); } $frozenPackages = []; foreach (array_keys($this->packageManager->getActivePackages()) as $packageKey) { if ($this->packageManager->isPackageFrozen($packageKey)) { $frozenPackages[] = $packageKey; } } if ($frozenPackages !== []) { $this->outputFormatted(PHP_EOL . 'Please note that the following package' . (count($frozenPackages) === 1 ? ' is' : 's are') . ' currently frozen: ' . PHP_EOL); $this->outputFormatted(implode(PHP_EOL, $frozenPackages) . PHP_EOL, [], 2); $message = 'As code and configuration changes in these packages are not detected, the application may respond '; $message .= 'unexpectedly if modifications were done anyway or the remaining code relies on these changes.' . PHP_EOL . PHP_EOL; $message .= 'You may call <b>package:refreeze all</b> in order to refresh frozen packages or use the <b>--force</b> '; $message .= 'option of this <b>cache:flush</b> command to flush caches if Flow becomes unresponsive.' . PHP_EOL; $this->outputFormatted($message, [$frozenPackages]); } $this->sendAndExit(0); }
/** * Return the json array for a given locale, sourceCatalog, xliffPath and package. * The json will be cached. * * @param Locale $locale The locale * @return Result * @throws Exception */ public function getCachedJson(Locale $locale) { $cacheIdentifier = md5($locale); if ($this->xliffToJsonTranslationsCache->has($cacheIdentifier)) { $json = $this->xliffToJsonTranslationsCache->get($cacheIdentifier); } else { $labels = []; $localeChain = $this->localizationService->getLocaleChain($locale); foreach ($this->packagesRegisteredForAutoInclusion as $packageKey => $sourcesToBeIncluded) { if (!is_array($sourcesToBeIncluded)) { continue; } $translationBasePath = Files::concatenatePaths([$this->packageManager->getPackage($packageKey)->getResourcesPath(), $this->xliffBasePath]); // We merge labels in the chain from the worst choice to best choice foreach (array_reverse($localeChain) as $allowedLocale) { $localeSourcePath = Files::getNormalizedPath(Files::concatenatePaths([$translationBasePath, $allowedLocale])); foreach ($sourcesToBeIncluded as $sourceName) { foreach (glob($localeSourcePath . $sourceName . '.xlf') as $xliffPathAndFilename) { $xliffPathInfo = pathinfo($xliffPathAndFilename); $sourceName = str_replace($localeSourcePath, '', $xliffPathInfo['dirname'] . '/' . $xliffPathInfo['filename']); $labels = Arrays::arrayMergeRecursiveOverrule($labels, $this->parseXliffToArray($xliffPathAndFilename, $packageKey, $sourceName)); } } } } $json = json_encode($labels); $this->xliffToJsonTranslationsCache->set($cacheIdentifier, $json); } return $json; }
/** * Checks the syntax of the given $packageKey and quits with an error message if it's not valid * * @param string $packageKey * @return void */ protected function validatePackageKey($packageKey) { if (!$this->packageManager->isPackageKeyValid($packageKey)) { $this->outputLine('Package key "%s" is not valid. Only UpperCamelCase with alphanumeric characters in the format <VendorName>.<PackageKey>, please!', array($packageKey)); exit(1); } }
/** * Generate a documentation skeleton for the package key * * @param string $packageKey The package key * @return array An array of generated filenames */ public function generateDocumentation($packageKey) { $documentationPath = Files::concatenatePaths([$this->packageManager->getPackage($packageKey)->getPackagePath(), 'Documentation']); $contextVariables = array(); $contextVariables['packageKey'] = $packageKey; $templatePathAndFilename = 'resource://Neos.Kickstarter/Private/Generator/Documentation/conf.py'; $fileContent = $this->renderTemplate($templatePathAndFilename, $contextVariables); $targetPathAndFilename = $documentationPath . '/conf.py'; $this->generateFile($targetPathAndFilename, $fileContent); $templatePathAndFilename = 'resource://Neos.Kickstarter/Private/Generator/Documentation/Makefile'; $fileContent = file_get_contents($templatePathAndFilename); $targetPathAndFilename = $documentationPath . '/Makefile'; $this->generateFile($targetPathAndFilename, $fileContent); $templatePathAndFilename = 'resource://Neos.Kickstarter/Private/Generator/Documentation/index.rst'; $fileContent = $this->renderTemplate($templatePathAndFilename, $contextVariables); $targetPathAndFilename = $documentationPath . '/index.rst'; $this->generateFile($targetPathAndFilename, $fileContent); $targetPathAndFilename = $documentationPath . '/_build/.gitignore'; $this->generateFile($targetPathAndFilename, '*' . chr(10) . '!.gitignore' . chr(10)); $targetPathAndFilename = $documentationPath . '/_static/.gitignore'; $this->generateFile($targetPathAndFilename, '*' . chr(10) . '!.gitignore' . chr(10)); $targetPathAndFilename = $documentationPath . '/_templates/.gitignore'; $this->generateFile($targetPathAndFilename, '*' . chr(10) . '!.gitignore' . chr(10)); return $this->generatedFiles; }
/** * Import sites content * * This command allows for importing one or more sites or partial content from an XML source. The format must * be identical to that produced by the export command. * * If a filename is specified, this command expects the corresponding file to contain the XML structure. The * filename php://stdin can be used to read from standard input. * * If a package key is specified, this command expects a Sites.xml file to be located in the private resources * directory of the given package (Resources/Private/Content/Sites.xml). * * @param string $packageKey Package key specifying the package containing the sites content * @param string $filename relative path and filename to the XML file containing the sites content * @return void */ public function importCommand($packageKey = null, $filename = null) { $exceedingArguments = $this->request->getExceedingArguments(); if (isset($exceedingArguments[0]) && $packageKey === null && $filename === null) { if (file_exists($exceedingArguments[0])) { $filename = $exceedingArguments[0]; } elseif ($this->packageManager->isPackageAvailable($exceedingArguments[0])) { $packageKey = $exceedingArguments[0]; } } if ($packageKey === null && $filename === null) { $this->outputLine('You have to specify either "--package-key" or "--filename"'); $this->quit(1); } $site = null; if ($filename !== null) { try { $site = $this->siteImportService->importFromFile($filename); } catch (\Exception $exception) { $this->systemLogger->logException($exception); $this->outputLine('<error>During the import of the file "%s" an exception occurred: %s, see log for further information.</error>', array($filename, $exception->getMessage())); $this->quit(1); } } else { try { $site = $this->siteImportService->importFromPackage($packageKey); } catch (\Exception $exception) { $this->systemLogger->logException($exception); $this->outputLine('<error>During the import of the "Sites.xml" from the package "%s" an exception occurred: %s, see log for further information.</error>', array($packageKey, $exception->getMessage())); $this->quit(1); } } $this->outputLine('Import of site "%s" finished.', array($site->getName())); }
/** * Generate additional folders for site packages. * * @param string $packageKey */ protected function generateAdditionalFolders($packageKey) { $resourcesPath = $this->packageManager->getPackage($packageKey)->getResourcesPath(); $publicResourcesPath = Files::concatenatePaths(array($resourcesPath, 'Public')); foreach (array('Images', 'JavaScript', 'Styles') as $publicResourceFolder) { Files::createDirectoryRecursively(Files::concatenatePaths(array($publicResourcesPath, $publicResourceFolder))); } }
/** * @param string $packageKey * @return string|null */ protected function getPackagePrivateResourcesPath($packageKey) { if (!$this->packageManager->isPackageActive($packageKey)) { return null; } $packageResourcesPath = $this->packageManager->getPackage($packageKey)->getResourcesPath(); return Files::concatenatePaths([$packageResourcesPath, 'Private']); }
/** * @param PaletteInterface $proxy * @param $profilePath * @return \Imagine\Image\Profile */ protected function createAndSetProfileOnProxy(PaletteInterface $proxy, $profilePath) { if (substr($profilePath, 0, 11) !== 'resource://') { $profilePath = $this->packageManager->getPackage('imagine.imagine')->getPackagePath() . 'lib/Imagine/resources/' . $profilePath; } $profile = Profile::fromPath($profilePath); ObjectAccess::setProperty($proxy, 'profile', $profile, TRUE); return $profile; }
/** * Rescan package availability and recreates the PackageStates configuration. */ public function rescanCommand() { $packageStates = $this->packageManager->rescanPackages(); $this->outputLine('The following packages are registered and will be loaded in this order:'); $this->outputLine(''); foreach ($packageStates['packages'] as $composerName => $packageState) { $this->outputLine($composerName); } $this->outputLine(''); $this->outputLine('Package rescan successful.'); }
/** * @param string $packageKey * @return Error|Message */ protected function freezePackage($packageKey) { try { $this->packageManager->freezePackage($packageKey); $message = new Message('Package %s has been frozen', 1343231689, array($packageKey)); } catch (\LogicException $exception) { $message = new Error($exception->getMessage(), 1343231690); } catch (UnknownPackageException $exception) { $message = new Error($exception->getMessage(), 1343231691); } return $message; }
/** * If site packages already exist and are active, we will deactivate them in order to prevent * interactions with the newly created or imported package (like Content Dimensions being used). * * @param string $activePackageKey Package key of one package which should stay active * @return array deactivated site packages */ protected function deactivateAllOtherSitePackages($activePackageKey) { $sitePackagesToDeactivate = $this->packageManager->getFilteredPackages('active', null, 'neos-site'); $deactivatedSitePackages = array(); foreach (array_keys($sitePackagesToDeactivate) as $packageKey) { if ($packageKey !== $activePackageKey) { $this->packageManager->deactivatePackage($packageKey); $deactivatedSitePackages[] = $packageKey; } } return $deactivatedSitePackages; }
/** * @test */ public function openResolves40CharacterLongPackageKeysUsingThePackageManager() { $packageKey = 'Some.PackageKey.Containing.40.Characters'; mkdir('vfs://Foo/Some/'); file_put_contents('vfs://Foo/Some/Path', 'fixture'); $this->mockPackageManager->expects($this->once())->method('isPackageAvailable')->with($packageKey)->will($this->returnValue(true)); $mockPackage = $this->createMock(PackageInterface::class); $mockPackage->expects($this->any())->method('getResourcesPath')->will($this->returnValue('vfs://Foo')); $this->mockPackageManager->expects($this->once())->method('getPackage')->with($packageKey)->will($this->returnValue($mockPackage)); $openedPathAndFilename = ''; $this->assertTrue($this->resourceStreamWrapper->open('resource://' . $packageKey . '/Some/Path', 'r', 0, $openedPathAndFilename)); $this->assertSame($openedPathAndFilename, 'vfs://Foo/Some/Path'); }
/** * Returns the absolute paths of public resources directories of all active packages. * This method is used directly by the FileSystemSymlinkTarget. * * @return array<string> */ public function getPublicResourcePaths() { $paths = []; $packages = $this->packageManager->getActivePackages(); foreach ($packages as $packageKey => $package) { /** @var PackageInterface $package */ $publicResourcesPath = Files::concatenatePaths([$package->getResourcesPath(), 'Public']); if (is_dir($publicResourcesPath)) { $paths[$packageKey] = $publicResourcesPath; } } return $paths; }
/** * @return void */ protected function displayHelpIndex() { $context = $this->bootstrap->getContext(); $applicationPackage = $this->packageManager->getPackage($this->applicationPackageKey); $this->outputLine('<b>%s %s ("%s" context)</b>', [$applicationPackage->getComposerManifest('description'), $applicationPackage->getInstalledVersion() ?: 'dev', $context]); $this->outputLine('<i>usage: %s <command identifier></i>', [$this->getFlowInvocationString()]); $this->outputLine(); $this->outputLine('The following commands are currently available:'); $this->displayShortHelpForCommands($this->commandManager->getAvailableCommands()); $this->outputLine('* = compile time command'); $this->outputLine(); $this->outputLine('See "%s help <commandidentifier>" for more information about a specific command.', [$this->getFlowInvocationString()]); $this->outputLine(); }
/** * If Site Packages already exist and are active, we will deactivate them in order to prevent * interactions with the newly created or imported package (like Content Dimensions being used). * * @param string $packageKey * @return array */ protected function deactivateOtherSitePackages($packageKey) { $sitePackagesToDeactivate = $this->packageManager->getFilteredPackages('active', null, 'neos-site'); $deactivatedSitePackages = array(); foreach ($sitePackagesToDeactivate as $sitePackageToDeactivate) { if ($sitePackageToDeactivate->getPackageKey() !== $packageKey) { $this->packageManager->deactivatePackage($sitePackageToDeactivate->getPackageKey()); $deactivatedSitePackages[] = $sitePackageToDeactivate->getPackageKey(); } } if (count($deactivatedSitePackages) >= 1) { $this->flashMessageContainer->addMessage(new Message(sprintf('The existing Site Packages "%s" were deactivated, in order to prevent interactions with the newly created package "%s".', implode(', ', $deactivatedSitePackages), $packageKey))); } }
/** * Validate a single configuration type * * @param string $configurationType the configuration typr to validate * @param string $path configuration path to validate, or NULL. * @param array $loadedSchemaFiles will be filled with a list of loaded schema files * @return \Neos\Error\Messages\Result * @throws Exception\SchemaValidationException */ protected function validateSingleType($configurationType, $path, &$loadedSchemaFiles) { $availableConfigurationTypes = $this->configurationManager->getAvailableConfigurationTypes(); if (in_array($configurationType, $availableConfigurationTypes) === false) { throw new Exception\SchemaValidationException('The configuration type "' . $configurationType . '" was not found. Only the following configuration types are supported: "' . implode('", "', $availableConfigurationTypes) . '"', 1364984886); } $configuration = $this->configurationManager->getConfiguration($configurationType); // find schema files for the given type and path $schemaFileInfos = []; $activePackages = $this->packageManager->getActivePackages(); foreach ($activePackages as $package) { $packageKey = $package->getPackageKey(); $packageSchemaPath = Files::concatenatePaths([$package->getResourcesPath(), 'Private/Schema']); if (is_dir($packageSchemaPath)) { foreach (Files::getRecursiveDirectoryGenerator($packageSchemaPath, '.schema.yaml') as $schemaFile) { $schemaName = substr($schemaFile, strlen($packageSchemaPath) + 1, -strlen('.schema.yaml')); $schemaNameParts = explode('.', str_replace('/', '.', $schemaName), 2); $schemaType = $schemaNameParts[0]; $schemaPath = isset($schemaNameParts[1]) ? $schemaNameParts[1] : null; if ($schemaType === $configurationType && ($path === null || strpos($schemaPath, $path) === 0)) { $schemaFileInfos[] = ['file' => $schemaFile, 'name' => $schemaName, 'path' => $schemaPath, 'packageKey' => $packageKey]; } } } } if (count($schemaFileInfos) === 0) { throw new Exception\SchemaValidationException('No schema files found for configuration type "' . $configurationType . '"' . ($path !== null ? ' and path "' . $path . '".' : '.'), 1364985056); } $result = new Result(); foreach ($schemaFileInfos as $schemaFileInfo) { $loadedSchemaFiles[] = $schemaFileInfo['file']; if ($schemaFileInfo['path'] !== null) { $data = Arrays::getValueByPath($configuration, $schemaFileInfo['path']); } else { $data = $configuration; } if (empty($data)) { $result->addNotice(new Notice('No configuration found, skipping schema "%s".', 1364985445, [substr($schemaFileInfo['file'], strlen(FLOW_PATH_ROOT))])); } else { $parsedSchema = Yaml::parse($schemaFileInfo['file']); $validationResultForSingleSchema = $this->schemaValidator->validate($data, $parsedSchema); if ($schemaFileInfo['path'] !== null) { $result->forProperty($schemaFileInfo['path'])->merge($validationResultForSingleSchema); } else { $result->merge($validationResultForSingleSchema); } } } return $result; }
/** * Generate a new migration * * If $diffAgainstCurrent is TRUE (the default), it generates a migration file * with the diff between current DB structure and the found mapping metadata. * * Otherwise an empty migration skeleton is generated. * * Only includes tables/sequences matching the $filterExpression regexp when * diffing models and existing schema. Include delimiters in the expression! * The use of * * --filter-expression '/^acme_com/' * * would only create a migration touching tables starting with "acme_com". * * Note: A filter-expression will overrule any filter configured through the * Neos.Flow.persistence.doctrine.migrations.ignoredTables setting * * @param boolean $diffAgainstCurrent Whether to base the migration on the current schema structure * @param string $filterExpression Only include tables/sequences matching the filter expression regexp * @return void * @see neos.flow:doctrine:migrate * @see neos.flow:doctrine:migrationstatus * @see neos.flow:doctrine:migrationexecute * @see neos.flow:doctrine:migrationversion */ public function migrationGenerateCommand($diffAgainstCurrent = true, $filterExpression = null) { // "driver" is used only for Doctrine, thus we (mis-)use it here // additionally, when no host is set, skip this step, assuming no DB is needed if (!$this->isDatabaseConfigured()) { $this->outputLine('Doctrine migration generation has been SKIPPED, the driver and host backend options are not set in /Configuration/Settings.yaml.'); $this->quit(1); } // use default filter expression from settings if ($filterExpression === null) { $ignoredTables = array_keys(array_filter($this->settings['doctrine']['migrations']['ignoredTables'])); if ($ignoredTables !== array()) { $filterExpression = sprintf('/^(?!%s$).*$/xs', implode('$|', $ignoredTables)); } } list($status, $migrationClassPathAndFilename) = $this->doctrineService->generateMigration($diffAgainstCurrent, $filterExpression); $this->outputLine('<info>%s</info>', [$status]); $this->outputLine(); if ($migrationClassPathAndFilename) { $choices = ['Don\'t Move']; $packages = [null]; /** @var Package $package */ foreach ($this->packageManager->getAvailablePackages() as $package) { $type = $package->getComposerManifest('type'); if ($type === null || strpos($type, 'typo3-') !== 0 && strpos($type, 'neos-') !== 0) { continue; } $choices[] = $package->getPackageKey(); $packages[] = $package; } $selectedPackageIndex = (int) $this->output->select('Do you want to move the migration to one of these packages?', $choices, 0); $this->outputLine(); if ($selectedPackageIndex !== 0) { /** @var Package $selectedPackage */ $selectedPackage = $packages[$selectedPackageIndex]; $targetPathAndFilename = Files::concatenatePaths([$selectedPackage->getPackagePath(), 'Migrations', $this->doctrineService->getDatabasePlatformName(), basename($migrationClassPathAndFilename)]); Files::createDirectoryRecursively(dirname($targetPathAndFilename)); rename($migrationClassPathAndFilename, $targetPathAndFilename); $this->outputLine('The migration was moved to: <comment>%s</comment>', [substr($targetPathAndFilename, strlen(FLOW_PATH_PACKAGES))]); $this->outputLine(); $this->outputLine('Next Steps:'); } else { $this->outputLine('Next Steps:'); $this->outputLine(sprintf('- Move <comment>%s</comment> to YourPackage/<comment>Migrations/%s/</comment>', $migrationClassPathAndFilename, $this->doctrineService->getDatabasePlatformName())); } $this->outputLine('- Review and adjust the generated migration.'); $this->outputLine('- (optional) execute the migration using <comment>%s doctrine:migrate</comment>', [$this->getFlowInvocationString()]); } }
/** * @param string $driver * @return array Not supported image format */ protected function findUnsupportedImageFormats($driver) { $this->imagineFactory->injectSettings(array('driver' => ucfirst($driver))); $imagine = $this->imagineFactory->create(); $unsupportedFormats = array(); foreach (array('jpg', 'gif', 'png') as $imageFormat) { $imagePath = Files::concatenatePaths(array($this->packageManager->getPackage('Neos.Neos')->getResourcesPath(), 'Private/Installer/TestImages/Test.' . $imageFormat)); try { $imagine->open($imagePath); } catch (\Exception $exception) { $unsupportedFormats[] = sprintf('"%s"', $imageFormat); } } return $unsupportedFormats; }
/** * Finds all Locale objects representing locales available in the * Flow installation. This is done by scanning all Private and Public * resource files of all active packages, in order to find localized files. * * Localized files have a locale identifier added before their extension * (or at the end of filename, if no extension exists). For example, a * localized file for foobar.png, can be foobar.en.png, fobar.en_GB.png, etc. * * Just one localized resource file causes the corresponding locale to be * regarded as available (installed, supported). * * Note: result of this method invocation is cached * * @return void */ protected function generateAvailableLocalesCollectionByScanningFilesystem() { $whitelistPaths = array_keys(array_filter((array) $this->settings['scan']['includePaths'])); if ($whitelistPaths === []) { return; } $blacklistPattern = $this->getScanBlacklistPattern(); /** @var PackageInterface $activePackage */ foreach ($this->packageManager->getActivePackages() as $activePackage) { $packageResourcesPath = Files::getNormalizedPath($activePackage->getResourcesPath()); if (!is_dir($packageResourcesPath)) { continue; } $directories = []; foreach ($whitelistPaths as $path) { $scanPath = Files::concatenatePaths(array($packageResourcesPath, $path)); if (is_dir($scanPath)) { array_push($directories, Files::getNormalizedPath($scanPath)); } } while ($directories !== []) { $currentDirectory = array_pop($directories); $relativeDirectory = '/' . str_replace($packageResourcesPath, '', $currentDirectory); if ($blacklistPattern !== '' && preg_match($blacklistPattern, $relativeDirectory) === 1) { continue; } if ($handle = opendir($currentDirectory)) { while (false !== ($filename = readdir($handle))) { if ($filename[0] === '.') { continue; } $pathAndFilename = Files::concatenatePaths([$currentDirectory, $filename]); if (is_dir($pathAndFilename)) { array_push($directories, Files::getNormalizedPath($pathAndFilename)); } else { $localeIdentifier = Utility::extractLocaleTagFromFilename($filename); if ($localeIdentifier !== false) { $this->localeCollection->addLocale(new Locale($localeIdentifier)); } } } closedir($handle); } } } }
/** * Tries to find out a package key which the Version belongs to. If no * package could be found, an empty string is returned. * * @param Version $version * @return string */ protected function getPackageKeyFromMigrationVersion(Version $version) { $sortedAvailablePackages = $this->packageManager->getAvailablePackages(); usort($sortedAvailablePackages, function (PackageInterface $packageOne, PackageInterface $packageTwo) { return strlen($packageTwo->getPackagePath()) - strlen($packageOne->getPackagePath()); }); $reflectedClass = new \ReflectionClass($version->getMigration()); $classPathAndFilename = Files::getUnixStylePath($reflectedClass->getFileName()); /** @var $package PackageInterface */ foreach ($sortedAvailablePackages as $package) { $packagePath = Files::getUnixStylePath($package->getPackagePath()); if (strpos($classPathAndFilename, $packagePath) === 0) { return $package->getPackageKey(); } } return ''; }
/** * Replace resource on an asset. Takes variants and redirect handling into account. * * @param AssetInterface $asset * @param PersistentResource $resource * @param array $options * @return void */ public function replaceAssetResource(AssetInterface $asset, PersistentResource $resource, array $options = []) { $originalAssetResource = $asset->getResource(); $asset->setResource($resource); if (isset($options['keepOriginalFilename']) && (bool) $options['keepOriginalFilename'] === true) { $asset->getResource()->setFilename($originalAssetResource->getFilename()); } $uriMapping = []; $redirectHandlerEnabled = isset($options['generateRedirects']) && (bool) $options['generateRedirects'] === true && $this->packageManager->isPackageAvailable('Neos.RedirectHandler'); if ($redirectHandlerEnabled) { $uriMapping[$this->resourceManager->getPublicPersistentResourceUri($originalAssetResource)] = $this->resourceManager->getPublicPersistentResourceUri($asset->getResource()); } if (method_exists($asset, 'getVariants')) { $variants = $asset->getVariants(); /** @var AssetVariantInterface $variant */ foreach ($variants as $variant) { $originalVariantResource = $variant->getResource(); $variant->refresh(); if (method_exists($variant, 'getAdjustments')) { foreach ($variant->getAdjustments() as $adjustment) { if (method_exists($adjustment, 'refit')) { $adjustment->refit($asset); } } } if ($redirectHandlerEnabled) { $uriMapping[$this->resourceManager->getPublicPersistentResourceUri($originalVariantResource)] = $this->resourceManager->getPublicPersistentResourceUri($variant->getResource()); } $this->getRepository($variant)->update($variant); } } if ($redirectHandlerEnabled) { /** @var \Neos\RedirectHandler\Storage\RedirectStorageInterface $redirectStorage */ $redirectStorage = $this->objectManager->get(\Neos\RedirectHandler\Storage\RedirectStorageInterface::class); foreach ($uriMapping as $originalUri => $newUri) { $existingRedirect = $redirectStorage->getOneBySourceUriPathAndHost($originalUri); if ($existingRedirect === null) { $redirectStorage->addRedirect($originalUri, $newUri, 301); } } } $this->getRepository($asset)->update($asset); $this->emitAssetResourceReplaced($asset); }
/** * Imports one or multiple sites from the XML file at $pathAndFilename * * @param string $pathAndFilename * @return Site The imported site * @throws UnknownPackageException|InvalidPackageStateException|NeosException */ public function importFromFile($pathAndFilename) { /** @var Site $importedSite */ $site = null; $xmlReader = new \XMLReader(); $xmlReader->open($pathAndFilename, null, LIBXML_PARSEHUGE); if ($this->workspaceRepository->findOneByName('live') === null) { $this->workspaceRepository->add(new Workspace('live')); $this->persistenceManager->persistAll(); } while ($xmlReader->read()) { if ($xmlReader->nodeType != \XMLReader::ELEMENT || $xmlReader->name !== 'site') { continue; } $site = $this->getSiteByNodeName($xmlReader->getAttribute('siteNodeName')); $site->setName($xmlReader->getAttribute('name')); $site->setState((int) $xmlReader->getAttribute('state')); $siteResourcesPackageKey = $xmlReader->getAttribute('siteResourcesPackageKey'); if (!$this->packageManager->isPackageAvailable($siteResourcesPackageKey)) { throw new UnknownPackageException(sprintf('Package "%s" specified in the XML as site resources package does not exist.', $siteResourcesPackageKey), 1303891443); } if (!$this->packageManager->isPackageActive($siteResourcesPackageKey)) { throw new InvalidPackageStateException(sprintf('Package "%s" specified in the XML as site resources package is not active.', $siteResourcesPackageKey), 1303898135); } $site->setSiteResourcesPackageKey($siteResourcesPackageKey); $rootNode = $this->contextFactory->create()->getRootNode(); // We fetch the workspace to be sure it's known to the persistence manager and persist all // so the workspace and site node are persisted before we import any nodes to it. $rootNode->getContext()->getWorkspace(); $this->persistenceManager->persistAll(); $sitesNode = $rootNode->getNode(SiteService::SITES_ROOT_PATH); if ($sitesNode === null) { $sitesNode = $rootNode->createNode(NodePaths::getNodeNameFromPath(SiteService::SITES_ROOT_PATH)); } $this->nodeImportService->import($xmlReader, $sitesNode->getPath(), dirname($pathAndFilename) . '/Resources'); } if ($site === null) { throw new NeosException(sprintf('The XML file did not contain a valid site node.'), 1418999522); } $this->emitSiteImported($site); return $site; }
/** * Evaluates the absolute path and filename of the resource file specified * by the given path. * * @param string $requestedPath * @param boolean $checkForExistence Whether a (non-hash) path should be checked for existence before being returned * @return mixed The full path and filename or FALSE if the file doesn't exist * @throws \InvalidArgumentException|ResourceException */ protected function evaluateResourcePath($requestedPath, $checkForExistence = true) { if (substr($requestedPath, 0, strlen(self::SCHEME)) !== self::SCHEME) { throw new \InvalidArgumentException('The ' . __CLASS__ . ' only supports the \'' . self::SCHEME . '\' scheme.', 1256052544); } $uriParts = Functions::parse_url($requestedPath); if (!is_array($uriParts) || !isset($uriParts['host'])) { return false; } if (preg_match('/^[0-9a-f]{40}$/i', $uriParts['host']) === 1) { $resource = $this->resourceManager->getResourceBySha1($uriParts['host']); return $this->resourceManager->getStreamByResource($resource); } if (!$this->packageManager->isPackageAvailable($uriParts['host'])) { throw new ResourceException(sprintf('Invalid resource URI "%s": Package "%s" is not available.', $requestedPath, $uriParts['host']), 1309269952); } $package = $this->packageManager->getPackage($uriParts['host']); $resourceUri = Files::concatenatePaths([$package->getResourcesPath(), $uriParts['path']]); if ($checkForExistence === false || file_exists($resourceUri)) { return $resourceUri; } return false; }
/** * Clean up resource registry * * This command checks the resource registry (that is the database tables) for orphaned resource objects which don't * seem to have any corresponding data anymore (for example: the file in Data/Persistent/Resources has been deleted * without removing the related PersistentResource object). * * If the Neos.Media package is active, this command will also detect any assets referring to broken resources * and will remove the respective Asset object from the database when the broken resource is removed. * * This command will ask you interactively what to do before deleting anything. * * @return void */ public function cleanCommand() { $this->outputLine('Checking if resource data exists for all known resource objects ...'); $this->outputLine(); $mediaPackagePresent = $this->packageManager->isPackageActive('Neos.Media'); $resourcesCount = $this->resourceRepository->countAll(); $this->output->progressStart($resourcesCount); $brokenResources = []; $relatedAssets = new \SplObjectStorage(); $relatedThumbnails = new \SplObjectStorage(); $iterator = $this->resourceRepository->findAllIterator(); foreach ($this->resourceRepository->iterate($iterator, function ($iteration) { $this->clearState($iteration); }) as $resource) { $this->output->progressAdvance(1); /* @var PersistentResource $resource */ $stream = $resource->getStream(); if (!is_resource($stream)) { $brokenResources[] = $resource->getSha1(); } } $this->output->progressFinish(); $this->outputLine(); if ($mediaPackagePresent && count($brokenResources) > 0) { /* @var AssetRepository $assetRepository */ $assetRepository = $this->objectManager->get(AssetRepository::class); /* @var ThumbnailRepository $thumbnailRepository */ $thumbnailRepository = $this->objectManager->get(ThumbnailRepository::class); foreach ($brokenResources as $key => $resourceSha1) { $resource = $this->resourceRepository->findOneBySha1($resourceSha1); $brokenResources[$key] = $resource; $assets = $assetRepository->findByResource($resource); if ($assets !== null) { $relatedAssets[$resource] = $assets; } $thumbnails = $thumbnailRepository->findByResource($resource); if ($assets !== null) { $relatedThumbnails[$resource] = $thumbnails; } } } if (count($brokenResources) > 0) { $this->outputLine('<b>Found %s broken resource(s):</b>', [count($brokenResources)]); $this->outputLine(); foreach ($brokenResources as $resource) { $this->outputLine('%s (%s) from "%s" collection', [$resource->getFilename(), $resource->getSha1(), $resource->getCollectionName()]); if (isset($relatedAssets[$resource])) { foreach ($relatedAssets[$resource] as $asset) { $this->outputLine(' -> %s (%s)', [get_class($asset), $asset->getIdentifier()]); } } } $response = null; while (!in_array($response, ['y', 'n', 'c'])) { $response = $this->output->ask('<comment>Do you want to remove all broken resource objects and related assets from the database? (y/n/c) </comment>'); } switch ($response) { case 'y': $brokenAssetCounter = 0; $brokenThumbnailCounter = 0; foreach ($brokenResources as $sha1 => $resource) { $this->outputLine('- delete %s (%s) from "%s" collection', [$resource->getFilename(), $resource->getSha1(), $resource->getCollectionName()]); $resource->disableLifecycleEvents(); $this->resourceRepository->remove($resource); if (isset($relatedAssets[$resource])) { foreach ($relatedAssets[$resource] as $asset) { $assetRepository->remove($asset); $brokenAssetCounter++; } } if (isset($relatedThumbnails[$resource])) { foreach ($relatedThumbnails[$resource] as $thumbnail) { $thumbnailRepository->remove($thumbnail); $brokenThumbnailCounter++; } } $this->persistenceManager->persistAll(); } $brokenResourcesCounter = count($brokenResources); if ($brokenResourcesCounter > 0) { $this->outputLine('Removed %s resource object(s) from the database.', [$brokenResourcesCounter]); } if ($brokenAssetCounter > 0) { $this->outputLine('Removed %s asset object(s) from the database.', [$brokenAssetCounter]); } if ($brokenThumbnailCounter > 0) { $this->outputLine('Removed %s thumbnail object(s) from the database.', [$brokenThumbnailCounter]); } break; case 'n': $this->outputLine('Did not delete any resource objects.'); break; case 'c': $this->outputLine('Stopping. Did not delete any resource objects.'); $this->quit(0); break; } } }
/** * Prepares an array with TypoScript paths to auto include before the Site TypoScript. * * @return array */ protected function prepareAutoIncludeTypoScript() { $autoIncludeTypoScript = array(); foreach (array_keys($this->packageManager->getActivePackages()) as $packageKey) { if (isset($this->autoIncludeConfiguration[$packageKey]) && $this->autoIncludeConfiguration[$packageKey] === true) { $autoIncludeTypoScriptFile = sprintf($this->autoIncludeTypoScriptPattern, $packageKey); if (is_file($autoIncludeTypoScriptFile)) { $autoIncludeTypoScript[] = $autoIncludeTypoScriptFile; } else { // If there is no Root.fusion found in the default path pattern or the legacy path pattern // use the default path pattern so an exception will show the correct path pattern and not a // legacy path pattern $legacyAutoIncludeTypoScriptFile = sprintf($this->legacyAutoIncludeTypoScriptPattern, $packageKey); if (is_file($legacyAutoIncludeTypoScriptFile)) { $autoIncludeTypoScript[] = $legacyAutoIncludeTypoScriptFile; } else { $autoIncludeTypoScript[] = $autoIncludeTypoScriptFile; } } } } return $autoIncludeTypoScript; }