From d84dca51fa2b53a14f26abea485fd8e3720da9da Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Jan 2021 12:50:00 +0100 Subject: [PATCH 1/4] Implement 'page-fieldtype-cleanup' command --- .../Command/PageFieldTypeCleanupCommand.php | 188 ++++++++++++++++++ src/bundle/Resources/config/services.yml | 7 + 2 files changed, 195 insertions(+) create mode 100644 src/bundle/Command/PageFieldTypeCleanupCommand.php diff --git a/src/bundle/Command/PageFieldTypeCleanupCommand.php b/src/bundle/Command/PageFieldTypeCleanupCommand.php new file mode 100644 index 0000000..14e6f1e --- /dev/null +++ b/src/bundle/Command/PageFieldTypeCleanupCommand.php @@ -0,0 +1,188 @@ +connection = $connection; + $this->pageFieldTypeGateway = $pageFieldTypeGateway; + + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('ezplatform:page-fieldtype-cleanup') + ->setDescription( + 'This command allows you to search your database for orphaned page fieldtype related records + and clean them up.' + ) + ->setHelp( + <<%command.name% allows you to check your database for orphaned records related to the Page Fieldtype +and clean those records if chosen to do so. + +After running command it is recommended to regenerate URL aliases, clear persistence cache and reindex. + +!As the script directly modifies the Database always perform a backup before running it! + +EOT + ); + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->io = new SymfonyStyle($input, $output); + + parent::initialize($input, $output); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($this->pageFieldTypeGateway === null) { + $this->io->warning('Page FieldType bundle is missing. Cannot continue.'); + + return 0; + } + + $this->io->title('eZ Platform Database Health Checker'); + $this->io->text( + sprintf('Using database: %s', $this->connection->getDatabase()) + ); + + $this->io->warning('Always perform the database backup before running this command!'); + + if (!$this->io->confirm( + 'Are you sure that you want to proceed and that you have created the database backup?', + false) + ) { + return 0; + } + + if (!$this->countOrphanedPages() > 0) { + return 0; + } + + $this->deleteOrphanedPagesRelations(); + + $this->io->success('Done'); + + return 0; + } + + private function countOrphanedPages(): int + { + $pagesQuery = $this->connection->createQueryBuilder(); + $pagesQuery = $pagesQuery->select('id') + ->from('ezpage_pages') + ->getSQL(); + + $countQuery = $this->connection->createQueryBuilder(); + $countQuery->select('COUNT(page_id)') + ->from('ezpage_map_zones_pages', 'p') + ->where( + $countQuery->expr()->notIn( + 'page_id', + $pagesQuery + ) + ); + + $count = (int) $countQuery->execute()->fetch(FetchMode::NUMERIC)[0]; + + if ($count <= 0) { + $this->io->success('Found: 0'); + + return $count; + } + + $this->io->caution(sprintf('Found: %d orphaned pages', $count)); + + return $count; + } + + private function deleteOrphanedPagesRelations(): void + { + if (!$this->io->confirm( + sprintf('Are you sure that you want to proceed? The maximum number of pages that will be cleaned + in first iteration is equal to %d.', self::PAGE_LIMIT), + false) + ) { + return; + } + + $pagesQuery = $this->connection->createQueryBuilder(); + $pagesQuery = $pagesQuery->select('id') + ->from('ezpage_pages') + ->getSQL(); + + $orphanedPagesQuery = $this->connection->createQueryBuilder(); + $orphanedPagesQuery->select('page_id') + ->from('ezpage_map_zones_pages', 'p') + ->where( + $orphanedPagesQuery->expr()->notIn( + 'page_id', + $pagesQuery + ) + ) + ->setMaxResults(self::PAGE_LIMIT); + + $records = $orphanedPagesQuery->execute()->fetchAll(FetchMode::COLUMN); + + $progressBar = $this->io->createProgressBar(count($records)); + + for ($i = 0; $i < self::PAGE_LIMIT; ++$i) { + if (isset($records[$i])) { + $progressBar->advance(1); + $this->removePage((int) $records[$i]); + } + } + } + + private function removePage(int $pageId): void + { + $removedBlocks = []; + $removedZones = []; + + foreach ($this->pageFieldTypeGateway->loadAttributesAssignedToPage($pageId) as $attribute) { + $this->pageFieldTypeGateway->unassignAttributeFromBlock((int) $attribute['id'], (int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeAttribute((int) $attribute['id']); + + if (!\in_array($attribute['block_id'], $removedBlocks, true)) { + $this->pageFieldTypeGateway->unassignBlockFromZone((int) $attribute['block_id'], (int) $attribute['zone_id']); + $this->pageFieldTypeGateway->removeBlock((int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeBlockDesign((int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeBlockVisibility((int) $attribute['block_id']); + $removedBlocks[] = $attribute['block_id']; + } + + if (!\in_array($attribute['zone_id'], $removedZones, true)) { + $this->pageFieldTypeGateway->unassignZoneFromPage((int) $attribute['zone_id'], $pageId); + $this->pageFieldTypeGateway->removeZone((int) $attribute['zone_id']); + } + } + } +} diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index d377573..ae7f908 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -19,6 +19,13 @@ services: tags: - { name: 'console.command', command: 'ezplatform:database-health-check' } + MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\Command\PageFieldTypeCleanupCommand: + arguments: + $connection: '@ezpublish.persistence.connection' + $pageFieldTypeGateway: '@?EzSystems\EzPlatformPageFieldType\FieldType\Page\Storage\DoctrineGateway' + tags: + - { name: 'console.command', command: 'ezplatform:page-fieldtype-cleanup' } + cache.null: class: Symfony\Component\Cache\Adapter\NullAdapter arguments: [~] From 31c99906802e4021afbf3b748b977d0f1dc64bf9 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Jan 2021 13:16:09 +0100 Subject: [PATCH 2/4] Update README --- README.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 76a8b4d..5d738f2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,26 @@ ## Description -This bundle allows you to check your database against know database corruption and fixes them. +This bundle allows you to check your database against know database corruption and fix them. Also, you can perform a Smoke Test on your project to determine if all Contents are accessible (ignoring permissions). +The additional functionality is cleaning up your database from leftovers left by `ezplatform-page-fieldtype` bundle. ### Supported database corruptions: - Content without version (fixed by removing corrupted content) - Content without attributes (fixed by removing corrupted content) - Content with duplicated attributes (fixed by removing duplicated attributes) +- Page FieldType related records which are unnecessary and cause flooding ## Usage +The following bundle introduces two commands: `ezplatform:database-health-check` and `ezplatform:page-fieldtype-cleanup`. + +*Fixing corruptions will modify your database! Always perform the database backup before running those commands!* + +After running those commands it is recommended to [regenerate URL aliases](https://doc.ezplatform.com/en/2.5/guide/url_management/#regenerating-url-aliases), clear persistence cache and [reindex](https://doc.ezplatform.com/en/2.5/guide/search/#reindexing). + +### ezplatform:database-health-check Bundle adds `db-checker` SiteAccess with `cache_pool` set to [NullAdapter](https://github.com/symfony/symfony/blob/3.4/src/Symfony/Component/Cache/Adapter/NullAdapter.php) so no SPI cache is used when retrieving Content from the database during Smoke Test. If corruption is found, you will be asked if you want to fix it. -*Fixing corruption will modify your database! Always perform the database backup before running this command!* - All Content's location will be checked for subitems, before removing it. In the case of existing subitems, you will be presented with an option to swap location with a different one, so subitems are preserved (Content won't be deleted after swap so script has to be re-run if you wish to delete corrupted Content). @@ -23,7 +30,13 @@ php -d memory_limit=-1 bin/console ezplatform:database-health-check --siteaccess ``` Please note that Command may run for a long time (depending on project size). You can speed it up by skipping Smoke Testing with `--skip-smoke-test` option. -After running command is recommended to [regenerate URL aliases](https://doc.ezplatform.com/en/2.5/guide/url_management/#regenerating-url-aliases), clear persistence cache and [reindex](https://doc.ezplatform.com/en/2.5/guide/search/#reindexing). +### ezplatform:page-fieldtype-cleanup +This command searches your database for `ezpage_*` records which are leftovers from https://issues.ibexa.co/browse/EZEE-3430 +and deletes them if necessary to prevent uncontrolled growth of the database. + +``` +php -d memory_limit=-1 bin/console ezplatform:page-fieldtype-cleanup +``` ## Installation ### Requirements @@ -32,7 +45,7 @@ This bundle requires eZ Platform 2.5+ ### 1. Enable `EzPlatformDatabaseHealthCheckerBundle` Edit `app/AppKernel.php`, and add ``` - $bundles[] = new MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\EzPlatformDatabaseHealthCheckerBundle(); +$bundles[] = new MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\EzPlatformDatabaseHealthCheckerBundle(); ``` at the end of list of bundles in `dev` environment. From 5ce6c9fe16c70a90b1412f0f22a0209b671ea80c Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Jan 2021 13:21:41 +0100 Subject: [PATCH 3/4] Add additional warning for `ezplatform:page-fieldtype-cleanup` command --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5d738f2..3dd03b9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ php -d memory_limit=-1 bin/console ezplatform:database-health-check --siteaccess Please note that Command may run for a long time (depending on project size). You can speed it up by skipping Smoke Testing with `--skip-smoke-test` option. ### ezplatform:page-fieldtype-cleanup +*Warning! This command is only available for Enterprise versions of the platform.* + This command searches your database for `ezpage_*` records which are leftovers from https://issues.ibexa.co/browse/EZEE-3430 and deletes them if necessary to prevent uncontrolled growth of the database. From d4bcacd4c160348412c500cbaa3bba2a30a85942 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Jan 2021 16:19:44 +0100 Subject: [PATCH 4/4] Separate PageFieldTypeGateway from the command --- .../Command/DatabaseHealthCheckCommand.php | 19 ++-- .../Command/PageFieldTypeCleanupCommand.php | 101 ++++-------------- src/bundle/Resources/config/services.yml | 9 +- .../Content/Gateway/DoctrineDatabase.php | 2 +- .../Gateway/PageFieldTypeDoctrineDatabase.php | 89 +++++++++++++++ .../Gateway/PageFieldTypeGatewayInterface.php | 12 +++ 6 files changed, 133 insertions(+), 99 deletions(-) create mode 100644 src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeDoctrineDatabase.php create mode 100644 src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeGatewayInterface.php diff --git a/src/bundle/Command/DatabaseHealthCheckCommand.php b/src/bundle/Command/DatabaseHealthCheckCommand.php index 18df93e..2c9c0c4 100644 --- a/src/bundle/Command/DatabaseHealthCheckCommand.php +++ b/src/bundle/Command/DatabaseHealthCheckCommand.php @@ -4,8 +4,6 @@ namespace MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\Command; -use Doctrine\DBAL\Connection; -use eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider; use eZ\Publish\API\Repository\ContentService; use eZ\Publish\API\Repository\LocationService; use eZ\Publish\API\Repository\PermissionResolver; @@ -59,9 +57,6 @@ class DatabaseHealthCheckCommand extends Command /** @var \Symfony\Component\Console\Style\SymfonyStyle */ private $io; - /** @var \eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider */ - private $repositoryConfigurationProvider; - public function __construct( ContentGateway $contentGateway, ContentService $contentService, @@ -69,8 +64,7 @@ public function __construct( SiteAccess $siteAccess, PermissionResolver $permissionResolver, Handler $handler, - Repository $repository, - Connection $connection + Repository $repository ) { $this->contentGateway = $contentGateway; $this->contentService = $contentService; @@ -79,7 +73,6 @@ public function __construct( $this->permissionResolver = $permissionResolver; $this->persistenceHandler = $handler; $this->repository = $repository; - $this->connection = $connection; parent::__construct(); } @@ -128,11 +121,11 @@ protected function initialize(InputInterface $input, OutputInterface $output): v parent::initialize($input, $output); } - protected function execute(InputInterface $input, OutputInterface $output): void + protected function execute(InputInterface $input, OutputInterface $output): ?int { $this->io->title('eZ Platform Database Health Checker'); $this->io->text( - sprintf('Using database: %s', $this->connection->getDatabase()) + sprintf('Using database: %s', $this->contentGateway->connection->getDatabase()) ); $this->io->warning( @@ -140,7 +133,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void ); if (!$this->io->confirm('Are you sure that you want to proceed?', false)) { - return; + return 0; } if ($this->siteAccess->name !== 'db-checker') { @@ -148,7 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output): void 'It is recommended to run this command in "db-checker" SiteAccess. Are you sure that you want ' . 'to continue?', false)) { - return; + return 0; } } @@ -161,6 +154,8 @@ protected function execute(InputInterface $input, OutputInterface $output): void $this->checkDuplicatedAttributes(); $this->io->success('Done'); + + return 0; } private function checkContentWithoutAttributes(InputInterface $input, OutputInterface $output) diff --git a/src/bundle/Command/PageFieldTypeCleanupCommand.php b/src/bundle/Command/PageFieldTypeCleanupCommand.php index 14e6f1e..825620d 100644 --- a/src/bundle/Command/PageFieldTypeCleanupCommand.php +++ b/src/bundle/Command/PageFieldTypeCleanupCommand.php @@ -4,9 +4,7 @@ namespace MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\Command; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\FetchMode; -use EzSystems\EzPlatformPageFieldType\FieldType\Page\Storage\Gateway; +use MateuszBieniek\EzPlatformDatabaseHealthChecker\Persistence\Legacy\Content\Gateway\PageFieldTypeGatewayInterface as Gateway; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -16,19 +14,15 @@ class PageFieldTypeCleanupCommand extends Command { const PAGE_LIMIT = 100; - /** @var \Doctrine\DBAL\Connection */ - private $connection; - - /** @var \EzSystems\EzPlatformPageFieldType\FieldType\Page\Storage\Gateway|null */ - private $pageFieldTypeGateway; - /** @var \Symfony\Component\Console\Style\SymfonyStyle */ private $io; - public function __construct(Connection $connection, ?Gateway $pageFieldTypeGateway = null) + /** @var Gateway */ + private $gateway; + + public function __construct(Gateway $gateway) { - $this->connection = $connection; - $this->pageFieldTypeGateway = $pageFieldTypeGateway; + $this->gateway = $gateway; parent::__construct(); } @@ -63,7 +57,7 @@ protected function initialize(InputInterface $input, OutputInterface $output): v protected function execute(InputInterface $input, OutputInterface $output): int { - if ($this->pageFieldTypeGateway === null) { + if ($this->gateway->pageFieldTypeGateway === null) { $this->io->warning('Page FieldType bundle is missing. Cannot continue.'); return 0; @@ -71,7 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->io->title('eZ Platform Database Health Checker'); $this->io->text( - sprintf('Using database: %s', $this->connection->getDatabase()) + sprintf('Using database: %s', $this->gateway->connection->getDatabase()) ); $this->io->warning('Always perform the database backup before running this command!'); @@ -83,48 +77,29 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - if (!$this->countOrphanedPages() > 0) { + if ($this->countOrphanedPageRelations() <= 0) { return 0; } - $this->deleteOrphanedPagesRelations(); + $this->deleteOrphanedPageRelations(); $this->io->success('Done'); return 0; } - private function countOrphanedPages(): int + private function countOrphanedPageRelations(): int { - $pagesQuery = $this->connection->createQueryBuilder(); - $pagesQuery = $pagesQuery->select('id') - ->from('ezpage_pages') - ->getSQL(); - - $countQuery = $this->connection->createQueryBuilder(); - $countQuery->select('COUNT(page_id)') - ->from('ezpage_map_zones_pages', 'p') - ->where( - $countQuery->expr()->notIn( - 'page_id', - $pagesQuery - ) - ); - - $count = (int) $countQuery->execute()->fetch(FetchMode::NUMERIC)[0]; + $count = $this->gateway->countOrphanedPageRelations(); - if ($count <= 0) { - $this->io->success('Found: 0'); - - return $count; - } - - $this->io->caution(sprintf('Found: %d orphaned pages', $count)); + $count <= 0 + ? $this->io->success('Found: 0') + : $this->io->caution(sprintf('Found: %d orphaned pages', $count)); return $count; } - private function deleteOrphanedPagesRelations(): void + private function deleteOrphanedPageRelations(): void { if (!$this->io->confirm( sprintf('Are you sure that you want to proceed? The maximum number of pages that will be cleaned @@ -134,54 +109,14 @@ private function deleteOrphanedPagesRelations(): void return; } - $pagesQuery = $this->connection->createQueryBuilder(); - $pagesQuery = $pagesQuery->select('id') - ->from('ezpage_pages') - ->getSQL(); - - $orphanedPagesQuery = $this->connection->createQueryBuilder(); - $orphanedPagesQuery->select('page_id') - ->from('ezpage_map_zones_pages', 'p') - ->where( - $orphanedPagesQuery->expr()->notIn( - 'page_id', - $pagesQuery - ) - ) - ->setMaxResults(self::PAGE_LIMIT); - - $records = $orphanedPagesQuery->execute()->fetchAll(FetchMode::COLUMN); + $records = $this->gateway->getOrphanedPageRelations(self::PAGE_LIMIT); $progressBar = $this->io->createProgressBar(count($records)); for ($i = 0; $i < self::PAGE_LIMIT; ++$i) { if (isset($records[$i])) { $progressBar->advance(1); - $this->removePage((int) $records[$i]); - } - } - } - - private function removePage(int $pageId): void - { - $removedBlocks = []; - $removedZones = []; - - foreach ($this->pageFieldTypeGateway->loadAttributesAssignedToPage($pageId) as $attribute) { - $this->pageFieldTypeGateway->unassignAttributeFromBlock((int) $attribute['id'], (int) $attribute['block_id']); - $this->pageFieldTypeGateway->removeAttribute((int) $attribute['id']); - - if (!\in_array($attribute['block_id'], $removedBlocks, true)) { - $this->pageFieldTypeGateway->unassignBlockFromZone((int) $attribute['block_id'], (int) $attribute['zone_id']); - $this->pageFieldTypeGateway->removeBlock((int) $attribute['block_id']); - $this->pageFieldTypeGateway->removeBlockDesign((int) $attribute['block_id']); - $this->pageFieldTypeGateway->removeBlockVisibility((int) $attribute['block_id']); - $removedBlocks[] = $attribute['block_id']; - } - - if (!\in_array($attribute['zone_id'], $removedZones, true)) { - $this->pageFieldTypeGateway->unassignZoneFromPage((int) $attribute['zone_id'], $pageId); - $this->pageFieldTypeGateway->removeZone((int) $attribute['zone_id']); + $this->gateway->removePage((int) $records[$i]); } } } diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index ae7f908..ef04af2 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -6,6 +6,11 @@ services: $locationGateway: '@ezpublish.persistence.legacy.location.gateway' $fieldHandler: '@ezpublish.persistence.legacy.field_handler' + MateuszBieniek\EzPlatformDatabaseHealthChecker\Persistence\Legacy\Content\Gateway\PageFieldTypeDoctrineDatabase: + arguments: + $connection: '@ezpublish.persistence.connection' + $pageFieldTypeGateway: '@?EzSystems\EzPlatformPageFieldType\FieldType\Page\Storage\DoctrineGateway' + MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\Command\DatabaseHealthCheckCommand: arguments: $contentGateway: '@MateuszBieniek\EzPlatformDatabaseHealthChecker\Persistence\Legacy\Content\Gateway\DoctrineDatabase' @@ -15,14 +20,12 @@ services: $permissionResolver: '@eZ\Publish\API\Repository\PermissionResolver' $handler: '@ezpublish.api.storage_engine' $repository: '@ezpublish.api.repository' - $connection: '@ezpublish.persistence.connection' tags: - { name: 'console.command', command: 'ezplatform:database-health-check' } MateuszBieniek\EzPlatformDatabaseHealthCheckerBundle\Command\PageFieldTypeCleanupCommand: arguments: - $connection: '@ezpublish.persistence.connection' - $pageFieldTypeGateway: '@?EzSystems\EzPlatformPageFieldType\FieldType\Page\Storage\DoctrineGateway' + $gateway: '@MateuszBieniek\EzPlatformDatabaseHealthChecker\Persistence\Legacy\Content\Gateway\PageFieldTypeDoctrineDatabase' tags: - { name: 'console.command', command: 'ezplatform:page-fieldtype-cleanup' } diff --git a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php index 8403406..529a819 100644 --- a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php @@ -17,7 +17,7 @@ class DoctrineDatabase implements GatewayInterface /** * @var \Doctrine\DBAL\Connection */ - protected $connection; + public $connection; /** * @var ContentGateway diff --git a/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeDoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeDoctrineDatabase.php new file mode 100644 index 0000000..1abf1a1 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeDoctrineDatabase.php @@ -0,0 +1,89 @@ +connection = $connection; + $this->pageFieldTypeGateway = $pageFieldTypeGateway; + } + + public function countOrphanedPageRelations(): int + { + $pagesQuery = $this->connection->createQueryBuilder(); + $pagesQuery = $pagesQuery->select('id') + ->from('ezpage_pages') + ->getSQL(); + + $countQuery = $this->connection->createQueryBuilder(); + $countQuery->select('COUNT(page_id)') + ->from('ezpage_map_zones_pages', 'p') + ->where( + $countQuery->expr()->notIn( + 'page_id', + $pagesQuery + ) + ); + + return (int) $countQuery->execute()->fetch(FetchMode::NUMERIC)[0]; + } + + public function getOrphanedPageRelations(int $limit): array + { + $pagesQuery = $this->connection->createQueryBuilder(); + $pagesQuery = $pagesQuery->select('id') + ->from('ezpage_pages') + ->getSQL(); + + $orphanedPagesQuery = $this->connection->createQueryBuilder(); + $orphanedPagesQuery->select('page_id') + ->from('ezpage_map_zones_pages', 'p') + ->where( + $orphanedPagesQuery->expr()->notIn( + 'page_id', + $pagesQuery + ) + ) + ->setMaxResults($limit); + + return $orphanedPagesQuery->execute()->fetchAll(FetchMode::COLUMN); + } + + public function removePage(int $pageId): void + { + $removedBlocks = []; + $removedZones = []; + + foreach ($this->pageFieldTypeGateway->loadAttributesAssignedToPage($pageId) as $attribute) { + $this->pageFieldTypeGateway->unassignAttributeFromBlock((int) $attribute['id'], (int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeAttribute((int) $attribute['id']); + + if (!\in_array($attribute['block_id'], $removedBlocks, true)) { + $this->pageFieldTypeGateway->unassignBlockFromZone((int) $attribute['block_id'], (int) $attribute['zone_id']); + $this->pageFieldTypeGateway->removeBlock((int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeBlockDesign((int) $attribute['block_id']); + $this->pageFieldTypeGateway->removeBlockVisibility((int) $attribute['block_id']); + $removedBlocks[] = $attribute['block_id']; + } + + if (!\in_array($attribute['zone_id'], $removedZones, true)) { + $this->pageFieldTypeGateway->unassignZoneFromPage((int) $attribute['zone_id'], $pageId); + $this->pageFieldTypeGateway->removeZone((int) $attribute['zone_id']); + } + } + } +} diff --git a/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeGatewayInterface.php b/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeGatewayInterface.php new file mode 100644 index 0000000..826cbef --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Gateway/PageFieldTypeGatewayInterface.php @@ -0,0 +1,12 @@ +