Skip to content

Update database to v2.5

4. Update the database

Before you start this procedure, make sure you have completed the previous step, Updating the app to v2.5.

Caution

Always back up your data before running any database update scripts.

After updating the database, clear the cache.

Do not use --force argument for mysql / psql commands when performing update queries. If there is any problem during the update, it is best if the query fails immediately, so you can fix the underlying problem before you execute the update again. If you leave this for later you risk ending up with an incompatible database, though the problems might not surface immediately.

Note

If you are starting from version v2.2 or later, skip to the relevant section.

A. Update to v2.2

Change from UTF8 to UTF8MB4

In v2.2 the character set for MySQL/MariaDB database tables changes from utf8 to utf8mb4 to support 4-byte characters.

To apply this change, use the following database update script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.1.0-to-7.2.0.sql

If you use DFS Cluster, also execute the following database update script:

1
mysql -u <username> -p <password> <dfs_database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.1.0-to-7.2.0-dfs.sql

Be aware that these upgrade statements may fail due to index collisions. This is because the indexes have been shortened, so duplicates may occur. If that happens, you must remove the duplicates manually, and then repeat the statements that failed.

After successfully running those statements, change the character set and collation for each table, as described in kernel upgrade documentation.

You should also change the character set that is specified in the application config:

In app/config/config.yml, set the following:

1
2
3
4
5
doctrine:
    dbal:
        connections:
            default:
                charset: utf8mb4

Also make the corresponding change in app/config/dfs/dfs.yml.

Migrate Landing Pages

To update to v2.2 with existing Landing Pages, you need to use a dedicated script. The script is contained in the ezplatform-page-migration bundle and works since version v2.2.2. To use the script:

  1. Run composer require ezsystems/ezplatform-page-migration
  2. Add the bundle to app/AppKernel.php: new EzSystems\EzPlatformPageMigrationBundle\EzPlatformPageMigrationBundle(),
  3. Run command bin/console ezplatform:page:migrate

Tip

This script uses the layout defined in your Landing Page. To migrate successfully, you need to copy your zone configuration from ez_systems_landing_page_field_type under ezplatform_page_fieldtype in the new config. Otherwise the script will encounter errors.

You can remove the bundle after the migration is complete.

The ezplatform:page:migrate command migrates Landing Pages created in eZ Platform v1.x, v2.0 and v2.1 to new Pages. The operation is transactional and rolls back in case of errors.

Avoid exception when migrating from eZ Publish

If you migrated to v1.13 from eZ Publish, and want to upgrade to v2.5, an exception will occur when you run the bin/console ezplatform:page:migrate command and the database contains internal drafts of Landing Pages.

To avoid this exception, you must first remove all internal drafts before you migrate.

Block migration

In v2.2 Page Builder does not offer all blocks that Landing Page editor did. The removed blocks include Keyword, Schedule, and Form blocks. The Places block has been removed from the clean installation and will only be available in the demo out of the box. If you use this block in your site, re-apply its configuration based on the demo.

Later versions of Page Builder come with a Content Scheduler block and new Form Blocks, but migration of Schedule blocks to Content Scheduler blocks and of Form Blocks is not supported.

If there are missing block definitions, such as Form Block or Schedule Block, you have an option to continue, but migrated Landing Pages will come without those blocks.

Tip

If you use different repositories with different SiteAccesses, use the --siteaccess switch to migrate them separately.

Tip

You can use the --dry-run switch to test the migration.

After the migration is finished, you need to clear the cache.

Migrate layouts

The ez_block::renderBlockAction controller used in layout templates has been replaced by EzPlatformPageFieldTypeBundle:Block:render. This controller has two additional parameters, locationId and languageCode. Only languageCode is required. Also, the HTML class data-studio-zone has been replaced with data-ez-zone-id See documentation for an example on usage of the new controller.

Migrate custom blocks

Landing Page blocks (from v2.1 and earlier) were defined using a class implementing EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Model\AbstractBlockType. In Page Builder (from v2.2 onwards), this interface is no longer present. Instead the logic of your block must be implemented in a Listener. Typically, what you previously would do in getTemplateParameters(), you'll now do in the onBlockPreRender() event handler.

The definition of block parameters has to be moved from createBlockDefinition() to the YAML configuration for your custom blocks.

For more information about how custom blocks are implemented in Page Builder, have a look at Creating custom Page blocks for your custom blocks.

For the migration of blocks from Landing Page to Page Builder, you'll need to provide a converter for attributes of custom blocks. For simple blocks you can use \EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter. Custom converters must implement the \EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\ConverterInterface interface. convert() will parse XML \DOMNode $node and return an array of \EzSystems\EzPlatformPageFieldType\FieldType\LandingPage\Model\Attribute objects.

Below is an example of a simple converter for a custom block:

1
2
3
4
app.block.foobar.converter:
    class: EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter
    tags:
        - { name: ezplatform.fieldtype.ezlandingpage.migration.attribute.converter, block_type: foobar }

Notice service tag ezplatform.fieldtype.ezlandingpage.migration.attribute.converter that must be used for attribute converters.

This converter is only needed when running the ezplatform:page:migrate script and can be removed once that has completed.

Page migration example

Below is an example how to migrate a Landing Page Layout and Block to new Page Builder. The code is based on the Random block defined in the Enterprise Beginner tutorial

Landing Page code

app/Resources/views/layouts/sidebar.html.twig:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div data-studio-zones-container>
    <main class="landing-page__zone landing-page__zone--{{ zones[0].id }} landing-page__zone--left col-xs-8" data-studio-zone="{{ zones[0].id }}">
        {% if zones[0].blocks %}
            {% for block in zones[0].blocks %}
                <div class="landing-page__block block_{{ block.type }}">
                    {{ render_esi(controller('ez_block::renderBlockAction', {
                        'contentId': contentInfo.id,
                        'blockId': block.id,
                        'versionNo': versionInfo.versionNo
                    })) }}
                </div>
            {% endfor %}
        {% endif %}
    </main>
    <aside class="landing-page__zone landing-page__zone--{{ zones[1].id }} landing-page__zone--left col-xs-4" data-studio-zone="{{ zones[1].id }}">
        {% if zones[1].blocks %}
            {% for block in zones[1].blocks %}
                <div class="landing-page__block block_{{ block.type }}">
                    {{ render_esi(controller('ez_block::renderBlockAction', {
                        'contentId': contentInfo.id,
                        'blockId': block.id,
                        'versionNo': versionInfo.versionNo
                    })) }}
                </div>
            {% endfor %}
        {% endif %}
    </aside>
</div>

app/config/layouts.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ez_systems_landing_page_field_type:
    layouts:
        sidebar:
            identifier: sidebar
            name: Right sidebar
            description: Main section with sidebar on the right
            thumbnail: assets/images/layouts/sidebar.png
            template: layouts/sidebar.html.twig
            zones:
                first:
                    name: First zone
                second:
                    name: Second zone

src/AppBundle/Block/RandomBlock.php:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php

namespace AppBundle\Block;

use EzSystems\LandingPageFieldTypeBundle\Exception\InvalidBlockAttributeException;
use EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Definition\BlockDefinition;
use EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Definition\BlockAttributeDefinition;
use EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Model\AbstractBlockType;
use EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Model\BlockValue;
use eZ\Publish\API\Repository\ContentService;
use eZ\Publish\API\Repository\LocationService;
use eZ\Publish\API\Repository\SearchService;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use eZ\Publish\API\Repository\Values\Content\LocationQuery;

class RandomBlock extends AbstractBlockType
{
    /**
     * Content ID regular expression.
     *
     * @example 16
     *
     * @var string
     */
    const PATTERN_CONTENT_ID = '/[0-9]+/';

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

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

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

    /**
     * @param \eZ\Publish\API\Repository\LocationService $locationService
     * @param \eZ\Publish\API\Repository\ContentService $contentService
     * @param \eZ\Publish\API\Repository\SearchService $searchService
     */
    public function __construct(
        LocationService $locationService,
        ContentService $contentService,
        SearchService $searchService
    ) {
        $this->locationService = $locationService;
        $this->contentService = $contentService;
        $this->searchService = $searchService;
    }

    public function getTemplateParameters(BlockValue $blockValue)
    {
        $attributes = $blockValue->getAttributes();
        $contentInfo = $this->contentService->loadContentInfo($attributes['parentContentId']);
        $randomContent = $this->getRandomContent(
            $this->getQuery($contentInfo->mainLocationId)
        );

        return [
            'content' => $randomContent,
        ];
    }

    /**
     * Returns random picked Content.
     *
     * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
     *
     * @return \eZ\Publish\API\Repository\Values\Content\Content
     */
    private function getRandomContent(LocationQuery $query)
    {
        $results = $this->searchService->findLocations($query);
        $searchHits = $results->searchHits;
        if (count($searchHits) > 0) {
            shuffle($searchHits);

            return $this->contentService->loadContentByContentInfo(
                $searchHits[0]->valueObject->contentInfo
            );
        }

        return null;
    }

    /**
     * Returns LocationQuery object based on given arguments.
     *
     * @param int $parentLocationId
     *
     * @return \eZ\Publish\API\Repository\Values\Content\LocationQuery
     */
    private function getQuery($parentLocationId)
    {
        $query = new LocationQuery();
        $query->query = new Criterion\LogicalAnd([
            new Criterion\Visibility(Criterion\Visibility::VISIBLE),
            new Criterion\ParentLocationId($parentLocationId),
        ]);

        return $query;
    }

    public function createBlockDefinition()
    {
        return new BlockDefinition(
            'random',
            'Random',
            'default',
            'assets/images/blocks/random_block.svg',
            [],
            [
                new BlockAttributeDefinition(
                    'parentContentId',
                    'Parent',
                    'embed',
                    self::PATTERN_CONTENT_ID,
                    'Choose a valid ContentID',
                    true,
                    false,
                    [],
                    []
                ),
            ]
        );
    }

    public function checkAttributesStructure(array $attributes)
    {
        if (!isset($attributes['parentContentId']) || preg_match(self::PATTERN_CONTENT_ID, $attributes['parentContentId']) !== 1) {
            throw new InvalidBlockAttributeException('Parent container', 'parentContentId', 'Parent ContentID must be defined.');
        }
    }
}

src/AppBundle/DependencyInjection/AppExtension.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace AppBundle\DependencyInjection;

use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Config\Resource\FileResource;

class AppExtension extends Extension implements PrependExtensionInterface
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new YamlFileLoader(
            $container,
            new FileLocator(__DIR__ . '/../Resources/config')
        );
        $loader->load('services.yml');
    }

    public function prepend(ContainerBuilder $container)
    {
        $configFile = __DIR__ . '/../Resources/config/blocks.yml';
        $config = Yaml::parse(file_get_contents($configFile));
        $container->prependExtensionConfig('ez_systems_landing_page_field_type', $config);
        $container->addResource(new FileResource($configFile));
    }
}

src/AppBundle/Resources/config/blocks.yml:

1
2
3
4
5
6
blocks:
    random:
        views:
            random:
                template: AppBundle:blocks:random.html.twig
                name: Random Content Block View

src/AppBundle/Resources/config/services.yml:

1
2
3
4
5
6
7
8
9
services:
    app.block.random:
        class: AppBundle\Block\RandomBlock
        arguments:
            - '@ezpublish.api.service.location'
            - '@ezpublish.api.service.content'
            - '@ezpublish.api.service.search'
        tags:
            - { name: landing_page_field_type.block_type, alias: random }
Corresponding Page Builder code

app/Resources/views/layouts/sidebar.html.twig:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div data-studio-zones-container>
    <main class="landing-page__zone landing-page__zone--{{ zones[0].id }} landing-page__zone--left col-xs-8" data-studio-zone="{{ zones[0].id }} data-ez-zone-id="{{ zones[0].id }}">
        {% if zones[0].blocks %}
            {% set locationId = parameters.location is not null ? parameters.location.id : contentInfo.mainLocationId %}
            {% for block in zones[0].blocks %}
                <div class="landing-page__block block_{{ block.type }} data-ez-block-id="{{ block.id }}">
                    {{ render_esi(controller('EzPlatformPageFieldTypeBundle:Block:render', {
                        'locationId': locationId,
                        'contentId': contentInfo.id,
                        'blockId': block.id,
                        'versionNo': versionInfo.versionNo,
                        'languageCode': field.languageCode
                    })) }}
                </div>
            {% endfor %}
        {% endif %}
    </main>
    <aside class="landing-page__zone landing-page__zone--{{ zones[1].id }} landing-page__zone--left col-xs-4" data-studio-zone="{{ zones[1].id }} data-ez-zone-id="{{ zones[1].id }}">
        {% if zones[1].blocks %}
            {% set locationId = parameters.location is not null ? parameters.location.id : contentInfo.mainLocationId %}
            {% for block in zones[1].blocks %}
                <div class="landing-page__block block_{{ block.type }} data-ez-block-id="{{ block.id }}">
                    {{ render_esi(controller('EzPlatformPageFieldTypeBundle:Block:render', {
                        'locationId': locationId,
                        'contentId': contentInfo.id,
                        'blockId': block.id,
                        'versionNo': versionInfo.versionNo,
                        'languageCode': field.languageCode
                    })) }}
                </div>
            {% endfor %}
        {% endif %}
    </aside>
</div>

app/config/layouts.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ezplatform_page_fieldtype:
    layouts:
        sidebar:
            identifier: sidebar
            name: Right sidebar
            description: Main section with sidebar on the right
            thumbnail: assets/images/layouts/sidebar.png
            template: layouts/sidebar.html.twig
            zones:
                first:
                    name: First zone
                second:
                    name: Second zone

src/AppBundle/Block/Event/Listener/RandomBlockListener.php in place of src/AppBundle/Block/RandomBlock.php:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php

namespace AppBundle\Block\Event\Listener;

use eZ\Publish\API\Repository\ContentService;
use eZ\Publish\API\Repository\LocationService;
use eZ\Publish\API\Repository\SearchService;
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
use EzSystems\EzPlatformPageFieldType\FieldType\Page\Block\Renderer\BlockRenderEvents;
use EzSystems\EzPlatformPageFieldType\FieldType\Page\Block\Renderer\Event\PreRenderEvent;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class RandomBlockListener implements EventSubscriberInterface
{
    /** @var \eZ\Publish\API\Repository\ContentService */
    private $contentService;
    /**
     * @var LocationService
     */
    private $locationService;
    /**
     * @var SearchService
     */
    private $searchService;

    /**
     * BannerBlockListener constructor.
     *
     * @param \eZ\Publish\API\Repository\ContentService $contentService
     */
    public function __construct(
        ContentService $contentService,
        LocationService $locationService,
        SearchService $searchService
    ) {
        $this->contentService = $contentService;
        $this->locationService = $locationService;
        $this->searchService = $searchService;
    }

    /**
     * @return array The event names to listen to
     */
    public static function getSubscribedEvents()
    {
        return [
            BlockRenderEvents::getBlockPreRenderEventName('random') => 'onBlockPreRender',
        ];
    }

    /**
     * @param \EzSystems\EzPlatformPageFieldType\FieldType\Page\Block\Renderer\Event\PreRenderEvent $event
     *
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
     */
    public function onBlockPreRender(PreRenderEvent $event)
    {
        //BlockDefinitionFactory
        $blockValue = $event->getBlockValue();
        $renderRequest = $event->getRenderRequest();
        $contentInfo = $this->contentService->loadContentInfo($blockValue->getAttribute('parentContentId')->getValue());

        $randomContent = $this->getRandomContent(
            $this->getQuery($contentInfo->mainLocationId)
        );

        $parameters = $renderRequest->getParameters();
        $parameters['content'] = $randomContent;

        $renderRequest->setParameters($parameters);
    }

    /**
     * Returns random picked Content.
     *
     * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
     *
     * @return \eZ\Publish\API\Repository\Values\Content\Content
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
     */
    private function getRandomContent(LocationQuery $query)
    {
        $results = $this->searchService->findLocations($query);
        $searchHits = $results->searchHits;
        if (count($searchHits) > 0) {
            shuffle($searchHits);

            return $this->contentService->loadContentByContentInfo(
                $searchHits[0]->valueObject->contentInfo
            );
        }

        return null;
    }

    /**
     * Returns LocationQuery object based on given arguments.
     *
     * @param int $parentLocationId
     *
     * @return \eZ\Publish\API\Repository\Values\Content\LocationQuery
     */
    private function getQuery($parentLocationId)
    {
        $query = new LocationQuery();
        $query->query = new Criterion\LogicalAnd([
            new Criterion\Visibility(Criterion\Visibility::VISIBLE),
            new Criterion\ParentLocationId($parentLocationId),
        ]);

        return $query;
    }
}

src/AppBundle/DependencyInjection/AppExtension.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace AppBundle\DependencyInjection;

use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Config\Resource\FileResource;

class AppExtension extends Extension implements PrependExtensionInterface
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new YamlFileLoader(
            $container,
            new FileLocator(__DIR__ . '/../Resources/config')
        );
        $loader->load('services.yml');
    }

    public function prepend(ContainerBuilder $container)
    {
        $configFile = __DIR__ . '/../Resources/config/blocks.yml';
        $config = Yaml::parse(file_get_contents($configFile));
        $container->prependExtensionConfig('ezplatform_page_fieldtype', $config);
        $container->addResource(new FileResource($configFile));
    }
}

src/AppBundle/Resources/config/blocks.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
blocks:
    random:
        name: Random
        category: default
        thumbnail: assets/images/layouts/sidebar.png
        #configuration_template: blocks/random_config.html.twig
        views:
            random:
                template:  AppBundle:blocks:random.html.twig
                name: Random Content Block View
        attributes:
            parentContentId:
                type: embed
                name: Parent Location ID
                validators:
                    not_blank:
                        message: Please provide parent node

src/AppBundle/Resources/config/services.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    AppBundle\Block\Event\Listener\RandomBlockListener: ~

    app.block.random.converter:
        class: EzSystems\EzPlatformPageMigration\Converter\AttributeConverter\DefaultConverter
        tags:
            - { name: ezplatform.fieldtype.ezlandingpage.migration.attribute.converter, block_type: random }

B. Update to v2.3

Database update script

Apply the following database update script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.2.0-to-7.3.0.sql

Form Builder

In an Enterprise installation, to create the Forms container under the content tree root use the following command:

1
php bin/console ezplatform:form-builder:create-forms-container

You can also specify content type, Field values and language code of the container, for example:

1
php bin/console ezplatform:form-builder:create-forms-container --content-type custom --field title --value 'My Forms' --field description --value 'Custom container for the forms' --language-code eng-US

You also need to run a script to add database tables for the Form Builder. You can find it in https://github.com/ezsystems/ezplatform-ee-installer/blob/2.3/Resources/sql/schema.sql#L136

Form (ezform) Field Type

After the update, in order to create forms, you have to add a new content type (for example, named "Form") that contains Form Field (this content type can contain other fields as well). After that you can use forms inside Landing Pages via Embed block.

C. Update to v2.4

Workflow

When updating an Enterprise installation, you need to run a script to add database tables for the Editorial Workflow.

Changes to the Forms folder

The built-in Forms folder is located in the Form Section in versions 2.4+.

If you are updating your Enterprise installation, you need to add this Section manually and move the folder to it.

To allow anonymous users to access Forms, you also need to add the content/read Policy with the Form Section to the Anonymous User.

Changes to Custom tags

v2.4 changed the way of configuring custom tags. They are no longer configured under the ezpublish key, but one level higher in the YAML structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ezpublish:
    system:
        <siteaccess>:
            fieldtypes:
                ezrichtext:
                    custom_tags: [exampletag]

ezrichtext:
    custom_tags:
        exampletag:
            # ...

The old configuration is deprecated, so if you use custom tags, you need to modify your config accordingly.

D. Update to v2.5

Database update script

Apply the following database update script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.4.0-to-7.5.0.sql
v2.5.3

To update to v2.5.3, additionally run the following script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.2-to-7.5.3.sql
v2.5.6

To update to v2.5.6, additionally run the following script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.4-to-7.5.5.sql

or for PostgreSQL:

1
psql <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/postgres/dbupdate-7.5.4-to-7.5.5.sql
v2.5.9

To update to v2.5.9, additionally run the following script:

1
mysql -u <username> -p <password> <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/mysql/dbupdate-7.5.6-to-7.5.7.sql

or for PostgreSQL:

1
psql <database_name> < vendor/ezsystems/ezpublish-kernel/data/update/postgres/dbupdate-7.5.6-to-7.5.7.sql

Additionally, reindex the content:

1
php bin/console ezplatform:reindex

Changes to database schema

The introduction of support for PostgreSQL includes a change in the way database schema is generated.

It is now created based on YAML configuration, using the new DoctrineSchemaBundle.

If you are updating your application according to the usual procedure, no additional actions are required. However, if you do not update your meta-repository, you need to take two additional steps:

  • enable EzSystems\DoctrineSchemaBundle\DoctrineSchemaBundle() in AppKernel.php
  • add ez_doctrine_schema configuration

Changes to Matrix Field Type

To migrate your content from legacy XML format to a new ezmatrix value use the following command:

1
bin/console ezplatform:migrate:legacy_matrix

Required manual cache clearing if using Redis

If you are using Redis as your persistence cache storage you should always clear it manually after an upgrade. You can do it in two ways, by using redis-cli and executing the following command:

1
FLUSHALL

or by executing the following command:

1
bin/console cache:pool:clear cache.redis

Updating to 2.5.3

Page Builder

This step is only required when updating an Enterprise installation from versions higher than v2.2 and lower than v2.5.3. In case of versions lower than 2.2, skip this step or ignore the information that indexes from a script below already exist.

When updating to v2.5.3, you need to run the following SQL commands to add missing indexes:

1
2
3
4
5
6
7
8
9
CREATE INDEX ezpage_map_zones_pages_zone_id ON ezpage_map_zones_pages(zone_id);
CREATE INDEX ezpage_map_zones_pages_page_id ON ezpage_map_zones_pages(page_id);
CREATE INDEX ezpage_map_blocks_zones_block_id ON ezpage_map_blocks_zones(block_id);
CREATE INDEX ezpage_map_blocks_zones_zone_id ON ezpage_map_blocks_zones(zone_id);
CREATE INDEX ezpage_map_attributes_blocks_attribute_id ON ezpage_map_attributes_blocks(attribute_id);
CREATE INDEX ezpage_map_attributes_blocks_block_id ON ezpage_map_attributes_blocks(block_id);
CREATE INDEX ezpage_blocks_design_block_id ON ezpage_blocks_design(block_id);
CREATE INDEX ezpage_blocks_visibility_block_id ON ezpage_blocks_visibility(block_id);
CREATE INDEX ezpage_pages_content_id_version_no ON ezpage_pages(content_id, version_no);

Updating to 2.5.16

Powered-By header

In order to promote use of eZ Platform, ezsystems/ez-support-tools v1.0.10, as of eZ Platform v2.5.16, sets the Powered-By header. It is enabled by default and generates a header like Powered-By: eZ Platform Enterprise v2.

To omit the version number, use the following configuration:

1
2
3
4
ezplatform_support_tools:
    system_info:
        powered_by:
            release: "none"

To opt out of the whole feature, disable it with the following configuration:

1
2
3
4
ezplatform_support_tools:
    system_info:
        powered_by:
            enabled: false

Updating to v2.5.18

To update to v2.5.18, if you are using MySQL, additionally run the following update SQL command:

1
ALTER TABLE ezpage_attributes MODIFY value LONGTEXT;
Update entity managers

Version v2.5.18 introduces new entity managers. To ensure that they work in multi-repository setups, you must update the GraphQL schema. You do this manually by following this procedure:

  1. Update your project to v2.5.18 and run the php bin/console cache:clear command to generate the service container.

  2. Run the following command to discover the names of the new entity managers. Take note of the names that you discover:

    php bin/console debug:container --parameter=doctrine.entity_managers --format=json | grep ibexa_

  3. For every entity manager prefixed with ibexa_, run the following command:

    php bin/console doctrine:schema:update --em=<ENTITY_MANAGER_NAME> --dump-sql

  4. Review the queries and ensure that there are no harmful changes that could affect your data.

  5. For every entity manager prefixed with ibexa_, run the following command to run queries on the database:

    php bin/console doctrine:schema:update --em=<ENTITY_MANAGER_NAME> --force

VCL configuration for Fastly

If you use Fastly, deploy the most up-to-date VCL configuration.

Locate the vendor/ezsystems/ezplatform-http-cache-fastly/fastly/ez_main.vcl file, make sure that it has been updated with the following changes, and upload it to your Fastly:

  • Add the following lines:
1
2
3
if (req.restarts == 0 && resp.status == 301 && req.http.x-fos-original-url) {
    set resp.http.location = regsub(resp.http.location, "/_fos_user_context_hash", req.http.x-fos-original-url);
}
  • Move the #FASTLY recv macro call to a new location, right after the Preserve X-Forwarded-For in all requests section.
Optimize workflow queries

Run the following SQL queries to optimize workflow performance:

1
2
CREATE INDEX idx_workflow_co_id_ver ON ezeditorialworkflow_workflows(content_id, version_no);
CREATE INDEX idx_workflow_name ON ezeditorialworkflow_workflows(workflow_name);

5. Finish the update

A. Platform.sh changes

If you are hosting your site on Ibexa Cloud be aware of the fact that Varnish is enabled by default as of v1.13.5, v2.4.3 and v2.5.0. If you are using Fastly, read about how to disable Varnish.

B. Dump assets

Dump web assets if you are using the prod environment. In dev this happens automatically:

1
2
yarn install
yarn encore prod

If you encounter problems, additionally clear the cache and install assets:

1
2
3
4
php bin/console cache:clear -e prod
php bin/console assets:install --symlink -e prod
yarn install
yarn encore prod

C. Commit, test and merge

When you resolve all conflicts and update composer.lock, commit the merge.

You may or may not keep composer.lock, depending on your version management workflow. If you do not want to keep it, run git reset HEAD composer.lock to remove it from the changes. Run git commit, and adapt the message if necessary.

Go back to master, and merge the update-2.5 branch:

1
2
git checkout master
git merge update-2.5

Insecure password hashes

To ensure that no users have unsupported, insecure password hashes, run the following command:

1
2
3
4
# In v1 and v2:
php bin/console ezplatform:user:validate-password-hashes
# In v3:
php bin/console ibexa:user:validate-password-hashes

This command checks if all user hashes are up-to-date and informs you if any of them need to be updated.

D. Complete the update

Complete the update by running the following commands:

1
2
3
4
5
6
# In v2.5:
php bin/console ezplatform:graphql:generate-schema
# In v3:
php bin/console ibexa:graphql:generate-schema

composer run post-install-cmd

Notify support

Please tell support that you have updated your installation. They will update your support portal to match the new version. This ensures that you receive notifications about new maintenance releases and security advisories for the correct version. You can contact support at support@ibexa.co or through your Support portal.

defaultLayout setting not available

If you migrated you installation from eZ Publish Platform, in Page Builder you can encounter an issue where the Default layout dropdown is disabled with a "Layout '' for setting 'defaultLayout' is not available" error message.

If this happens, add the following temporary configuration to app/config/ezplatform.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ezpublish:
    system:
        global:
            ezpage:
                layouts:
                    GlobalZoneLayout:
                        name: Global zone layout
                        template: globalzonelayout.tpl
                    2ZonesLayout1:
                        name: 2 zones (layout 1)
                        template: 2zoneslayout1.tpl
                    2ZonesLayout2:
                        name: 2 zones (layout 2)
                        template: 2zoneslayout2.tpl
                    2ZonesLayout3:
                        name: 2 zones (layout 3)
                        template: 2zoneslayout3.tpl
                    3ZonesLayout1:
                        name: 3 zones (layout 1)
                        template: 3zoneslayout1.tpl
                    3ZonesLayout2:
                        name: 3 zones (layout 2)
                        template: 3zoneslayout2.tpl
                    CallForActionLayout:
                        name: Call For Action zone layout
                        template: callforactionlayout.tpl

Clear the cache and refresh the page. The dropdown should now be active. Select any option in the dropdown and save the content type.

You should now be able to remove the Field definition from the content type.

Afterwards, you can remove the configuration above from ezplatform.yml.

Update to v3.3

It is strongly recommended to also update to the latest LTS, v3.3.