-
Notifications
You must be signed in to change notification settings - Fork 201
Added script for fixing var_dir issues for images #2638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,361 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * This file is part of the eZ Publish Kernel package. | ||
| * | ||
| * @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
| * @license For full copyright and license information view LICENSE file distributed with this source code. | ||
| */ | ||
| namespace eZ\Bundle\EzPublishMigrationBundle\Command\LegacyStorage; | ||
|
|
||
| use DOMDocument; | ||
| use Exception; | ||
| use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ChainConfigResolver; | ||
| use eZ\Publish\Core\MVC\Symfony\SiteAccess; | ||
| use eZ\Publish\Core\Persistence\Database\DatabaseHandler; | ||
| use eZ\Publish\Core\Persistence\Legacy\Content\Gateway as ContentGateway; | ||
| use eZ\Publish\Core\FieldType\Image\ImageStorage\Gateway as ImageGateway; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Process\Process; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
| use Symfony\Component\Console\Input\InputArgument; | ||
| use Symfony\Component\Console\Input\InputOption; | ||
| use Symfony\Component\Console\Helper\ProgressBar; | ||
| use Symfony\Component\Console\Question\ConfirmationQuestion; | ||
| use Symfony\Component\Process\PhpExecutableFinder; | ||
| use Symfony\Component\Console\Exception\RuntimeException; | ||
|
|
||
| class FixImagesVarDirCommand extends Command | ||
| { | ||
| const DEFAULT_ITERATION_COUNT = 100; | ||
| const STORAGE_IMAGES_PATH = '/storage/images/'; | ||
|
|
||
| /** | ||
| * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler | ||
| */ | ||
| private $db; | ||
|
|
||
| /** | ||
| * @var \eZ\Publish\Core\Persistence\Legacy\Content\Gateway | ||
| */ | ||
| private $contentGateway; | ||
|
|
||
| /** | ||
| * @var \eZ\Publish\Core\FieldType\Image\ImageStorage\Gateway | ||
| */ | ||
| private $imageGateway; | ||
|
|
||
| /** | ||
| * @var \eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ChainConfigResolver | ||
| */ | ||
| private $configResolver; | ||
|
|
||
| /** | ||
| * @var \eZ\Publish\Core\MVC\Symfony\SiteAccess | ||
| */ | ||
| private $siteaccess; | ||
|
|
||
| /** | ||
| * @var int | ||
| */ | ||
| protected $done = 0; | ||
|
|
||
| /** | ||
| * @var string | ||
| */ | ||
| private $phpPath; | ||
|
|
||
| /** | ||
| * @var bool | ||
| */ | ||
| private $dryRun; | ||
|
|
||
| /** | ||
| * @var int | ||
| */ | ||
| private $varDir; | ||
|
|
||
| /** | ||
| * @var array | ||
| */ | ||
| private $imageAttributes = []; | ||
|
|
||
| /** | ||
| * @param \eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ChainConfigResolver; $configResolver | ||
| * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db | ||
| * @param \eZ\Publish\Core\MVC\Symfony\SiteAccess $siteaccess | ||
| * @param \eZ\Publish\Core\Persistence\Legacy\Content\Gateway $contentGateway | ||
| * @param \eZ\Publish\Core\FieldType\Image\ImageStorage\Gateway $imageGateway | ||
| */ | ||
| public function __construct( | ||
| ChainConfigResolver $configResolver, | ||
| DatabaseHandler $db, | ||
| SiteAccess $siteaccess, | ||
| ContentGateway $contentGateway, | ||
| ImageGateway $imageGateway | ||
| ) { | ||
| parent::__construct(); | ||
| $this->db = $db; | ||
| $this->configResolver = $configResolver; | ||
| $this->siteaccess = $siteaccess; | ||
| $this->contentGateway = $contentGateway; | ||
| $this->imageGateway = $imageGateway; | ||
| } | ||
|
|
||
| protected function configure() | ||
| { | ||
| $this | ||
| ->setName('ezplatform:fix_images_var_dir') | ||
| ->setDescription( | ||
| 'This update script will fix references to images that aren\'t part of the current var_dir.' | ||
| ) | ||
| ->addOption( | ||
| 'dry-run', | ||
| null, | ||
| InputOption::VALUE_NONE, | ||
| 'Execute a dry run' | ||
| ) | ||
| ->addOption( | ||
| 'iteration-count', | ||
| null, | ||
| InputArgument::OPTIONAL, | ||
| 'Limit how many records get updated by single process', | ||
| self::DEFAULT_ITERATION_COUNT | ||
| ) | ||
| ->setHelp( | ||
| <<<EOT | ||
| The command <info>%command.name%</info> fixes referefences to images that aren't part of the current var_dir. | ||
mateuszbieniek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| This may for instance occur when the var_dir setting is changed. This script will rename the files, and update the | ||
| database references to the new path | ||
|
|
||
| Since this script can potentially run for a very long time, to avoid memory exhaustion run it in | ||
| production environment using <info>--env=prod</info> switch. | ||
|
|
||
| EOT | ||
| ); | ||
| } | ||
|
|
||
| protected function initialize(InputInterface $input, OutputInterface $output) | ||
| { | ||
| parent::initialize($input, $output); | ||
| $this->imageGateway->setConnection($this->db); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warning: this will stop working on upper branch. At this point I'd refactor Gateway itself, or decorate it just to be safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Script has to be refactored slightly for 2.x and onward, so maybe w can leave it as it is and change it for "upper braches"? |
||
| } | ||
|
|
||
| /** | ||
| * @param InputInterface $input | ||
| * @param OutputInterface $output | ||
| * @return int|void|null | ||
| */ | ||
| protected function execute(InputInterface $input, OutputInterface $output) | ||
| { | ||
| $iterationCount = (int)$input->getOption('iteration-count'); | ||
| $this->dryRun = $input->getOption('dry-run'); | ||
| $consoleScript = $_SERVER['argv'][0]; | ||
glye marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| $this->varDir = $this->configResolver->getParameter( | ||
| 'var_dir', | ||
| null, | ||
| $this->siteaccess->name | ||
| ); | ||
|
|
||
| if (getenv('INNER_CALL')) { | ||
| $this->processImages($iterationCount, $output); | ||
| $output->writeln($this->done); | ||
| } else { | ||
| $output->writeln([ | ||
| sprintf('Fixing image references using siteaccess %s (var_dir: %s)', $this->siteaccess->name, $this->varDir), | ||
| 'Calculating number of Images to fix...', | ||
| ]); | ||
|
|
||
| $count = $this->countImagesToFix(); | ||
| $output->writeln([ | ||
| sprintf('Found total of Images for fixing: %d', $count), | ||
| '', | ||
| ]); | ||
|
|
||
| if ($count == 0) { | ||
| $output->writeln('Nothing to process, exiting.'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| $helper = $this->getHelper('question'); | ||
| $question = new ConfirmationQuestion( | ||
| '<question>Are you sure you want to proceed?</question> ', | ||
| false | ||
| ); | ||
|
|
||
| if (!$helper->ask($input, $output, $question)) { | ||
| $output->writeln(''); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| $progressBar = $this->getProgressBar($count, $output); | ||
| $progressBar->start(); | ||
|
|
||
| for ($fixed = 0; $fixed < $count; $fixed += $iterationCount) { | ||
| $processScriptFragments = [ | ||
| $this->getPhpPath(), | ||
| $consoleScript, | ||
| $this->getName(), | ||
| '--iteration-count=' . $iterationCount, | ||
| '--siteaccess=' . $this->siteaccess->name, | ||
| ]; | ||
|
|
||
| $process = new Process( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So side comment, but with this, re-index command and probably also others, maybe we can find a way to expose reusable code for parallel processes? So one place where we can make sure any global arguments like siteaccess, debug, env, memory, ... is passed to php & bin/console. Same goes for installers and need to run sub commands, like in Commerce installer PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, this seems to be very repetitive. |
||
| implode(' ', $processScriptFragments) | ||
| ); | ||
|
|
||
| $process->setEnv(['INNER_CALL' => 1]); | ||
| $process->run(); | ||
|
|
||
| if (!$process->isSuccessful()) { | ||
| throw new RuntimeException($process->getErrorOutput()); | ||
| } | ||
|
|
||
| $doneInProcess = (int)$process->getOutput(); | ||
| $this->done += $doneInProcess; | ||
| $progressBar->advance($doneInProcess); | ||
| } | ||
|
|
||
| $progressBar->finish(); | ||
| $output->writeln([ | ||
| '', | ||
| sprintf('Done: %d', $this->done), | ||
| ]); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param int $limit | ||
| * @param \Symfony\Component\Console\Output\OutputInterface $output | ||
| */ | ||
| protected function processImages($limit, OutputInterface $output) | ||
| { | ||
| $images = $this->getImagesToFix($limit); | ||
|
|
||
| foreach ($images as $image) { | ||
| $filePath = $image['filepath']; | ||
| $relativePath = substr( | ||
| $filePath, | ||
| strpos($filePath, self::STORAGE_IMAGES_PATH) | ||
| ); | ||
|
|
||
| $newFilePath = $this->varDir . $relativePath; | ||
|
|
||
| if (!$this->dryRun) { | ||
| $this->updateImage($image['id'], $image['contentobject_attribute_id'], $filePath, $newFilePath); | ||
| } | ||
|
|
||
| ++$this->done; | ||
| } | ||
|
|
||
| if (!$this->dryRun) { | ||
| $this->updateContentObjectAtributes(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param int $imageId | ||
| * @param int $contentObjectAttributeId | ||
| * @param string $oldFilePath | ||
| * @param string $newFilePath | ||
| */ | ||
| protected function updateImage($imageId, $contentObjectAttributeId, $oldFilePath, $newFilePath) | ||
| { | ||
| $this->imageGateway->updateImageFilePath($imageId, $newFilePath); | ||
| $this->imageAttributes[$contentObjectAttributeId][$oldFilePath] = $newFilePath; | ||
| } | ||
|
|
||
| protected function updateContentObjectAtributes() | ||
| { | ||
| foreach ($this->imageAttributes as $attributeId => $files) { | ||
| $attributeObjects = $this->contentGateway->getContentObjectAttributesById($attributeId); | ||
|
|
||
| foreach ($attributeObjects as $attributeObject) { | ||
| $dom = new DOMDocument('1.0', 'utf-8'); | ||
|
|
||
| try { | ||
| $dom->loadXML(''); | ||
| } catch (Exception $e) { | ||
| continue; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please log this information. Side: you should get some kind of exception w/o the error handler at the beginning. But if not there are |
||
| } | ||
|
|
||
| foreach ($dom->getElementsByTagName('ezimage') as $ezimageNode) { | ||
| $oldPath = $ezimageNode->getAttribute('url'); | ||
|
|
||
| if (isset($files[$oldPath])) { | ||
| $ezimageNode->setAttribute('url', $files[$oldPath]); | ||
| $ezimageNode->setAttribute('dirpath', \dirname($files[$oldPath])); | ||
| } | ||
|
|
||
| foreach ($ezimageNode->getElementsByTagName('alias') as $ezimageAlias) { | ||
| $oldPath = $ezimageAlias->getAttribute('url'); | ||
| if (isset($files[$oldPath])) { | ||
| $ezimageAlias->setAttribute('url', $files[$oldPath]); | ||
| $ezimageAlias->setAttribute('dirpath', \dirname($files[$oldPath])); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| $this->contentGateway->updateContentObjectAtribute($attributeObject['id'], $attributeObject['version'], $dom->saveXML()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param int $limit | ||
| * | ||
| * @return array | ||
| */ | ||
| protected function getImagesToFix($limit) | ||
| { | ||
| return $this->imageGateway->getImagesOutsidePath('/' . $this->varDir . '/storage/', $limit, 0); | ||
| } | ||
|
|
||
| /** | ||
| * @return int | ||
| */ | ||
| protected function countImagesToFix() | ||
| { | ||
| return $this->imageGateway->countImageReferencesOutsidePath('/' . $this->varDir . '/storage/'); | ||
| } | ||
|
|
||
| /** | ||
| * @param int $maxSteps | ||
| * @param \Symfony\Component\Console\Output\OutputInterface $output | ||
| * | ||
| * @return \Symfony\Component\Console\Helper\ProgressBar | ||
| */ | ||
| protected function getProgressBar($maxSteps, OutputInterface $output) | ||
| { | ||
| $progressBar = new ProgressBar($output, $maxSteps); | ||
| $progressBar->setFormat( | ||
| ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%' | ||
| ); | ||
|
|
||
| return $progressBar; | ||
| } | ||
|
|
||
| /** | ||
| * @return string | ||
| */ | ||
| private function getPhpPath() | ||
| { | ||
| if ($this->phpPath) { | ||
| return $this->phpPath; | ||
| } | ||
| $phpFinder = new PhpExecutableFinder(); | ||
| $this->phpPath = $phpFinder->find(); | ||
| if (!$this->phpPath) { | ||
| throw new RuntimeException( | ||
| 'The php executable could not be found, it\'s needed for executing parable sub processes, so add it to your PATH environment variable and try again' | ||
| ); | ||
| } | ||
|
|
||
| return $this->phpPath; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * File containing the EzPublishMigrationExtension class. | ||
| * | ||
| * @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
| * @license For full copyright and license information view LICENSE file distributed with this source code. | ||
| */ | ||
| namespace eZ\Bundle\EzPublishMigrationBundle\DependencyInjection; | ||
|
|
||
| use Symfony\Component\HttpKernel\DependencyInjection\Extension; | ||
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
| use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; | ||
| use Symfony\Component\Config\FileLocator; | ||
|
|
||
| class EzPublishMigrationExtension extends Extension | ||
| { | ||
| public function load(array $configs, ContainerBuilder $container) | ||
| { | ||
| $loader = new YamlFileLoader( | ||
| $container, | ||
| new FileLocator(__DIR__ . '/../Resources/config') | ||
| ); | ||
| $loader->load('services.yml'); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.