Updating from <2.2
If you are updating from a version prior to 1.13, you have to implement all the changes from Updating from <1.13 before following the steps below.
Note
During database update, you have to go through all the changes between your current version and your final version
e.g. during update from v2.2 to v2.5 you have to perform all the steps from: <2.3, <2.4 and <2.5.
Only after applying all changes your database will work properly.
Change from UTF8 to UTF8MB4
Since 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:
| 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:
| 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:
| 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 it:
- Run
composer require ezsystems/ezplatform-page-migration
- Add the bundle to
app/AppKernel.php
: new EzSystems\EzPlatformPageMigrationBundle\EzPlatformPageMigrationBundle(),
- Run command
bin/console ezplatform:page:migrate
Tip
This script will use 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 command will migrate Landing Pages created in eZ Platform 1.x, 2.0 and 2.1 to new Pages.
The operation is transactional and will roll back in case of errors.
Block migration
In 2.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 had been using 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 2.1 and earlier) were defined using a class implementing EzSystems\LandingPageFieldTypeBundle\FieldType\LandingPage\Model\AbstractBlockType
.
In Page Builder (from 2.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 Pagebuilder, have a look at Creating custom Page 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:
| 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
:
| blocks:
random:
views:
random:
template: AppBundle:blocks:random.html.twig
name: Random Content Block View
|
src/AppBundle/Resources/config/services.yml
:
| 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
18 | blocks:
random:
name: Random
category: default
thumbnail: assets/images/layouts/sidebar.png
# read https://doc.ezplatform.com/en/latest/guide/extending/extending_page/#block-modal-template
#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 }
|
You can now follow the steps from Updating from <2.3.