Skip to content

Commit

Permalink
Merge branch '6.13' into 7.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Longosz committed Sep 17, 2018
2 parents d2cec4a + f11ecd3 commit c31c285
Show file tree
Hide file tree
Showing 51 changed files with 1,934 additions and 51 deletions.
1 change: 0 additions & 1 deletion data/mysql/cleandata.sql
Expand Up @@ -1929,7 +1929,6 @@ INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:51','eznode',1,18,0,1,3,18,9,'Images','59b514174bffe4ae402b3d63aad79fe0');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:45','eznode',1,12,0,1,3,12,10,'Anonymous-User','ccb62ebca03a31272430bc414bd5cd5b');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:45','eznode',1,31,0,0,1,12,11,'anonymous_user','c593ec85293ecb0e02d50d4c5c6c20eb');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('nop:','nop',1,15,0,0,1,15,14,'images','59b514174bffe4ae402b3d63aad79fe0');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:53','eznode',1,34,0,0,1,20,17,'multimedia','2e5bc8831f7ae6a29530e7f1bbf2de9c');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:52','eznode',1,33,0,0,1,19,17,'files','45b963397aa40d4a0063e0d85e4fe7a1');
INSERT INTO `ezurlalias_ml` (`action`, `action_type`, `alias_redirects`, `id`, `is_alias`, `is_original`, `lang_mask`, `link`, `parent`, `text`, `text_md5`) VALUES ('eznode:51','eznode',1,32,0,0,1,18,17,'images','59b514174bffe4ae402b3d63aad79fe0');
Expand Down
14 changes: 12 additions & 2 deletions eZ/Bundle/EzPublishCoreBundle/ApiLoader/RepositoryFactory.php
Expand Up @@ -17,6 +17,8 @@
use eZ\Publish\SPI\Limitation\Type as SPILimitationType;
use eZ\Publish\Core\Base\Container\ApiLoader\FieldTypeCollectionFactory;
use eZ\Publish\Core\Base\Container\ApiLoader\FieldTypeNameableCollectionFactory;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

Expand Down Expand Up @@ -62,18 +64,25 @@ class RepositoryFactory implements ContainerAwareInterface
*/
private $policyMap;

/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;

public function __construct(
ConfigResolverInterface $configResolver,
FieldTypeCollectionFactory $fieldTypeCollectionFactory,
FieldTypeNameableCollectionFactory $fieldTypeNameableCollectionFactory,
$repositoryClass,
array $policyMap
array $policyMap,
LoggerInterface $logger = null
) {
$this->configResolver = $configResolver;
$this->fieldTypeCollectionFactory = $fieldTypeCollectionFactory;
$this->fieldTypeNameableCollectionFactory = $fieldTypeNameableCollectionFactory;
$this->repositoryClass = $repositoryClass;
$this->policyMap = $policyMap;
$this->logger = null !== $logger ? $logger : new NullLogger();
}

/**
Expand Down Expand Up @@ -112,7 +121,8 @@ public function buildRepository(
'languages' => $this->configResolver->getParameter('languages'),
'content' => ['default_version_archive_limit' => $config['options']['default_version_archive_limit']],
),
new UserReference($this->configResolver->getParameter('anonymous_user_id'))
new UserReference($this->configResolver->getParameter('anonymous_user_id')),
$this->logger
);

return $repository;
Expand Down
229 changes: 229 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Command/RegenerateUrlAliasesCommand.php
@@ -0,0 +1,229 @@
<?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\EzPublishCoreBundle\Command;

use Exception;
use eZ\Publish\API\Repository\Repository;
use eZ\Publish\API\Repository\Values\Content\Language;
use eZ\Publish\API\Repository\Values\Content\Location;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

/**
* The ezplatform:urls:regenerate-aliases Symfony command implementation.
* Recreates system URL aliases for all existing Locations and cleanups corrupted URL alias nodes.
*/
class RegenerateUrlAliasesCommand extends Command
{
const DEFAULT_ITERATION_COUNT = 1000;

const BEFORE_RUNNING_HINTS = <<<EOT
<error>Before you continue:</error>
- Make sure to back up your database.
- Take installation offline, during the script execution the database should not be modified.
- Run this command without memory limit, i.e. processing of 300k Locations can take up to 1 GB of RAM.
- Run this command in production environment using <info>--env=prod</info>
EOT;

/**
* @var \eZ\Publish\API\Repository\Repository
*/
private $repository;

/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;

/**
* @param \eZ\Publish\API\Repository\Repository $repository
* @param \Psr\Log\LoggerInterface $logger
*/
public function __construct(Repository $repository, LoggerInterface $logger = null)
{
parent::__construct();

$this->repository = $repository;
$this->logger = null !== $logger ? $logger : new NullLogger();
}

/**
* {@inheritdoc}
*/
protected function configure()
{
$beforeRunningHints = self::BEFORE_RUNNING_HINTS;
$this
->setName('ezplatform:urls:regenerate-aliases')
->setDescription(
'Regenerates Location URL aliases (autogenerated) and cleans up custom Location ' .
'and global URL aliases stored in the Legacy Storage Engine'
)
->addOption(
'iteration-count',
'c',
InputOption::VALUE_OPTIONAL,
'Number of Locations fetched into memory and processed at once',
self::DEFAULT_ITERATION_COUNT
)
->setHelp(
<<<EOT
{$beforeRunningHints}
The command <info>%command.name%</info> regenerates URL aliases for Locations and cleans up
corrupted URL aliases (pointing to non-existent Locations).
Existing aliases are archived (will redirect to the new ones).
Note: This script can potentially run for a very long time.
Due to performance issues the command does not send any Signals.
<comment>HTTP cache needs to be cleared manually after executing this command.</comment>
EOT
);
}

/**
* Regenerate URL aliases.
*
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$iterationCount = (int)$input->getOption('iteration-count');

$locationsCount = $this->repository->sudo(
function (Repository $repository) {
return $repository->getLocationService()->getAllLocationsCount();
}
);

$helper = $this->getHelper('question');
$question = new ConfirmationQuestion(
sprintf(
"<info>Found %d Locations.</info>\n%s\n<info>Do you want to proceed? [y/N] </info>",
$locationsCount,
self::BEFORE_RUNNING_HINTS
),
false
);
if (!$helper->ask($input, $output, $question)) {
return;
}

$output->writeln('<info>Cleaning up corrupted URL aliases...</info>');
$corruptedAliasesCount = $this->repository->sudo(
function (Repository $repository) {
return $repository->getURLAliasService()->deleteCorruptedUrlAliases();
}
);
$output->writeln("<info>Done. Deleted {$corruptedAliasesCount} entries.</info>");

$output->writeln('Regenerating System URL aliases...');

$progressBar = $this->getProgressBar($locationsCount, $output);
$progressBar->start();

for ($offset = 0; $offset <= $locationsCount; $offset += $iterationCount) {
gc_disable();
$locations = $this->repository->sudo(
function (Repository $repository) use ($offset, $iterationCount) {
return $repository->getLocationService()->loadAllLocations($offset, $iterationCount);
}
);
$this->processLocations($locations, $progressBar);
gc_enable();
}
$progressBar->finish();
$output->writeln('');
$output->writeln('<info>Done.</info>');
}

/**
* Return configured progress bar helper.
*
* @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;
}

/**
* Process single results page of fetched Locations.
*
* @param \eZ\Publish\API\Repository\Values\Content\Location[] $locations
* @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
*/
private function processLocations(array $locations, ProgressBar $progressBar)
{
$contentList = $this->repository->sudo(
function (Repository $repository) use ($locations) {
$contentInfoList = array_map(
function (Location $location) {
return $location->contentInfo;
},
$locations
);

// load Content list in all languages
return $repository->getContentService()->loadContentListByContentInfo(
$contentInfoList,
Language::ALL,
false
);
}
);
foreach ($locations as $location) {
try {
// ignore missing Content items
if (!isset($contentList[$location->contentId])) {
continue;
}

$content = $contentList[$location->contentId];
$this->repository->sudo(
function (Repository $repository) use ($location, $content) {
$repository->getURLAliasService()->refreshSystemUrlAliasesForLocation(
$location
);
}
);
} catch (Exception $e) {
$contentInfo = $location->getContentInfo();
$msg = sprintf(
'Failed processing location %d - [%d] %s (%s: %s)',
$location->id,
$contentInfo->id,
$contentInfo->name,
get_class($e),
$e->getMessage()
);
$this->logger->warning($msg);
// in debug mode log full exception with a trace
$this->logger->debug($e);
} finally {
$progressBar->advance(1);
}
}
}
}
9 changes: 9 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/commands.yml
@@ -0,0 +1,9 @@
services:
ezpublish.console.command.regenerate_url_aliases:
class: eZ\Bundle\EzPublishCoreBundle\Command\RegenerateUrlAliasesCommand
arguments:
# intentionally passing inner repository to avoid sending Signals due to performance issues
- '@ezpublish.api.inner_repository'
- '@?logger'
tags:
- { name: console.command }
1 change: 1 addition & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/papi.yml
Expand Up @@ -31,6 +31,7 @@ services:
- "@ezpublish.field_type_nameable_collection.factory"
- "%ezpublish.api.inner_repository.class%"
- "%ezpublish.api.role.policy_map%"
- "@?logger"
calls:
- [setContainer, ["@service_container"]]

Expand Down
3 changes: 3 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml
@@ -1,3 +1,6 @@
imports:
- { resource: commands.yml }

parameters:
ezpublish.siteaccess.class: eZ\Publish\Core\MVC\Symfony\SiteAccess
ezpublish.siteaccess.default.name: default
Expand Down
Expand Up @@ -24,6 +24,9 @@
use Exception;
use PDO;

/**
* @deprecated since 6.7.8, use the ezplatform:urls:regenerate-aliases command instead.
*/
class RegenerateUrlAliasesCommand extends ContainerAwareCommand
{
const MIGRATION_TABLE = '__migration_ezurlalias_ml';
Expand Down Expand Up @@ -79,7 +82,7 @@ protected function configure()
$this
->setName('ezplatform:regenerate:legacy_storage_url_aliases')
->setDescription(
'Regenerates Location URL aliases (autogenerated) and migrates custom Location ' .
'[DEPRECATED] Regenerates Location URL aliases (autogenerated) and migrates custom Location ' .
'and global URL aliases with Legacy Storage Engine'
)
->addArgument(
Expand All @@ -95,6 +98,7 @@ protected function configure()
)
->setHelp(
<<<EOT
<error>This command is deprecated, use the ezplatform:urls:regenerate-aliases command instead.</error>
The command <info>%command.name%</info> regenerates URL aliases for Locations
and migrates existing custom Location and global URL aliases to a separate database table. Separate
table must be named '__migration_ezurlalias_ml' and should be created manually to be identical (but
Expand Down Expand Up @@ -122,6 +126,15 @@ protected function configure()

protected function execute(InputInterface $input, OutputInterface $output)
{
@trigger_error(
sprintf(
'%s is deprecated since 6.7.8. Use the ezplatform:urls:regenerate-aliases command instead.',
$this->getName()
),
E_USER_DEPRECATED
);
$output->writeln('<error>This command is deprecated, use the ezplatform:urls:regenerate-aliases command instead.</error>');

$this->checkStorage();

$action = $input->getArgument('action');
Expand Down
17 changes: 17 additions & 0 deletions eZ/Publish/API/Repository/ContentService.php
Expand Up @@ -150,6 +150,23 @@ public function loadContent($contentId, array $languages = null, $versionNo = nu
*/
public function loadContentByRemoteId($remoteId, array $languages = null, $versionNo = null, $useAlwaysAvailable = true);

/**
* Bulk-load Content items by the list of ContentInfo Value Objects.
*
* Note: it does not throw exceptions on load, just ignores erroneous Content item.
* Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is
* allowed to access every Content on the list.
*
* @param \eZ\Publish\API\Repository\Values\Content\ContentInfo[] $contentInfoList
* @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on
* returned value object. If not given all languages are returned.
* @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true,
* unless all languages have been asked for.
*
* @return \eZ\Publish\API\Repository\Values\Content\Content[] list of Content items with Content Ids as keys
*/
public function loadContentListByContentInfo(array $contentInfoList, array $languages = [], $useAlwaysAvailable = true);

/**
* Creates a new content draft assigned to the authenticated user.
*
Expand Down
19 changes: 19 additions & 0 deletions eZ/Publish/API/Repository/LocationService.php
Expand Up @@ -212,4 +212,23 @@ public function newLocationCreateStruct($parentLocationId);
* @return \eZ\Publish\API\Repository\Values\Content\LocationUpdateStruct
*/
public function newLocationUpdateStruct();

/**
* Get the total number of all existing Locations. Can be combined with loadAllLocations.
*
* @see loadAllLocations
*
* @return int Total number of Locations
*/
public function getAllLocationsCount(): int;

/**
* Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
*
* @param int $limit
* @param int $offset
*
* @return \eZ\Publish\API\Repository\Values\Content\Location[]
*/
public function loadAllLocations(int $offset = 0, int $limit = 25): array;
}

0 comments on commit c31c285

Please sign in to comment.