Skip to content
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

Open
wants to merge 19 commits into
base: 6.13
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d11e86c
Added script for fixing var_dir issues for images
mateuszbieniek Mar 18, 2020
981dd99
CS fixup
mateuszbieniek Mar 18, 2020
f0c36a9
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Mar 18, 2020
4622abb
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Mar 18, 2020
3a8ec28
Update eZ/Bundle/EzPublishMigrationBundle/Resources/config/services.yml
mateuszbieniek Mar 18, 2020
3272624
Update eZ/Publish/Core/FieldType/Image/ImageStorage/Gateway.php
mateuszbieniek Mar 18, 2020
6543f9c
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Mar 18, 2020
16bb1da
Update eZ/Publish/Core/FieldType/Image/ImageStorage/Gateway/LegacySto…
mateuszbieniek Mar 18, 2020
6a7f71c
Update eZ/Publish/Core/FieldType/Image/ImageStorage/Gateway/LegacySto…
mateuszbieniek Mar 18, 2020
e677e6b
Reverted changes to LegacyStorage as it is deprecated since 6.11
mateuszbieniek Mar 18, 2020
2f401ed
Update eZ/Bundle/EzPublishMigrationBundle/Resources/config/services.yml
mateuszbieniek Apr 2, 2020
f28c7c2
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Apr 2, 2020
44e3525
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Apr 2, 2020
e91dd5b
Update eZ/Bundle/EzPublishMigrationBundle/Command/LegacyStorage/FixIm…
mateuszbieniek Apr 2, 2020
2923b6c
Update eZ/Publish/Core/FieldType/Image/ImageStorage/Gateway/DoctrineS…
mateuszbieniek Apr 2, 2020
9bed9cf
Update eZ/Bundle/EzPublishMigrationBundle/DependencyInjection/EzPubli…
mateuszbieniek Apr 2, 2020
6844858
Update eZ/Publish/Core/FieldType/Image/ImageStorage/Gateway/DoctrineS…
mateuszbieniek Apr 2, 2020
1baa65e
Update eZ/Publish/Core/Persistence/Legacy/Content/Gateway/DoctrineDat…
mateuszbieniek Apr 2, 2020
5cca5be
CR fixups
mateuszbieniek Apr 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Comment on lines +32 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused now? ;)


/**
* @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS

Suggested change
* @param InputInterface $input
* @param OutputInterface $output

* @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
Copy link
Member

Choose a reason for hiding this comment

The 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:

  • Initial question if User wants to proceede
  • Subprocesses (there were several bugs related to propagating command switches and it would be good to avoid them).


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()
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this? Empty string for loadXML always either generates warning or throws.


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');
}
}
11 changes: 11 additions & 0 deletions eZ/Bundle/EzPublishMigrationBundle/Resources/config/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 }

Loading