-
Notifications
You must be signed in to change notification settings - Fork 203
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
Added script for fixing var_dir issues for images #2995
base: 6.13
Are you sure you want to change the base?
Changes from all commits
d11e86c
981dd99
f0c36a9
4622abb
3a8ec28
3272624
6543f9c
16bb1da
6a7f71c
e677e6b
2f401ed
f28c7c2
44e3525
e91dd5b
2923b6c
9bed9cf
6844858
1baa65e
5cca5be
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,343 @@ | ||||||
<?php | ||||||
|
||||||
/** | ||||||
* @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 = []; | ||||||
|
||||||
public function __construct( | ||||||
ChainConfigResolver $configResolver, | ||||||
SiteAccess $siteaccess, | ||||||
ContentGateway $contentGateway, | ||||||
ImageGateway $imageGateway | ||||||
) { | ||||||
parent::__construct(); | ||||||
$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 database references to images that are not placed in 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 database references to images that are not placed in the current var_dir. | ||||||
|
||||||
This may for instance occur when the var_dir setting is changed. This script will 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 | ||||||
mateuszbieniek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
production environment using <info>--env=prod</info> switch and with <info>--no-debug</info> for non-prod environments. | ||||||
|
||||||
EOT | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* @param InputInterface $input | ||||||
* @param OutputInterface $output | ||||||
Comment on lines
+129
to
+130
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. CS
Suggested change
|
||||||
* @return int | ||||||
*/ | ||||||
protected function execute(InputInterface $input, OutputInterface $output) | ||||||
{ | ||||||
$iterationCount = (int) $input->getOption('iteration-count'); | ||||||
$this->dryRun = $input->getOption('dry-run'); | ||||||
$consoleScript = $_SERVER['argv'][0]; | ||||||
|
||||||
$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(); | ||||||
Comment on lines
+160
to
+179
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. There's a lot of common code in general for such script. Maybe it's time to extract common base. Static analysis reports duplicated code for this block, but of course for our need refactoring would involve some simplification/common base for:
|
||||||
|
||||||
for ($fixed = 0; $fixed < $count; $fixed += $iterationCount) { | ||||||
$processScriptFragments = [ | ||||||
$this->getPhpPath(), | ||||||
$consoleScript, | ||||||
$this->getName(), | ||||||
'--iteration-count=' . $iterationCount, | ||||||
'--siteaccess=' . $this->siteaccess->name, | ||||||
]; | ||||||
|
||||||
$process = new Process( | ||||||
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() | ||||||
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. Static analysis: reduce cognitive complexity of this method by reducing number of nested blocks/extracting to smaller methods. |
||||||
{ | ||||||
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; | ||||||
} | ||||||
Comment on lines
+263
to
+267
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. What's the point of this? Empty string for |
||||||
|
||||||
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,24 @@ | ||
<?php | ||
|
||
/** | ||
* @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'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
services: | ||
ezpublish.migration.fix_images_var_dir: | ||
mateuszbieniek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class: eZ\Bundle\EzPublishMigrationBundle\Command\LegacyStorage\FixImagesVarDirCommand | ||
mateuszbieniek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
arguments: | ||
- '@ezpublish.config.resolver' | ||
- '@ezpublish.siteaccess' | ||
- '@ezpublish.persistence.legacy.content.gateway' | ||
- '@ezpublish.fieldType.ezimage.storage_gateway' | ||
tags: | ||
- { name: console.command } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused now? ;)