From b1c9485fdfac8bc5e467ca7c6fbb6fad92133d90 Mon Sep 17 00:00:00 2001 From: Vidar Date: Fri, 26 Oct 2018 11:16:24 +0200 Subject: [PATCH 1/2] DB gateway (#81) * Moved commands' db query functions to dedicated Gateway service * Fixed regression in 'PostgreSQL do not support alias in update queries (#78)' commit * Added tests for DB gateway service * Added Gateway tests on Travis * Added kernel 1.13 tests on Travis --- .travis.yml | 25 +- bin/.travis/prepare_unittest.sh | 29 + .../ConvertXmlTextToRichTextCommand.php | 148 +----- bundle/Command/ImportXmlCommand.php | 133 +---- bundle/Resources/config/services.yml | 9 +- .../Legacy/ContentModelGateway.php | 287 ++++++++++ phpunit-integration-legacy-empty-db.xml | 27 + phpunit.xml | 1 + .../FieldType/Persistence/Legacy/BaseTest.php | 82 +++ .../Legacy/ContentModelGatewayTest.php | 496 ++++++++++++++++++ .../Legacy/_fixtures/contentclass.php | 66 +++ .../_fixtures/contentclass_attribute.php | 93 ++++ .../_fixtures/contentobject_attribute.php | 157 ++++++ .../Legacy/_fixtures/setval.pgsql.sql | 30 ++ .../LegacyEmptyDBSetupFactory.php | 61 +++ 15 files changed, 1382 insertions(+), 262 deletions(-) create mode 100755 bin/.travis/prepare_unittest.sh create mode 100644 lib/FieldType/XmlText/Persistence/Legacy/ContentModelGateway.php create mode 100644 phpunit-integration-legacy-empty-db.xml create mode 100644 tests/lib/FieldType/Persistence/Legacy/BaseTest.php create mode 100644 tests/lib/FieldType/Persistence/Legacy/ContentModelGatewayTest.php create mode 100644 tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass.php create mode 100644 tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass_attribute.php create mode 100644 tests/lib/FieldType/Persistence/Legacy/_fixtures/contentobject_attribute.php create mode 100644 tests/lib/FieldType/Persistence/Legacy/_fixtures/setval.pgsql.sql create mode 100644 tests/lib/SetupFactory/LegacyEmptyDBSetupFactory.php diff --git a/.travis.yml b/.travis.yml index 93de545f..38147953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,17 @@ language: php +services: + - mysql + - postgresql + +# Mysql isn't installed on trusty (only client is), so we need to specifically install it +addons: + apt: + packages: + - mysql-server-5.6 + - mysql-client-core-5.6 + - mysql-client-5.6 + matrix: include: - php: 5.6 @@ -14,12 +26,23 @@ matrix: env: TEST_CONFIG="phpunit-integration-legacy-solr.xml" SOLR_VERSION="4.10.4" CORES_SETUP="single" SOLR_CORES="collection1" SOLR_CONFIG="vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/schema.xml vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/custom-fields-types.xml vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/language-fieldtypes.xml" - php: 7.1 env: TEST_CONFIG="phpunit-integration-legacy-solr.xml" SOLR_VERSION="6.6.0" CORES_SETUP="shared" SOLR_CONFIG="vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/schema.xml vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/custom-fields-types.xml vendor/ezsystems/ezplatform-solr-search-engine/lib/Resources/config/solr/language-fieldtypes.xml" + - php: 7.1 + env: TEST_CONFIG="phpunit-integration-legacy-empty-db.xml" DB="postgresql" DATABASE="pgsql://postgres@localhost/testdb" + - php: 7.1 + env: TEST_CONFIG="phpunit-integration-legacy-empty-db.xml" DB="mysql" DATABASE="mysql://root@localhost/testdb" + - php: 7.0 + env: TEST_CONFIG="phpunit.xml" COMPOSER_REQUIRE="ezsystems/ezpublish-kernel:^6.13.6@dev" # test only master (+ Pull requests) branches: only: - master + +# setup requirements for running db tests +before_install: + - if [ "$TEST_CONFIG" != "" ] ; then ./bin/.travis/prepare_unittest.sh ; fi + before_script: # Disable memory_limit for composer - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini @@ -30,7 +53,7 @@ before_script: script: - php vendor/bin/phpunit --bootstrap tests/bootstrap.php -c $TEST_CONFIG - - if [ "$CHECK_CS" == "true" ]; then phpenv config-rm xdebug.ini && ./vendor/bin/php-cs-fixer fix -v --dry-run --diff --show-progress=estimating; fi + - if [ "$CHECK_CS" == "true" ]; then ./vendor/bin/php-cs-fixer fix -v --dry-run --diff --show-progress=estimating; fi notifications: email: false diff --git a/bin/.travis/prepare_unittest.sh b/bin/.travis/prepare_unittest.sh new file mode 100755 index 00000000..dd16bc35 --- /dev/null +++ b/bin/.travis/prepare_unittest.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# File for setting up system for unit/integration testing + +# Disable xdebug to speed things up as we don't currently generate coverge on travis +# And make sure we use UTF-8 encoding +phpenv config-rm xdebug.ini +echo "default_charset = UTF-8" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + +# Setup DB +if [ "$DB" = "mysql" ] ; then + # https://github.com/travis-ci/travis-ci/issues/3049 + # make sure we don't run out of entropy apparently (see link above) + sudo apt-get -y install haveged + sudo service haveged start + # make tmpfs and run MySQL on it for reasonable performance + sudo mkdir /mnt/ramdisk + sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk + sudo stop mysql + sudo mv /var/lib/mysql /mnt/ramdisk + sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql + sudo start mysql + # Install test db + mysql -e "CREATE DATABASE IF NOT EXISTS testdb DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;" -uroot +fi +if [ "$DB" = "postgresql" ] ; then psql -c "CREATE DATABASE testdb;" -U postgres ; psql -c "CREATE EXTENSION pgcrypto;" -U postgres testdb ; fi + +if [ "$COMPOSER_REQUIRE" != "" ] ; then composer require --no-update $COMPOSER_REQUIRE ; fi +#travis_retry composer update --no-progress --no-interaction $COMPSER_ARGS diff --git a/bundle/Command/ConvertXmlTextToRichTextCommand.php b/bundle/Command/ConvertXmlTextToRichTextCommand.php index b648e4e9..f87fd86e 100644 --- a/bundle/Command/ConvertXmlTextToRichTextCommand.php +++ b/bundle/Command/ConvertXmlTextToRichTextCommand.php @@ -15,7 +15,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use eZ\Publish\Core\FieldType\XmlText\Value; use eZ\Publish\Core\FieldType\XmlText\Converter\RichText as RichTextConverter; -use Doctrine\DBAL\Connection; +use eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway as Gateway; use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\ProcessBuilder; @@ -27,9 +27,9 @@ class ConvertXmlTextToRichTextCommand extends ContainerAwareCommand const DEFAULT_REPOSITORY_USER = 'admin'; /** - * @var \Doctrine\DBAL\Connection + * @var \eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway */ - private $dbal; + private $gateway; /** * @var \Psr\Log\LoggerInterface @@ -81,11 +81,11 @@ class ConvertXmlTextToRichTextCommand extends ContainerAwareCommand */ protected $kernelCacheDir; - public function __construct(Connection $dbal, RichTextConverter $converter, $kernelCacheDir, LoggerInterface $logger) + public function __construct(Gateway $gateway, RichTextConverter $converter, $kernelCacheDir, LoggerInterface $logger) { parent::__construct(); - $this->dbal = $dbal; + $this->gateway = $gateway; $this->converter = $converter; $this->kernelCacheDir = $kernelCacheDir; $this->logger = $logger; @@ -258,7 +258,7 @@ protected function baseExecute(InputInterface $input, OutputInterface $output, & } else { $this->imageContentTypeIdentifiers = ['image']; } - $imageContentTypeIds = $this->getContentTypeIds($this->imageContentTypeIdentifiers); + $imageContentTypeIds = $this->gateway->getContentTypeIds($this->imageContentTypeIdentifiers); if (count($imageContentTypeIds) !== count($this->imageContentTypeIdentifiers)) { throw new RuntimeException('Unable to lookup all content type identifiers, not found: ' . implode(',', array_diff($this->imageContentTypeIdentifiers, array_keys($imageContentTypeIds)))); } @@ -378,7 +378,7 @@ protected function login() protected function fixEmbeddedImages($dryRun, $contentId, OutputInterface $output) { - $count = $this->getRowCountOfContentObjectAttributes('ezrichtext', $contentId); + $count = $this->gateway->getRowCountOfContentObjectAttributes('ezrichtext', $contentId); $output->writeln("Found $count field rows to convert."); @@ -387,7 +387,7 @@ protected function fixEmbeddedImages($dryRun, $contentId, OutputInterface $outpu do { $limit = self::MAX_OBJECTS_PER_CHILD; - $statement = $this->getFieldRows('ezrichtext', $contentId, $offset, $limit); + $statement = $this->gateway->getFieldRows('ezrichtext', $contentId, $offset, $limit); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { if (empty($row['data_text'])) { $inputValue = Value::EMPTY_VALUE; @@ -437,141 +437,21 @@ protected function fixEmbeddedImages($dryRun, $contentId, OutputInterface $outpu $output->writeln("Updated ezembed tags in $totalCount field(s)"); } - protected function convertFieldDefinitions($dryRun, OutputInterface $output) + protected function convertFieldDefinitions($dryRun, $output) { - $query = $this->dbal->createQueryBuilder(); - $query->select('count(a.id)') - ->from('ezcontentclass_attribute', 'a') - ->where( - $query->expr()->eq( - 'a.data_type_string', - ':datatypestring' - ) - ) - ->setParameter(':datatypestring', 'ezxmltext'); - - $statement = $query->execute(); - $count = (int) $statement->fetchColumn(); - + $count = $this->gateway->countContentTypeFieldsByFieldType('ezxmltext'); $output->writeln("Found $count field definiton to convert."); - $updateQuery = $this->dbal->createQueryBuilder(); - $updateQuery->update('ezcontentclass_attribute') - ->set('data_type_string', ':newdatatypestring') - // was tagPreset in ezxmltext, unused in RichText - ->set('data_text2', ':datatext2') - ->where( - $updateQuery->expr()->eq( - 'data_type_string', - ':olddatatypestring' - ) - ) - ->setParameters([ - ':newdatatypestring' => 'ezrichtext', - ':datatext2' => null, - ':olddatatypestring' => 'ezxmltext', - ]); - + $updateQuery = $this->gateway->getContentTypeFieldTypeUpdateQuery('ezxmltext', 'ezrichtext'); if (!$dryRun) { $updateQuery->execute(); } - $output->writeln("Converted $count ezxmltext field definitions to ezrichtext"); } - protected function getRowCountOfContentObjectAttributes($datatypeString, $contentId) - { - $query = $this->dbal->createQueryBuilder(); - $query->select('count(a.id)') - ->from('ezcontentobject_attribute', 'a') - ->where( - $query->expr()->eq( - 'a.data_type_string', - ':datatypestring' - ) - ) - ->setParameter(':datatypestring', $datatypeString); - - if ($contentId !== null) { - $query->andWhere( - $query->expr()->eq( - 'a.contentobject_id', - ':contentid' - ) - ) - ->setParameter(':contentid', $contentId); - } - - $statement = $query->execute(); - - return (int) $statement->fetchColumn(); - } - - /** - * Get the specified field rows. - * Note that if $contentId !== null, then $offset and $limit will be ignored. - * - * @param $datatypeString - * @param $contentId - * @param $offset - * @param $limit - * @return \Doctrine\DBAL\Driver\Statement|int - */ - protected function getFieldRows($datatypeString, $contentId, $offset, $limit) - { - $query = $this->dbal->createQueryBuilder(); - $query->select('a.*') - ->from('ezcontentobject_attribute', 'a') - ->where( - $query->expr()->eq( - 'a.data_type_string', - ':datatypestring' - ) - ) - ->orderBy('a.id') - ->setParameter(':datatypestring', $datatypeString); - - if ($contentId === null) { - $query->setFirstResult($offset) - ->setMaxResults($limit); - } else { - $query->andWhere( - $query->expr()->eq( - 'a.contentobject_id', - ':contentid' - ) - ) - ->setParameter(':contentid', $contentId); - } - - return $query->execute(); - } - protected function updateFieldRow($dryRun, $id, $version, $datatext) { - $updateQuery = $this->dbal->createQueryBuilder(); - $updateQuery->update('ezcontentobject_attribute') - ->set('data_type_string', ':datatypestring') - ->set('data_text', ':datatext') - ->where( - $updateQuery->expr()->eq( - 'id', - ':id' - ) - ) - ->andWhere( - $updateQuery->expr()->eq( - 'version', - ':version' - ) - ) - ->setParameters([ - ':datatypestring' => 'ezrichtext', - ':datatext' => $datatext, - ':id' => $id, - ':version' => $version, - ]); - + $updateQuery = $this->gateway->getUpdateFieldRowQuery($id, $version, $datatext); if (!$dryRun) { $updateQuery->execute(); } @@ -709,7 +589,7 @@ protected function dumpOnErrors($errors, $dataText, $contentobjectId, $contentob protected function convertFields($dryRun, $contentId, $checkDuplicateIds, $checkIdValues, $offset, $limit) { - $statement = $this->getFieldRows('ezxmltext', $contentId, $offset, $limit); + $statement = $this->gateway->getFieldRows('ezxmltext', $contentId, $offset, $limit); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { if (empty($row['data_text'])) { $inputValue = Value::EMPTY_VALUE; @@ -745,7 +625,7 @@ protected function convertFields($dryRun, $contentId, $checkDuplicateIds, $check protected function processFields($dryRun, $checkDuplicateIds, $checkIdValues, OutputInterface $output) { - $count = $this->getRowCountOfContentObjectAttributes('ezxmltext', null); + $count = $this->gateway->getRowCountOfContentObjectAttributes('ezxmltext', null); $output->writeln("Found $count field rows to convert."); $offset = 0; diff --git a/bundle/Command/ImportXmlCommand.php b/bundle/Command/ImportXmlCommand.php index 24889564..973a48dd 100644 --- a/bundle/Command/ImportXmlCommand.php +++ b/bundle/Command/ImportXmlCommand.php @@ -5,7 +5,6 @@ namespace EzSystems\EzPlatformXmlTextFieldTypeBundle\Command; use DOMDocument; -use PDO; use Psr\Log\LogLevel; use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; @@ -13,8 +12,8 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Doctrine\DBAL\Connection; use eZ\Publish\Core\FieldType\XmlText\Converter\RichText as RichTextConverter; +use eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway as Gateway; class ImportXmlCommand extends ContainerAwareCommand { @@ -24,9 +23,9 @@ class ImportXmlCommand extends ContainerAwareCommand private $converter; /** - * @var \Doctrine\DBAL\Connection + * @var \eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway */ - private $dbal; + private $gateway; /** * @var string @@ -43,11 +42,11 @@ class ImportXmlCommand extends ContainerAwareCommand */ private $output; - public function __construct(Connection $dbal, RichTextConverter $converter) + public function __construct(Gateway $gateway, RichTextConverter $converter) { parent::__construct(); + $this->gateway = $gateway; $this->converter = $converter; - $this->dbal = $dbal; $this->exportDir = null; } @@ -113,7 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } else { $this->imageContentTypeIdentifiers = ['image']; } - $imageContentTypeIds = $this->getContentTypeIds($this->imageContentTypeIdentifiers); + $imageContentTypeIds = $this->gateway->getContentTypeIds($this->imageContentTypeIdentifiers); if (count($imageContentTypeIds) !== count($this->imageContentTypeIdentifiers)) { throw new RuntimeException('Unable to lookup all content type identifiers, not found : ' . implode(',', array_diff($this->imageContentTypeIdentifiers, array_keys($imageContentTypeIds)))); } @@ -122,31 +121,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->importDumps($dryRun, $contentObjectId); } - protected function getContentTypeIds($contentTypeIdentifiers) - { - $query = $this->dbal->createQueryBuilder(); - - $query->select('c.identifier, c.id') - ->from('ezcontentclass', 'c') - ->where( - $query->expr()->in( - 'c.identifier', - ':contentTypeIdentifiers' - ) - ) - ->setParameter(':contentTypeIdentifiers', $contentTypeIdentifiers, Connection::PARAM_STR_ARRAY); - - $statement = $query->execute(); - - $columns = $statement->fetchAll(PDO::FETCH_ASSOC); - $result = []; - foreach ($columns as $column) { - $result[$column['identifier']] = $column['id']; - } - - return $result; - } - protected function importDumps($dryRun, $contentObjectId = null) { foreach (new \DirectoryIterator($this->exportDir) as $dirItem) { @@ -211,97 +185,6 @@ protected function validateConversion(DOMDocument $xmlDoc, $filename, $attribute return $result; } - protected function contentObjectAttributeExists($objectId, $attributeId, $version, $language) - { - $query = $this->dbal->createQueryBuilder(); - $query->select('count(a.id)') - ->from('ezcontentobject_attribute', 'a') - ->where( - $query->expr()->eq( - 'a.data_type_string', - ':datatypestring' - ) - ) - ->andWhere( - $query->expr()->eq( - 'a.contentobject_id', - ':objectid' - ) - ) - ->andWhere( - $query->expr()->eq( - 'a.id', - ':attributeid' - ) - ) - ->andWhere( - $query->expr()->eq( - 'a.version', - ':version' - ) - ) - ->andWhere( - $query->expr()->eq( - 'a.language_code', - ':language' - ) - ) - ->setParameter(':datatypestring', 'ezxmltext') - ->setParameter(':objectid', $objectId) - ->setParameter(':attributeid', $attributeId) - ->setParameter(':version', $version) - ->setParameter(':language', $language); - - $statement = $query->execute(); - $count = (int)$statement->fetchColumn(); - - return $count === 1; - } - - protected function updateContentObjectAttribute($xml, $objectId, $attributeId, $version, $language) - { - $updateQuery = $this->dbal->createQueryBuilder(); - $updateQuery->update('ezcontentobject_attribute') - ->set('data_text', ':newxml') - ->where( - $updateQuery->expr()->eq( - 'data_type_string', - ':datatypestring' - ) - ) - ->andWhere( - $updateQuery->expr()->eq( - 'contentobject_id', - ':objectid' - ) - ) - ->andWhere( - $updateQuery->expr()->eq( - 'id', - ':attributeid' - ) - ) - ->andWhere( - $updateQuery->expr()->eq( - 'a.version', - ':version' - ) - ) - ->andWhere( - $updateQuery->expr()->eq( - 'a.language_code', - ':language' - ) - ) - ->setParameter(':newxml', $xml) - ->setParameter(':datatypestring', 'ezxmltext') - ->setParameter(':objectid', $objectId) - ->setParameter(':attributeid', $attributeId) - ->setParameter(':version', $version) - ->setParameter(':language', $language); - $updateQuery->execute(); - } - protected function createDocument($xmlString) { $document = new DOMDocument(); @@ -334,9 +217,9 @@ protected function importXml($dryRun, $filename, $objectId, $attributeId, $versi return; } - if ($this->contentObjectAttributeExists($objectId, $attributeId, $version, $language)) { + if ($this->gateway->contentObjectAttributeExists($objectId, $attributeId, $version, $language)) { if (!$dryRun) { - $this->updateContentObjectAttribute($xmlDoc->saveXML(), $objectId, $attributeId, $version, $language); + $this->gateway->updateContentObjectAttribute($xmlDoc->saveXML(), $objectId, $attributeId, $version, $language); } } else { $this->output->writeln("Warning: The file $filename doesn't match any contentobject attribute stored in the database, skipping"); diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index 60e86762..5de42004 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -7,10 +7,15 @@ services: tags: - { name: ezpublish_rest.field_type_processor, alias: ezxmltext } + ezxmltext.persistence.legacy.content_model_gateway: + class: eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway + arguments: + - "@ezpublish.persistence.connection" + ezxmltext.command.convert_to_richtext: class: EzSystems\EzPlatformXmlTextFieldTypeBundle\Command\ConvertXmlTextToRichTextCommand arguments: - - "@ezpublish.persistence.connection" + - "@ezxmltext.persistence.legacy.content_model_gateway" - "@ezxmltext.richtext_converter" - "%kernel.cache_dir%" - "@?logger" @@ -26,7 +31,7 @@ services: ezxmltext.command.import_xml: class: EzSystems\EzPlatformXmlTextFieldTypeBundle\Command\ImportXmlCommand arguments: - - "@ezpublish.persistence.connection" + - "@ezxmltext.persistence.legacy.content_model_gateway" - "@ezxmltext.richtext_converter" tags: - { name: console.command } diff --git a/lib/FieldType/XmlText/Persistence/Legacy/ContentModelGateway.php b/lib/FieldType/XmlText/Persistence/Legacy/ContentModelGateway.php new file mode 100644 index 00000000..0de76bf2 --- /dev/null +++ b/lib/FieldType/XmlText/Persistence/Legacy/ContentModelGateway.php @@ -0,0 +1,287 @@ +dbal = $dbal; + } + + public function getContentTypeIds($contentTypeIdentifiers) + { + $query = $this->dbal->createQueryBuilder(); + + $query->select('c.identifier, c.id') + ->from('ezcontentclass', 'c') + ->where( + $query->expr()->in( + 'c.identifier', + ':contentTypeIdentifiers' + ) + ) + ->setParameter(':contentTypeIdentifiers', $contentTypeIdentifiers, Connection::PARAM_STR_ARRAY); + + $statement = $query->execute(); + + $columns = $statement->fetchAll(PDO::FETCH_ASSOC); + $result = []; + foreach ($columns as $column) { + $result[$column['identifier']] = $column['id']; + } + + return $result; + } + + /** + * @param string $fieldTypeIdentifier + * @return int + */ + public function countContentTypeFieldsByFieldType($fieldTypeIdentifier) + { + $query = $this->dbal->createQueryBuilder(); + $query->select('count(a.id)') + ->from('ezcontentclass_attribute', 'a') + ->where( + $query->expr()->eq( + 'a.data_type_string', + ':datatypestring' + ) + ) + ->setParameter(':datatypestring', $fieldTypeIdentifier); + $statement = $query->execute(); + + return (int) $statement->fetchColumn(); + } + + /** + * @param string $fromFieldTypeIdentifier + * @param string $toFieldTypeIdentifier + * @return \Doctrine\DBAL\Query\QueryBuilder + */ + public function getContentTypeFieldTypeUpdateQuery($fromFieldTypeIdentifier, $toFieldTypeIdentifier) + { + $updateQuery = $this->dbal->createQueryBuilder(); + $updateQuery->update('ezcontentclass_attribute') + ->set('data_type_string', ':newdatatypestring') + // was tagPreset in ezxmltext, unused in RichText + ->set('data_text2', ':datatext2') + ->where( + $updateQuery->expr()->eq( + 'data_type_string', + ':olddatatypestring' + ) + ) + ->setParameters([ + ':olddatatypestring' => $fromFieldTypeIdentifier, + ':newdatatypestring' => $toFieldTypeIdentifier, + ':datatext2' => null, + ]); + + return $updateQuery; + } + + public function getRowCountOfContentObjectAttributes($datatypeString, $contentId) + { + $query = $this->dbal->createQueryBuilder(); + $query->select('count(a.id)') + ->from('ezcontentobject_attribute', 'a') + ->where( + $query->expr()->eq( + 'a.data_type_string', + ':datatypestring' + ) + ) + ->setParameter(':datatypestring', $datatypeString); + + if ($contentId !== null) { + $query->andWhere( + $query->expr()->eq( + 'a.contentobject_id', + ':contentid' + ) + ) + ->setParameter(':contentid', $contentId); + } + + $statement = $query->execute(); + + return (int) $statement->fetchColumn(); + } + + /** + * Get the specified field rows. + * Note that if $contentId !== null, then $offset and $limit will be ignored. + * + * @param string $datatypeString + * @param int $contentId + * @param int $offset + * @param int $limit + * @return \Doctrine\DBAL\Driver\Statement|int + */ + public function getFieldRows($datatypeString, $contentId, $offset, $limit) + { + $query = $this->dbal->createQueryBuilder(); + $query->select('a.*') + ->from('ezcontentobject_attribute', 'a') + ->where( + $query->expr()->eq( + 'a.data_type_string', + ':datatypestring' + ) + ) + ->orderBy('a.id') + ->setParameter(':datatypestring', $datatypeString); + + if ($contentId === null) { + $query->setFirstResult($offset) + ->setMaxResults($limit); + } else { + $query->andWhere( + $query->expr()->eq( + 'a.contentobject_id', + ':contentid' + ) + ) + ->setParameter(':contentid', $contentId); + } + + return $query->execute(); + } + + /** + * @param int $id + * @param int $version + * @param string $datatext + * @return \Doctrine\DBAL\Query\QueryBuilder + */ + public function getUpdateFieldRowQuery($id, $version, $datatext) + { + $updateQuery = $this->dbal->createQueryBuilder(); + $updateQuery->update('ezcontentobject_attribute') + ->set('data_type_string', ':datatypestring') + ->set('data_text', ':datatext') + ->where( + $updateQuery->expr()->eq( + 'id', + ':id' + ) + ) + ->andWhere( + $updateQuery->expr()->eq( + 'version', + ':version' + ) + ) + ->setParameters([ + ':datatypestring' => 'ezrichtext', + ':datatext' => $datatext, + ':id' => $id, + ':version' => $version, + ]); + + return $updateQuery; + } + + public function contentObjectAttributeExists($objectId, $attributeId, $version, $language) + { + $query = $this->dbal->createQueryBuilder(); + $query->select('count(a.id)') + ->from('ezcontentobject_attribute', 'a') + ->where( + $query->expr()->eq( + 'a.data_type_string', + ':datatypestring' + ) + ) + ->andWhere( + $query->expr()->eq( + 'a.contentobject_id', + ':objectid' + ) + ) + ->andWhere( + $query->expr()->eq( + 'a.id', + ':attributeid' + ) + ) + ->andWhere( + $query->expr()->eq( + 'a.version', + ':version' + ) + ) + ->andWhere( + $query->expr()->eq( + 'a.language_code', + ':language' + ) + ) + ->setParameter(':datatypestring', 'ezxmltext') + ->setParameter(':objectid', $objectId) + ->setParameter(':attributeid', $attributeId) + ->setParameter(':version', $version) + ->setParameter(':language', $language); + + $statement = $query->execute(); + $count = (int)$statement->fetchColumn(); + + return $count === 1; + } + + public function updateContentObjectAttribute($xml, $objectId, $attributeId, $version, $language) + { + $updateQuery = $this->dbal->createQueryBuilder(); + $updateQuery->update('ezcontentobject_attribute') + ->set('data_text', ':newxml') + ->where( + $updateQuery->expr()->eq( + 'data_type_string', + ':datatypestring' + ) + ) + ->andWhere( + $updateQuery->expr()->eq( + 'contentobject_id', + ':objectid' + ) + ) + ->andWhere( + $updateQuery->expr()->eq( + 'id', + ':attributeid' + ) + ) + ->andWhere( + $updateQuery->expr()->eq( + 'version', + ':version' + ) + ) + ->andWhere( + $updateQuery->expr()->eq( + 'language_code', + ':language' + ) + ) + ->setParameter(':newxml', $xml) + ->setParameter(':datatypestring', 'ezxmltext') + ->setParameter(':objectid', $objectId) + ->setParameter(':attributeid', $attributeId) + ->setParameter(':version', $version) + ->setParameter(':language', $language); + $updateQuery->execute(); + } +} diff --git a/phpunit-integration-legacy-empty-db.xml b/phpunit-integration-legacy-empty-db.xml new file mode 100644 index 00000000..d6330975 --- /dev/null +++ b/phpunit-integration-legacy-empty-db.xml @@ -0,0 +1,27 @@ + + + + + + + + + + ./tests/lib/FieldType/Persistence/Legacy/ + + + + + ./bundle + ./lib + ./vendor + + + diff --git a/phpunit.xml b/phpunit.xml index 347ed80a..786c7e36 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,7 @@ ./tests/lib/ ./tests/lib/XmlTextAPIIntegrationTest.php ./tests/lib/XmlTextSPIIntegrationTest.php + ./tests/lib/FieldType/Persistence/Legacy ./tests/lib/XmlTextSPIIntegrationTest.php diff --git a/tests/lib/FieldType/Persistence/Legacy/BaseTest.php b/tests/lib/FieldType/Persistence/Legacy/BaseTest.php new file mode 100644 index 00000000..8655cfd8 --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/BaseTest.php @@ -0,0 +1,82 @@ +getSetupFactory()->getDatabaseHandler(); + + foreach ($data as $table => $rows) { + // Check that at least one row exists + if (!isset($rows[0])) { + continue; + } + + $q = $db->createInsertQuery(); + $q->insertInto($db->quoteIdentifier($table)); + + // Contains the bound parameters + $values = array(); + + // Binding the parameters + foreach ($rows[0] as $col => $val) { + $q->set( + $db->quoteIdentifier($col), + $q->bindParam($values[$col]) + ); + } + + $stmt = $q->prepare(); + + foreach ($rows as $row) { + try { + // This CANNOT be replaced by: + // $values = $row + // each $values[$col] is a PHP reference which should be + // kept for parameters binding to work + foreach ($row as $col => $val) { + $values[$col] = $val; + } + + $stmt->execute(); + } catch (Exception $e) { + echo "$table ( ", implode(', ', $row), " )\n"; + throw $e; + } + } + } + + $this->resetSequences(); + } + + public function resetSequences() + { + switch ($this->getDB()) { + case 'pgsql': + // Update PostgreSQL sequences + $handler = $this->getSetupFactory()->getDatabaseHandler(); + + $queries = array_filter(preg_split('(;\\s*$)m', + file_get_contents(__DIR__ . '/_fixtures/setval.pgsql.sql'))); + foreach ($queries as $query) { + $handler->exec($query); + } + } + } +} diff --git a/tests/lib/FieldType/Persistence/Legacy/ContentModelGatewayTest.php b/tests/lib/FieldType/Persistence/Legacy/ContentModelGatewayTest.php new file mode 100644 index 00000000..fa2a9577 --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/ContentModelGatewayTest.php @@ -0,0 +1,496 @@ +getSetupFactory()->resetDB(); + } + + public function getContentTypeIdsProvider() + { + return [ + [ + ['image', 'thumbnail'], + ['image' => 27, 'thumbnail' => 2], + ], + [ + ['image'], + ['image' => 27], + ], + ]; + } + + /** + * @dataProvider getContentTypeIdsProvider + * @param [] $identifiers + * @param [] $expected + */ + public function testGetContentTypeIds($identifiers, $expected) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentclass.php'); + $gatewayService = $this->getGatewayService(); + $ids = $gatewayService->getContentTypeIds($identifiers); + $this->assertEquals($expected, $ids); + } + + public function testCountContentTypeFieldsByFieldType() + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentclass_attribute.php'); + $gatewayService = $this->getGatewayService(); + + $count = $gatewayService->countContentTypeFieldsByFieldType('ezxmltext'); + $this->assertEquals(2, $count, 'Expected to find 2 content type fields'); + + $count = $gatewayService->countContentTypeFieldsByFieldType('ezstring'); + $this->assertEquals(1, $count, 'Expected to find 1 content type field'); + + $count = $gatewayService->countContentTypeFieldsByFieldType('foobar'); + $this->assertEquals(0, $count, 'Expected to find 0 content type fields'); + } + + public function testGetContentTypeFieldTypeUpdateQuery() + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentclass_attribute.php'); + $gatewayService = $this->getGatewayService(); + + $count1 = $gatewayService->countContentTypeFieldsByFieldType('ezxmltext'); + $this->assertEquals(2, $count1, 'Expected to find 2 field definitions subject for conversion'); + + $updateQuery = $gatewayService->getContentTypeFieldTypeUpdateQuery('ezxmltext', 'ezrichtext'); + $updateQuery->execute(); + + $count2 = $gatewayService->countContentTypeFieldsByFieldType('ezxmltext'); + $this->assertEquals(0, $count2, 'Expected all field definitions to be converted'); + } + + public function getRowCountOfContentObjectAttributesProvider() + { + return [ + [ + 'ezxmltext', + 68, + 2, + ], + [ + 'ezstring', + 68, + 3, + ], + [ + 'foobar', + 68, + 0, + ], + [ + 'ezxmltext', + 69, + 3, + ], + ]; + } + + /** + * @dataProvider getRowCountOfContentObjectAttributesProvider + * @param string $datatypeString + * @param int $contentId + * @param int $expectedCount + */ + public function testGetRowCountOfContentObjectAttributes($datatypeString, $contentId, $expectedCount) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentobject_attribute.php'); + + $gatewayService = $this->getGatewayService(); + $count = $gatewayService->getRowCountOfContentObjectAttributes($datatypeString, $contentId); + + $this->assertEquals($expectedCount, $count, 'Number of attributes does not match'); + } + + public function getFieldRowsProvider() + { + return [ + [ //test $contentId + 'ezxmltext', + 68, + 0, + 100, + [ + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '183', + 'contentobject_id' => '68', + 'data_float' => '0.0', + 'data_int' => '1045487555', + 'data_text' => '
Content consumption is changing rapidly. An agile solution to distribute your content and empower your digital business model is key to success in every industry.
', + 'data_type_string' => 'ezxmltext', + 'id' => '283', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '184', + 'contentobject_id' => '68', + 'data_float' => '0', + 'data_int' => '1045487555', + 'data_text' => '
eZ Publish Enterprise is the platform to make the omni-channel approach possible. A powerful presentation engine provides a multiplicity of websites and pages that display your content in a variety of renderings. A powerful API directly and simply integrates your content with any Web-enabled application on any device, including the iPad, iPhone or Android without ever interfering with or impacting the platform itself.
', + 'data_type_string' => 'ezxmltext', + 'id' => '284', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + ], + ], + [ // test $offset, $limit + 'ezxmltext', + null, + 0, + 1, + [ + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '183', + 'contentobject_id' => '68', + 'data_float' => '0.0', + 'data_int' => '1045487555', + 'data_text' => '
Content consumption is changing rapidly. An agile solution to distribute your content and empower your digital business model is key to success in every industry.
', + 'data_type_string' => 'ezxmltext', + 'id' => '283', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + ], + ], + [ // test $offset, $limit + 'ezxmltext', + null, + 1, + 1, + [ + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '184', + 'contentobject_id' => '68', + 'data_float' => '0', + 'data_int' => '1045487555', + 'data_text' => '
eZ Publish Enterprise is the platform to make the omni-channel approach possible. A powerful presentation engine provides a multiplicity of websites and pages that display your content in a variety of renderings. A powerful API directly and simply integrates your content with any Web-enabled application on any device, including the iPad, iPhone or Android without ever interfering with or impacting the platform itself.
', + 'data_type_string' => 'ezxmltext', + 'id' => '284', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + ], + ], + [ // test $offset, $limit + 'ezxmltext', + null, + 1, + 2, + [ + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '184', + 'contentobject_id' => '68', + 'data_float' => '0', + 'data_int' => '1045487555', + 'data_text' => '
eZ Publish Enterprise is the platform to make the omni-channel approach possible. A powerful presentation engine provides a multiplicity of websites and pages that display your content in a variety of renderings. A powerful API directly and simply integrates your content with any Web-enabled application on any device, including the iPad, iPhone or Android without ever interfering with or impacting the platform itself.
', + 'data_type_string' => 'ezxmltext', + 'id' => '284', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + [ + 'attribute_original_id' => '0', + 'contentclassattribute_id' => '183', + 'contentobject_id' => '69', + 'data_float' => '0', + 'data_int' => null, + 'data_text' => '
Increasing the productivity of your content infrastructure, eZ Publish Enterprise provides you with powerful tools to create, automate and collaborate on content...
', + 'data_type_string' => 'ezxmltext', + 'id' => '295', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => '0', + 'sort_key_string' => '', + 'version' => '1', + ], + ], + ], + ]; + } + + /** + * @dataProvider getFieldRowsProvider + * @param string $datatypeString + * @param int $contentId + * @param int $offset + * @param int $limit + * @param [] $expectedRows + */ + public function testGetFieldRows($datatypeString, $contentId, $offset, $limit, $expectedRows) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentobject_attribute.php'); + + $gatewayService = $this->getGatewayService(); + $statement = $gatewayService->getFieldRows($datatypeString, $contentId, $offset, $limit); + $index = 0; + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $this->assertLessThan(count($expectedRows), $index, 'Too many rows returned by getFieldRows'); + $this->assertEquals($expectedRows[$index], $row, 'Result from getFieldRows() did not return expected result'); + ++$index; + } + $this->assertEquals(count($expectedRows), $index, 'Too few rows returned by getFieldRows'); + } + + protected function getFieldRows($contentId) + { + $gatewayService = $this->getGatewayService(); + $statement = $gatewayService->getFieldRows('ezxmltext', $contentId, 0, 100); + $rows = []; + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $rows[] = $row; + } + + return $rows; + } + + public function getAllFieldRows() + { + $query = $this->getDBAL()->createQueryBuilder(); + $query->select('a.*') + ->from('ezcontentobject_attribute', 'a') + ->orderBy('a.id'); + + $statement = $query->execute(); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $rows[] = $row; + } + + return $rows; + } + + public function getUpdateFieldRowQueryProvider() + { + return [ + [ + 283, + 1, + 'foobar', + ], + [ + 295, + 1, + 'foobar', + ], + ]; + } + + /** + * @dataProvider getUpdateFieldRowQueryProvider + * @param int $id + * @param int $version + * @param string $datatext + */ + public function testGetUpdateFieldRowQuery($id, $version, $datatext) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentobject_attribute.php'); + + $gatewayService = $this->getGatewayService(); + $originalRows = $this->getAllFieldRows(); + + $updateQuery = $gatewayService->getUpdateFieldRowQuery($id, $version, $datatext); + $updateQuery->execute(); + + $updatedRows = $this->getAllFieldRows(); + foreach ($originalRows as $key => $expectedRow) { + if ($expectedRow['id'] == $id && $expectedRow['version'] == $version) { + $expectedRow['data_text'] = $datatext; + $expectedRow['data_type_string'] = 'ezrichtext'; + } + + $rowFound = false; + foreach ($updatedRows as $updatedRow) { + if ($expectedRow['id'] == $updatedRow['id'] && $expectedRow['version'] == $updatedRow['version'] && $expectedRow['language_code'] == $updatedRow['language_code']) { + $this->assertEquals($expectedRow, $updatedRow, 'Table row is not correct'); + $rowFound = true; + break; + } + } + $this->assertTrue($rowFound, "Row seems to have disappeared from db where id=$id and version=$version"); + } + } + + public function contentObjectAttributeExistsProvider() + { + return [ + [ + 68, + 283, + 1, + 'eng-GB', + true, + ], + [ + 68, + 283, + 1, + 'nor-NO', + false, + ], + [ + 69, + 295, + 1, + 'eng-GB', + true, + ], + [ + 69, + 295, + 2, + 'eng-GB', + true, + ], + [ + 69, + 295, + 3, + 'eng-GB', + false, + ], + ]; + } + + /** + * @dataProvider contentObjectAttributeExistsProvider + * @param int $objectId + * @param int $attributeId + * @param int $version + * @param string $language + * @param bool $expectedResult + */ + public function testContentObjectAttributeExists($objectId, $attributeId, $version, $language, $expectedResult) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentobject_attribute.php'); + + $gatewayService = $this->getGatewayService(); + $result = $gatewayService->contentObjectAttributeExists($objectId, $attributeId, $version, $language); + + $this->assertEquals($expectedResult, $result, 'contentObjectAttributeExists() did not return expected value'); + } + + public function updateContentObjectAttributeProvider() + { + return [ + [ + 'foobar', + 68, + 283, + 1, + 'eng-GB', + ], + [ + 'foobar', + 69, + 295, + 1, + 'eng-GB', + ], + [ + 'foobar', + 69, + 295, + 2, + 'eng-GB', + ], + [ + 'foobar', + 69, + 296, + 2, + 'nor-NO', + ], + ]; + } + + /** + * @dataProvider updateContentObjectAttributeProvider + * @param string $xml + * @param int $objectId + * @param int $attributeId + * @param int $version + * @param string $language + */ + public function testUpdateContentObjectAttribute($xml, $objectId, $attributeId, $version, $language) + { + $this->insertDatabaseFixture(__DIR__ . '/_fixtures/contentobject_attribute.php'); + + $gatewayService = $this->getGatewayService(); + $originalRows = $this->getAllFieldRows(); + + $gatewayService->updateContentObjectAttribute($xml, $objectId, $attributeId, $version, $language); + + $updatedRows = $this->getAllFieldRows(); + foreach ($originalRows as $key => $expectedRow) { + if ($expectedRow['contentobject_id'] == $objectId + && $expectedRow['id'] == $attributeId + && $expectedRow['version'] == $version + && $expectedRow['language_code'] == $language) { + $expectedRow['data_text'] = $xml; + } + + $rowFound = false; + foreach ($updatedRows as $updatedRow) { + if ($expectedRow['contentobject_id'] == $updatedRow['contentobject_id'] && $expectedRow['id'] == $updatedRow['id'] && $expectedRow['version'] == $updatedRow['version'] && $expectedRow['language_code'] == $updatedRow['language_code']) { + $this->assertEquals($expectedRow, $updatedRow, 'Table row is not correct'); + $rowFound = true; + break; + } + } + $this->assertTrue($rowFound, "Row seems to have disappeared from db where id=$objectId and version=$version"); + } + } + + protected function getGatewayService() + { + return $this->getSetupFactory()->getServiceContainer()->get('ezxmltext.persistence.legacy.content_model_gateway'); + } + + public function getDB() + { + return $this->getSetupFactory()->getDB(); + } + + public function getDBAL() + { + $handler = $this->getSetupFactory()->getDatabaseHandler(); + $connection = $handler->getConnection(); + + return $connection; + } +} diff --git a/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass.php b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass.php new file mode 100644 index 00000000..82e2ed7a --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass.php @@ -0,0 +1,66 @@ + array( + 0 => array( + 'always_available' => 0, + 'contentobject_name' => '', + 'created' => 1024392098, + 'creator_id' => 14, + 'id' => 27, + 'identifier' => 'image', + 'initial_language_id' => 2, + 'is_container' => 0, + 'language_mask' => 3, + 'modified' => 1523277289, + 'modifier_id' => 14, + 'remote_id' => 'f6df12aa74e36230eb675f364fccd25a', + 'serialized_description_list' => 'a:1:{s:6:"eng-GB";s:0:"";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:5:"Image";s:16:"always-available";s:6:"eng-GB";}', + 'sort_field' => 1, + 'sort_order' => 1, + 'url_alias_name' => '', + 'version' => 0, + ), + 1 => array( + 'always_available' => 0, + 'contentobject_name' => '', + 'created' => 1024392098, + 'creator_id' => 14, + 'id' => 1, + 'identifier' => 'testarticle', + 'initial_language_id' => 2, + 'is_container' => 0, + 'language_mask' => 3, + 'modified' => 1523277289, + 'modifier_id' => 14, + 'remote_id' => 'f6df12aa74e36230eb675f364fccd25b', + 'serialized_description_list' => 'a:1:{s:6:"eng-GB";s:0:"";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:5:"Image";s:16:"always-available";s:6:"eng-GB";}', + 'sort_field' => 1, + 'sort_order' => 1, + 'url_alias_name' => '', + 'version' => 0, + ), + 2 => array( + 'always_available' => 0, + 'contentobject_name' => '', + 'created' => 1024392098, + 'creator_id' => 14, + 'id' => 2, + 'identifier' => 'thumbnail', + 'initial_language_id' => 2, + 'is_container' => 0, + 'language_mask' => 3, + 'modified' => 1523277289, + 'modifier_id' => 14, + 'remote_id' => 'f6df12aa74e36230eb675f364fccd25c', + 'serialized_description_list' => 'a:1:{s:6:"eng-GB";s:0:"";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:5:"Image";s:16:"always-available";s:6:"eng-GB";}', + 'sort_field' => 1, + 'sort_order' => 1, + 'url_alias_name' => '', + 'version' => 0, + ), + ), +); diff --git a/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass_attribute.php b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass_attribute.php new file mode 100644 index 00000000..d529a488 --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentclass_attribute.php @@ -0,0 +1,93 @@ + [ + 0 => [ + 'can_translate' => 1, + 'category' => '', + 'contentclass_id' => 1, + 'data_float1' => 0, + 'data_float2' => 0, + 'data_float3' => 0, + 'data_float4' => 0, + 'data_int1' => 5, + 'data_int2' => 0, + 'data_int3' => 0, + 'data_int4' => 0, + 'data_text1' => '', // Test schema out of date? should be data_text + 'data_text2' => '', + 'data_text3' => '', + 'data_text4' => '', + 'data_text5' => '', + 'data_type_string' => 'ezxmltext', + 'id' => 119, + 'identifier' => 'short_description', + 'is_information_collector' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'placement' => 3, + 'serialized_data_text' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_description_list' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:7:"Summary";s:16:"always-available";s:6:"eng-GB";}', + 'version' => 0, + ], + 1 => [ + 'can_translate' => 1, + 'category' => '', + 'contentclass_id' => 1, + 'data_float1' => 0, + 'data_float2' => 0, + 'data_float3' => 0, + 'data_float4' => 0, + 'data_int1' => 20, + 'data_int2' => 0, + 'data_int3' => 0, + 'data_int4' => 0, + 'data_text1' => '', + 'data_text2' => '', + 'data_text3' => '', + 'data_text4' => '', + 'data_text5' => '', + 'data_type_string' => 'ezxmltext', + 'id' => 156, + 'identifier' => 'description', + 'is_information_collector' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'placement' => 4, + 'serialized_data_text' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_description_list' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:11:"Description";s:16:"always-available";s:6:"eng-GB";}', + 'version' => 0, + ], + 2 => [ + 'can_translate' => 1, + 'category' => '', + 'contentclass_id' => 1, + 'data_float1' => 0, + 'data_float2' => 0, + 'data_float3' => 0, + 'data_float4' => 0, + 'data_int1' => 255, + 'data_int2' => 0, + 'data_int3' => 0, + 'data_int4' => 0, + 'data_text1' => 'Folder', + 'data_text2' => '', + 'data_text3' => '', + 'data_text4' => '', + 'data_text5' => '', + 'data_type_string' => 'ezstring', + 'id' => 4, + 'identifier' => 'name', + 'is_information_collector' => 0, + 'is_required' => 1, + 'is_searchable' => 1, + 'placement' => 1, + 'serialized_data_text' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_description_list' => 'a:2:{s:6:"eng-GB";s:0:"";s:16:"always-available";s:6:"eng-GB";}', + 'serialized_name_list' => 'a:2:{s:6:"eng-GB";s:4:"Name";s:16:"always-available";s:6:"eng-GB";}', + 'version' => 0, + ], + ], +]; diff --git a/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentobject_attribute.php b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentobject_attribute.php new file mode 100644 index 00000000..0917b2db --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/_fixtures/contentobject_attribute.php @@ -0,0 +1,157 @@ + [ + 0 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 181, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => 'Deliver', + 'data_type_string' => 'ezstring', + 'id' => '281', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => 'deliver', + 'version' => 1, + ], + 1 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 182, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => '', + 'data_type_string' => 'ezstring', + 'id' => '282', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 2 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 183, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => 1045487555, + 'data_text' => '
Content consumption is changing rapidly. An agile solution to distribute your content and empower your digital business model is key to success in every industry.
', + 'data_type_string' => 'ezxmltext', + 'id' => '283', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 3 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 184, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => 1045487555, + 'data_text' => '
eZ Publish Enterprise is the platform to make the omni-channel approach possible. A powerful presentation engine provides a multiplicity of websites and pages that display your content in a variety of renderings. A powerful API directly and simply integrates your content with any Web-enabled application on any device, including the iPad, iPhone or Android without ever interfering with or impacting the platform itself.
', + 'data_type_string' => 'ezxmltext', + 'id' => '284', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 4 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 185, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => 1045487555, + 'data_text' => '
', + 'data_type_string' => 'ezstring', + 'id' => '285', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 5 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 186, + 'contentobject_id' => 68, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => ' +MQ==NzIwMDAwLzEwMDAwNzIwMDAwLzEwMDAwMg==QWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKQ==MjAxNDoxMToxMCAxNzo0MDoyMw==MTY4MQ==NzcwMjgx', + 'data_type_string' => 'ezimage', + 'id' => '286', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 6 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 181, + 'contentobject_id' => 69, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => 'Create', + 'data_type_string' => 'ezstring', + 'id' => '293', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 7 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 183, + 'contentobject_id' => 69, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => '
Increasing the productivity of your content infrastructure, eZ Publish Enterprise provides you with powerful tools to create, automate and collaborate on content...
', + 'data_type_string' => 'ezxmltext', + 'id' => '295', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 1, + ], + 8 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 183, + 'contentobject_id' => 69, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => '
version2
', + 'data_type_string' => 'ezxmltext', + 'id' => '295', + 'language_code' => 'eng-GB', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 2, + ], + 9 => [ + 'attribute_original_id' => 0, + 'contentclassattribute_id' => 183, + 'contentobject_id' => 69, + 'data_float' => 0, + 'data_int' => null, + 'data_text' => '
version2
', + 'data_type_string' => 'ezxmltext', + 'id' => '296', + 'language_code' => 'nor-NO', + 'language_id' => '2', + 'sort_key_int' => 0, + 'sort_key_string' => '', + 'version' => 2, + ], + ], +]; diff --git a/tests/lib/FieldType/Persistence/Legacy/_fixtures/setval.pgsql.sql b/tests/lib/FieldType/Persistence/Legacy/_fixtures/setval.pgsql.sql new file mode 100644 index 00000000..52700135 --- /dev/null +++ b/tests/lib/FieldType/Persistence/Legacy/_fixtures/setval.pgsql.sql @@ -0,0 +1,30 @@ +SELECT setval('ezcobj_state_group_id_seq',max(id)) FROM ezcobj_state_group; +SELECT setval('ezcobj_state_id_seq',max(id)) FROM ezcobj_state; +SELECT setval('ezcontentclass_attribute_id_seq',max(id)) FROM ezcontentclass_attribute; +SELECT setval('ezcontentclass_id_seq',max(id)) FROM ezcontentclass; +SELECT setval('ezcontentclassgroup_id_seq',max(id)) FROM ezcontentclassgroup; +SELECT setval('ezcontentobject_attribute_id_seq',max(id)) FROM ezcontentobject_attribute; +SELECT setval('ezcontentobject_link_id_seq',max(id)) FROM ezcontentobject_link; +SELECT setval('ezcontentobject_id_seq',max(id)) FROM ezcontentobject; +SELECT setval('ezcontentobject_tree_node_id_seq',max(node_id)) FROM ezcontentobject_tree; +SELECT setval('ezcontentobject_version_id_seq',max(id)) FROM ezcontentobject_version; +SELECT setval('ezimagefile_id_seq',max(id)) FROM ezimagefile; +SELECT setval('ezkeyword_attribute_link_id_seq',max(id)) FROM ezkeyword_attribute_link; +SELECT setval('ezkeyword_id_seq',max(id)) FROM ezkeyword; +SELECT setval('eznode_assignment_id_seq',max(id)) FROM eznode_assignment; +SELECT setval('ezpolicy_limitation_id_seq',max(id)) FROM ezpolicy_limitation; +SELECT setval('ezpolicy_limitation_value_id_seq',max(id)) FROM ezpolicy_limitation_value; +SELECT setval('ezpolicy_id_seq',max(id)) FROM ezpolicy; +SELECT setval('ezrole_id_seq',max(id)) FROM ezrole; +SELECT setval('ezsearch_object_word_link_id_seq',max(id)) FROM ezsearch_object_word_link; +SELECT setval('ezsearch_word_id_seq',max(id)) FROM ezsearch_word; +SELECT setval('ezsection_id_seq',max(id)) FROM ezsection; +SELECT setval('ezurl_id_seq',max(id)) FROM ezurl; +SELECT setval('ezurlalias_ml_incr_id_seq',max(id)) FROM ezurlalias_ml_incr; +SELECT setval('ezurlalias_id_seq',max(id)) FROM ezurlalias; +SELECT setval('ezurlwildcard_id_seq',max(id)) FROM ezurlwildcard; +SELECT setval('ezuser_accountkey_id_seq',max(id)) FROM ezuser_accountkey; +SELECT setval('ezuser_role_id_seq',max(id)) FROM ezuser_role; +SELECT setval('ezcontentbrowsebookmark_id_seq',max(id)) FROM ezcontentbrowsebookmark; +SELECT setval('eznotification_id_seq',max(id)) FROM eznotification; +SELECT setval('ezpreferences_id_seq',max(id)) FROM ezpreferences; diff --git a/tests/lib/SetupFactory/LegacyEmptyDBSetupFactory.php b/tests/lib/SetupFactory/LegacyEmptyDBSetupFactory.php new file mode 100644 index 00000000..873f8ff5 --- /dev/null +++ b/tests/lib/SetupFactory/LegacyEmptyDBSetupFactory.php @@ -0,0 +1,61 @@ +load('storage_engines/legacy/external_storage_gateways.yml'); + $loader->load('storage_engines/legacy/field_value_converters.yml'); + $loader->load('fieldtype_external_storages.yml'); + $loader->load('fieldtypes.yml'); + $loader->load('indexable_fieldtypes.yml'); + $loader->load('../../bundle/Resources/config/services.yml'); + + // Service ezxmltext.command.convert_to_richtext requires kernel.cache_dir + $containerBuilder->setParameter('kernel.cache_dir', __DIR__); + } + + public function getDatabaseHandler() + { + return parent::getDatabaseHandler(); + } + + public function getInitialData() + { + $data = parent::getInitialData(); + $tables = []; + // just get the table names in the dump, so that insertData() will truncate them + foreach (array_reverse(array_keys($data)) as $table) { + $tables[$table] = []; + } + + return $tables; + } + + public function resetDB() + { + $this->getRepository(true); + } +} From d111132f31ecd91a9877dd98c9d4b9d545784b99 Mon Sep 17 00:00:00 2001 From: Vidar Date: Fri, 26 Oct 2018 11:20:53 +0200 Subject: [PATCH 2/2] UI improvements (#82) * Let "ezxmltext:convert-to-richtext --content-object .." output non-validating Docbook xml on imports * Added progress bar to ezxmltext:convert-to-richtext * Replaced '\n' with PHP_EOL constant --- .../ConvertXmlTextToRichTextCommand.php | 90 +++++++++++++++---- bundle/Command/ImportXmlCommand.php | 24 +++-- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/bundle/Command/ConvertXmlTextToRichTextCommand.php b/bundle/Command/ConvertXmlTextToRichTextCommand.php index f87fd86e..bc87c308 100644 --- a/bundle/Command/ConvertXmlTextToRichTextCommand.php +++ b/bundle/Command/ConvertXmlTextToRichTextCommand.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Console\Helper\ProgressBar; use eZ\Publish\Core\FieldType\XmlText\Value; use eZ\Publish\Core\FieldType\XmlText\Converter\RichText as RichTextConverter; use eZ\Publish\Core\FieldType\XmlText\Persistence\Legacy\ContentModelGateway as Gateway; @@ -61,6 +62,15 @@ class ConvertXmlTextToRichTextCommand extends ContainerAwareCommand */ protected $processes = []; + /** + * @var bool + */ + protected $hasProgressBar; + + /** + * @var \Symfony\Component\Console\Helper\ProgressBar + */ + protected $progressBar; /** * @var int */ @@ -139,10 +149,10 @@ protected function configure() 'export-dir-filter', null, InputOption::VALUE_OPTIONAL, - "To be used together with --export-dir option. Specify what kind of problems should be exported:\n - notice: ezxmltext contains problems which the conversion tool was able to fix automatically and likly do not need manual intervention\n - warning: the conversion tool was able to convert the ezxmltext to valid richtext, but data was maybe altered/removed/added in the process. Manual supervision recommended\n - error: the ezxmltext text cannot be converted and manual changes are required.", + 'To be used together with --export-dir option. Specify what kind of problems should be exported:' . PHP_EOL . + 'notice: ezxmltext contains problems which the conversion tool was able to fix automatically and likly do not need manual intervention' . PHP_EOL . + 'warning: the conversion tool was able to convert the ezxmltext to valid richtext, but data was maybe altered/removed/added in the process. Manual supervision recommended' . PHP_EOL . + 'error: the ezxmltext text cannot be converted and manual changes are required.', sprintf('%s,%s', LogLevel::WARNING, LogLevel::ERROR) ) ->addOption( @@ -161,9 +171,15 @@ protected function configure() 'fix-embedded-images-only', null, InputOption::VALUE_NONE, - "Use this option to ensure that embedded images in a database are tagget correctly so that the editor will detect them as such.\n - This option is needed if you have an existing ezplatform database which was converted with an earlier version of\n - 'ezxmltext:convert-to-richtext' which did not convert embedded images correctly." + 'Use this option to ensure that embedded images in a database are tagget correctly so that the editor will detect them as such.' . PHP_EOL . + 'This option is needed if you have an existing ezplatform database which was converted with an earlier version of' . PHP_EOL . + '\'ezxmltext:convert-to-richtext\' which did not convert embedded images correctly.' + ) + ->addOption( + 'no-progress', + null, + InputOption::VALUE_NONE, + 'Disable the progress bar.' ) ->addOption( 'user', @@ -181,17 +197,19 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($dryRun) { $output->writeln('Running in dry-run mode. No changes will actually be written to database'); if ($this->exportDir !== '') { - $output->writeln("Note: --export-dir option provided, files will be written to $this->exportDir even in dry-run mode\n"); + $output->writeln("Note: --export-dir option provided, files will be written to $this->exportDir even in dry-run mode" . PHP_EOL); } } + $this->hasProgressBar = !$input->getOption('no-progress'); + $testContentId = $input->getOption('test-content-object'); if ($testContentId !== null && $this->maxConcurrency !== 1) { throw new RuntimeException('Multi concurrency is not supported together with the --test-content-object option'); } if ($input->getOption('fix-embedded-images-only')) { - $output->writeln("Fixing embedded images only. No other changes are done to the database\n"); + $output->writeln('Fixing embedded images only. No other changes are done to the database' . PHP_EOL); $this->fixEmbeddedImages($dryRun, $testContentId, $output); return; @@ -381,6 +399,7 @@ protected function fixEmbeddedImages($dryRun, $contentId, OutputInterface $outpu $count = $this->gateway->getRowCountOfContentObjectAttributes('ezrichtext', $contentId); $output->writeln("Found $count field rows to convert."); + $this->progressBarStart($output, $count); $offset = 0; $totalCount = 0; @@ -431,10 +450,12 @@ protected function fixEmbeddedImages($dryRun, $contentId, OutputInterface $outpu ); } } + $this->progressBarAdvance($offset); $offset += self::MAX_OBJECTS_PER_CHILD; } while ($offset + self::MAX_OBJECTS_PER_CHILD <= $count); + $this->progressBarFinish(); - $output->writeln("Updated ezembed tags in $totalCount field(s)"); + $output->writeln(PHP_EOL . 'Updated ezembed tags in $totalCount field(s)'); } protected function convertFieldDefinitions($dryRun, $output) @@ -459,11 +480,16 @@ protected function updateFieldRow($dryRun, $id, $version, $datatext) protected function waitForAvailableProcessSlot(OutputInterface $output) { - if (count($this->processes) >= $this->maxConcurrency) { + if (!$this->processSlotAvailable()) { $this->waitForChild($output); } } + protected function processSlotAvailable() + { + return count($this->processes) < $this->maxConcurrency; + } + protected function waitForChild(OutputInterface $output) { $childEnded = false; @@ -623,30 +649,64 @@ protected function convertFields($dryRun, $contentId, $checkDuplicateIds, $check $this->writeCustomTagLog(); } + protected function progressBarStart(OutputInterface $output, $count) + { + if ($this->hasProgressBar) { + $this->progressBar = new ProgressBar($output, $count); + $this->progressBar->start(); + } + } + + protected function progressBarAdvance($step) + { + if ($this->hasProgressBar) { + $this->progressBar->advance($step); + } + } + + protected function progressBarFinish() + { + if ($this->hasProgressBar) { + $this->progressBar->finish(); + } + } + protected function processFields($dryRun, $checkDuplicateIds, $checkIdValues, OutputInterface $output) { $count = $this->gateway->getRowCountOfContentObjectAttributes('ezxmltext', null); $output->writeln("Found $count field rows to convert."); + if ($count < self::MAX_OBJECTS_PER_CHILD * $this->maxConcurrency && $this->maxConcurrency > 1) { + $objectsPerChild = (int) ceil($count / $this->maxConcurrency); + } else { + $objectsPerChild = self::MAX_OBJECTS_PER_CHILD; + } $offset = 0; $fork = $this->maxConcurrency > 1; + $this->progressBarStart($output, $count); do { - $limit = self::MAX_OBJECTS_PER_CHILD; + $limit = $objectsPerChild; if ($fork) { + $processSlotAvailable = $this->processSlotAvailable(); $this->waitForAvailableProcessSlot($output); + if (!$processSlotAvailable) { + $this->progressBarAdvance($objectsPerChild); + } $process = $this->createChildProcess($dryRun, $checkDuplicateIds, $checkIdValues, $offset, $limit, $output); $this->processes[$process->getPid()] = ['offset' => $offset, 'limit' => $limit, 'process' => $process]; } else { $this->convertFields($dryRun, null, $checkDuplicateIds, $checkIdValues, $offset, $limit); } - $offset += self::MAX_OBJECTS_PER_CHILD; - } while ($offset + self::MAX_OBJECTS_PER_CHILD <= $count); + $offset += $objectsPerChild; + } while ($offset + $objectsPerChild <= $count); while (count($this->processes) > 0) { $this->waitForChild($output); + $this->progressBarAdvance($objectsPerChild); } - $output->writeln("Converted $count ezxmltext fields to richtext"); + $this->progressBarFinish(); + $output->writeln(PHP_EOL . 'Converted $count ezxmltext fields to richtext'); } protected function createDocument($xmlString) diff --git a/bundle/Command/ImportXmlCommand.php b/bundle/Command/ImportXmlCommand.php index 973a48dd..0df3e10c 100644 --- a/bundle/Command/ImportXmlCommand.php +++ b/bundle/Command/ImportXmlCommand.php @@ -42,6 +42,11 @@ class ImportXmlCommand extends ContainerAwareCommand */ private $output; + /** + * @var string|null + */ + private $contentObjectId; + public function __construct(Gateway $gateway, RichTextConverter $converter) { parent::__construct(); @@ -92,13 +97,13 @@ protected function execute(InputInterface $input, OutputInterface $output) { $dryRun = false; if ($input->getOption('dry-run')) { - $output->writeln("Running in dry-run mode. No changes will actually be written to database\n"); + $output->writeln('Running in dry-run mode. No changes will actually be written to database' . PHP_EOL); $dryRun = true; } $this->output = $output; - $contentObjectId = $input->getOption('content-object'); + $this->contentObjectId = $input->getOption('content-object'); if ($input->getOption('export-dir')) { $this->exportDir = $input->getOption('export-dir'); @@ -118,10 +123,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->converter->setImageContentTypes($imageContentTypeIds); - $this->importDumps($dryRun, $contentObjectId); + $this->importDumps($dryRun); } - protected function importDumps($dryRun, $contentObjectId = null) + protected function importDumps($dryRun) { foreach (new \DirectoryIterator($this->exportDir) as $dirItem) { if ($dirItem->isFile() && $dirItem->getExtension() === 'xml') { @@ -143,7 +148,7 @@ protected function importDumps($dryRun, $contentObjectId = null) $language = $fileNameArray[4]; $filename = $this->exportDir . DIRECTORY_SEPARATOR . $dirItem->getFilename(); - if ($contentObjectId !== null && $contentObjectId !== $objectId) { + if ($this->contentObjectId !== null && $this->contentObjectId !== $objectId) { continue; } @@ -155,7 +160,9 @@ protected function importDumps($dryRun, $contentObjectId = null) protected function validateConversion(DOMDocument $xmlDoc, $filename, $attributeId) { - $this->converter->convert($xmlDoc, true, true, $attributeId); + $docBookDoc = new DOMDocument(); + $docBookDoc->loadXML($this->converter->convert($xmlDoc, true, true, $attributeId)); + $docBookDoc->formatOutput = true; // Looks like XSLT processor is setting formatOutput to true $xmlDoc->formatOutput = false; $errors = $this->converter->getErrors(); @@ -178,6 +185,11 @@ protected function validateConversion(DOMDocument $xmlDoc, $filename, $attribute } } } + + if ($this->contentObjectId !== null) { + $this->output->writeln('Docbook result:'); + $this->output->writeln($docBookDoc->saveXML()); + } } else { $result = true; }