Upgrade the Cms module
Edit on GitHubUpgrading from version 6.* to version 7.*
Version 7.0.0 of the CMS module introduces the multi-store functionality. The multi-store CMS page feature enables management of CMS page display per store via a store toggle control in the Back Office.
BC breaks and solutions:
- Update deprecated methods and classes
- Update removed exceptions
- Update storage behavior
- Migrate database
Estimated migration time: 2 hours
To upgrade to the new version of the module, do the following:
- Update the
Cms
module with Composer:
"spryker/cms": "^7.0.0"
- Remove all the deprecated references: Search for and update usage of any deprecated class or method that was used.
- Renamed the
createLocaleQuery
method togetLocaleQuery
inCmsPersistenceFactory
. - Removed the deprecated method
getName()
in favor ofgetBlockPrefix()
inCmsGlossaryForm.php
. - Removed deprecated method
getName()
in favor ofgetBlockPrefix()
inCmsRedirectForm.php
.
Interfaces were moved to the CmsExtension
module.
- Moved the
CmsVersionPostSavePluginInterface
interface fromCms
toCmsExtension
- Moved the
CmsVersionTransferExpanderPluginInterface
interface fromCms
toCmsExtension
- Moved the
CmsPageDataExpanderPluginInterface.php
interface fromCms
toCmsExtension
- Removed the deprecated class
CmsBlockKeyBuilder
in favor of installing the newCmsBlock
module instead.
Exceptions were replaced by Throwables.
- Replaced Exception in favor of Throwable in
CmsPageSaver
- Replaced Exception in favor of Throwable in
PageRemover
- Replaced Exception in favor of Throwable in
CmsPageActivator
- Replaced Exception in favor of Throwable in
CmsGlossarySaver
Also, keep in mind that a doc block with non-existing methods was removed from CmsQueryContainerInterface
.
- Update database schema.
The event behavior needs to be applied to all SpyCmsPageStore columns
.
src/Pyz/Zed/Cms/Persistence/Propel/Schema/spy_cms.schema.xml
<?xml version="1.0"?>
<database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="zed" xsi:noNamespaceSchemaLocation="http://static.spryker.com/schema-01.xsd" namespace="Orm\Zed\Cms\Persistence" package="src.Orm.Zed.Cms.Persistence">
<table name="spy_cms_page_store">
<behavior name="event">
<parameter name="spy_cms_page_store_all" column="*"/>
</behavior>
</table>
</database>
- Perform database schema migration.
On your local development environment, you may run:
console transfer:generate
console propel:install
console transfer:generate
When looking to generate a production migration, this command will help you find the SQL patches needed against your production schema.
vendor/bin/console propel:diff
Before migrating a production database, always review each SQL statement individually, even when there are many of them.
- Perform Data Migration.
For a quick and smooth migration, we have prepared an example migration script. This script will find a page and share it with every store. Your specific project may require customization, especially if you have special rules regarding sharing of pages per store or a collection of CmsPages
larger than what can be stored in your migration server’s available memory.
This script will only migrate pages to stores where persistence is shared.
Pyz\Zed\Cms\Communication\Console\CmsStoreToPageDataMigration.php
<?php
/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/
namespace Pyz\Zed\Cms\Communication\Console;
use Orm\Zed\Cms\Persistence\SpyCmsPageQuery;
use Orm\Zed\Cms\Persistence\SpyCmsPageStore;
use Orm\Zed\Store\Persistence\SpyStoreQuery;
use Propel\Runtime\Propel;
use Spryker\Zed\Kernel\Communication\Console\Console;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @method \Spryker\Zed\CmsBlock\Communication\CmsBlockCommunicationFactory getFactory()
* @method \Spryker\Zed\CmsBlock\Business\CmsBlockFacade getFacade()
*/
class CmsStoreToPageDataMigration extends Console
{
const COMMAND_NAME = 'cms-store-cms-page:migrate';
const COMMAND_DESCRIPTION = 'Updates CMS page store relation by existing stores';
const COMMAND_ARGUMENT_DRY_RUN = 'dry-run';
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return void
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$connection = Propel::getConnection();
$spyCmsPages = SpyCmsPageQuery::create()->find();
$spyStores = SpyStoreQuery::create()->find();
$output->writeln(sprintf('Count pages: %s', count($spyCmsPages)));
$output->writeln(sprintf('Stores: %s', implode(', ', array_map(function(array $spyStore) {
return $spyStore['Name'];
}, $spyStores->toArray()))));
foreach ($spyCmsPages as $spyCmsPage) {
try {
$connection->beginTransaction();
foreach ($spyStores as $spyStore) {
$spyCmsPageStore = new SpyCmsPageStore();
$spyCmsPageStore->setSpyCmsPage($spyCmsPage);
$spyCmsPageStore->setSpyStore($spyStore);
$spyCmsPageStore->save();
}
$connection->commit();
} catch (\Exception $exception) {
$output->writeln('ERROR: ' . $exception->getMessage());
$connection->rollBack();
}
}
$output->writeln('Successfully finished.');
}
/**
* @return void
*/
protected function configure()
{
parent::configure();
$this->setName(static::COMMAND_NAME);
$this->setDescription(static::COMMAND_DESCRIPTION);
$this->addOption(
static::COMMAND_ARGUMENT_DRY_RUN,
null,
InputOption::VALUE_REQUIRED,
'Run without database changes'
);
}
}
Don’t forget to register your migration console command in Pyz\Zed\Console\ConsoleDependencyProvider
.
Your command should be executable with $ console cms-store-cms-page:migrate
.
Don’t forget to sync your newly updated Zed data with the storage tables.
$ console event:trigger -r cms_page
$ console event:trigger -r cms_page_search
Upgrading from version 5. to version 6.*
CMS version 5.0 is responsible only for CMS pages and page versioning. CMS Block functionality became more flexible and moved to the CmsBlock
module.
If you used CMS Blocks before, you need to migrate your data to the new structure. If you did not use CMS Blocks in your project, you can skip the migration process.
The migration process for CMS Block data is as follows:
Install CMS Block module
To install the module, "spryker/cms-block": "^1.0.0"
with Composer is required.
Perform database migration
vendor/bin/console propel:diff
, also manual review is necessary for the generated migration filevendor/bin/console propel:migrate
vendor/bin/console propel:model:build
After running the last command, you’ll find some new classes in your project under the \Orm\Zed\Cms\Persistence
namespace.
It’s important to make sure that they are extending the base classes from the core, for example:
Orm\Zed\Cms\Persistence\SpyCmsBlock
extendsSpryker\Zed\CmsBlock\Persistence\Propel\AbstractSpyCmsBlock
Orm\Zed\Cms\Persistence\SpyCmsBlockQuery
extendsSpryker\Zed\CmsBlock\Persistence\Propel\AbstractSpyCmsBlockQuery.
The same is for SpyCmsBlockGlossaryKeyMapping
, SpyCmsBlockGlossaryKeyMappingQuery
, SpyCmsBlockTemplate
, and SpyCmsBlockTemplateQuery
.
Move templates
Now, your block and page templates can be found in src/Pyz/Yves/Cms/Theme/default/template/*
or src/Pyz/Shared/Cms/Theme/default/template/*
folders.
Move CMS Block templates to the src/Pyz/Shared/CmsBlock/Theme/default/template/*
folder.
Run migration script
For quick and smooth migration, we have prepared a migration script. You can find it below.
Code sample
<?php
/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/
namespace Pyz\Zed\CmsBlock\Communication\Console;
use Orm\Zed\Cms\Persistence\SpyCmsPage;
use Orm\Zed\Cms\Persistence\SpyCmsPageQuery;
use Orm\Zed\Cms\Persistence\SpyCmsTemplate;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlock;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlockGlossaryKeyMapping;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlockGlossaryKeyMappingQuery;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlockQuery;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlockTemplate;
use Orm\Zed\CmsBlock\Persistence\SpyCmsBlockTemplateQuery;
use Orm\Zed\CmsBlockCategoryConnector\Persistence\SpyCmsBlockCategoryConnector;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Propel;
use Spryker\Zed\Kernel\Communication\Console\Console;
use Spryker\Zed\Kernel\Locator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @method \Spryker\Zed\CmsBlock\Communication\CmsBlockCommunicationFactory getFactory()
* @method \Spryker\Zed\CmsBlock\Business\CmsBlockFacade getFacade()
*/
class CmsToCmsBlockDataMigration extends Console
{
const COMMAND_NAME = 'cms-cms-block:migrate';
const COMMAND_DESCRIPTION = 'Migrates CMS Block data from CMS module';
const COMMAND_ARGUMENT_DRY_RUN = 'dry-run';
const CMS_BLOCK_RELATION_TYPE_CATEGORY = 'category';
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return void
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$connection = Propel::getConnection();
$spyCmsBlocks = SpyCmsBlockQuery::create()
->filterByFkPage(null, Criteria::ISNOTNULL)
->filterByFkTemplate(null, Criteria::ISNULL)
->find();
$spyCmsBlocksCount = count($spyCmsBlocks);
$output->writeln(sprintf('Processing %s blocks...', $spyCmsBlocksCount));
foreach ($spyCmsBlocks as $spyCmsBlock) {
$spyCmsPage = SpyCmsPageQuery::create()
->filterByIdCmsPage($spyCmsBlock->getFkPage())
->findOne();
try {
$connection->beginTransaction();
//Migration of template
$spyCmsTemplate = $spyCmsPage->getCmsTemplate();
$spyCmsBlockTemplate = $this->createCmsBlockTemplate($spyCmsTemplate);
$spyCmsBlock->setFkTemplate($spyCmsBlockTemplate->getIdCmsBlockTemplate());
//Migration of common data fields
$spyCmsBlock->setValidFrom($spyCmsPage->getValidFrom());
$spyCmsBlock->setValidTo($spyCmsPage->getValidTo());
$spyCmsBlock->setIsActive($spyCmsPage->getIsActive());
//Migration of category relation
if ($spyCmsBlock->getType() === self::CMS_BLOCK_RELATION_TYPE_CATEGORY) {
if (!$this->isCategoryConnectorInstalled()) {
$output->writeln('To import relation to Category you need to install CmsBlockCategoryConnector module');
$connection->rollBack();
continue;
}
$this->createCmsBlockCategoryConnector($spyCmsBlock);
}
$spyCmsBlock->save();
$this->migrateGlossary($spyCmsPage, $spyCmsBlock);
$connection->commit();
} catch (\Exception $exception) {
$output->writeln('ERROR: ' . $exception->getMessage());
$connection->rollBack();
}
}
$output->writeln('Successfully finished.');
}
/**
* @param \Orm\Zed\Cms\Persistence\SpyCmsTemplate $spyCmsTemplate
*
* @return \Orm\Zed\CmsBlock\Persistence\SpyCmsBlockTemplate
*/
protected function createCmsBlockTemplate(SpyCmsTemplate $spyCmsTemplate)
{
$spyCmsBlockTemplate = SpyCmsBlockTemplateQuery::create()
->filterByTemplatePath($spyCmsTemplate->getTemplatePath())
->findOne();
if (empty($spyCmsBlockTemplate)) {
$spyCmsBlockTemplate = new SpyCmsBlockTemplate();
$spyCmsBlockTemplate->setTemplateName($spyCmsTemplate->getTemplateName());
$spyCmsBlockTemplate->setTemplatePath($spyCmsTemplate->getTemplatePath());
$spyCmsBlockTemplate->save();
}
return $spyCmsBlockTemplate;
}
/**
* @param SpyCmsBlock $spyCmsBlock
*
* @return SpyCmsBlockCategoryConnector
*/
protected function createCmsBlockCategoryConnector(SpyCmsBlock $spyCmsBlock)
{
$spyCmsBlockRelation = new SpyCmsBlockCategoryConnector();
$spyCmsBlockRelation->setFkCmsBlock($spyCmsBlock->getIdCmsBlock());
$spyCmsBlockRelation->setFkCategory($spyCmsBlock->getValue());
$spyCmsBlockRelation->save();
return $spyCmsBlockRelation;
}
/**
* @param SpyCmsPage $spyCmsPage
* @param SpyCmsBlock $spyCmsBlock
*
* @return void
*/
protected function migrateGlossary(SpyCmsPage $spyCmsPage, SpyCmsBlock $spyCmsBlock)
{
foreach ($spyCmsPage->getSpyCmsGlossaryKeyMappings() as $cmsGlossaryKeyMapping) {
$exists = SpyCmsBlockGlossaryKeyMappingQuery::create()
->filterByFkCmsBlock($spyCmsBlock->getIdCmsBlock())
->filterByPlaceholder($cmsGlossaryKeyMapping->getPlaceholder())
->exists();
if (!$exists) {
$spyCmsBlockGlossaryKeyMapping = new SpyCmsBlockGlossaryKeyMapping();
$spyCmsBlockGlossaryKeyMapping->setFkCmsBlock($spyCmsBlock->getIdCmsBlock());
$spyCmsBlockGlossaryKeyMapping->setFkGlossaryKey($cmsGlossaryKeyMapping->getFkGlossaryKey());
$spyCmsBlockGlossaryKeyMapping->setPlaceholder($cmsGlossaryKeyMapping->getPlaceholder());
$spyCmsBlockGlossaryKeyMapping->save();
}
}
}
/**
* @return void
*/
protected function configure()
{
parent::configure();
$this->setName(static::COMMAND_NAME);
$this->setDescription(static::COMMAND_DESCRIPTION);
$this->addOption(
static::COMMAND_ARGUMENT_DRY_RUN,
null,
InputOption::VALUE_REQUIRED,
'Run without database changes'
);
}
/**
* @return bool
*/
protected function isCategoryConnectorInstalled()
{
return class_exists(SpyCmsBlockCategoryConnector::class);
}
}
Copy the script to src/Pyz/Zed/CmsBlock/Communication/Console/CmsToCmsBlockDataMigration.php
and register it in the Pyz\Zed\Console\ConsoleDependencyProvider
.
<?php
namespace Pyz\Zed\Console;
class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
public function getConsoleCommands(Container $container)
{
$commands = [
...
CmsToCmsBlockDataMigration()
];
...
}
}
Run the script with the command vendor/bin/console cms-cms-block:migrate
.
Upgrading from version 4.* to version 5.*
CMS Version 5.0 has a new concept for showing pages in the frontend. In the previous CMS versions, after creating a CMS page and running the collectors, we were able to see the page in the frontend, but now this has changed. After creating a CMS page, another step called Publish is needed to display the page in the frontend. Publish aggregates all CMS related data and puts it to our new CMS table spy_cms_version
. The new collectors push this data to the frontend storage and search.
Before upgrading, make sure that you do not use any deprecated code from version 3|4.*. Check the description of the deprecated code (inside the code to see what you will need to use instead).
Database migration
To start the database migration, run the following commands:
vendor/bin/console propel:diff
, manual review is necessary for the generated migration file.vendor/bin/console propel:migrate
vendor/bin/console propel:model:build
After running the last command, you will find some new classes in your project under the \Orm\Zed\Cms\Persistence
namespace. It is important to make sure that they are extending the base classes from the core, for example:
Orm\Zed\Cms\Persistence\SpyCmsVersion
extendsSpryker\Zed\Cms\Persistence\Propel\SpyCmsVersion
Orm\Zed\Cms\Persistence\SpyCmsVersionQuery
extendsSpryker\Zed\Cms\Persistence\Propel\SpyCmsVersionQuery
CMS templates
In this version, we have moved all CMS templates to the Shared layer instead of only Yves, but you are still able to use the old files.
src/Pyz/Yves/Cms/Theme/default/template/*
=> src/Pyz/Shared/Cms/Theme/default/template/*
CMS twig functions
The TwigCms
function has been improved to provide better speed and performance, it will only send a query to Redis when the translations are not available.
You can still work with the current version although upgrading is highly recommended.
You can find it here: src/Pyz/Yves/Cms/Plugin/TwigCms.php
.
CMS collector
To push new CMS version data to the frontend storage and search, add it to the src/Pyz/Zed/Collector/CollectorDependencyProvider.php
plugin stack:
Code sample:
<?php
...
use Spryker\Zed\CmsCollector\Communication\Plugin\CmsVersionPageCollectorSearchPlugin;
use Spryker\Zed\CmsCollector\Communication\Plugin\CmsVersionPageCollectorStoragePlugin;
class CollectorDependencyProvider extends SprykerCollectorDependencyProvider
{
...
$container[self::SEARCH_PLUGINS] = function (Container $container) {
return [
...
CmsConstants::RESOURCE_TYPE_PAGE => new CmsVersionPageCollectorSearchPlugin(),
];
};
...
$container[self::STORAGE_PLUGINS] = function (Container $container) {
return [
...
CmsConstants::RESOURCE_TYPE_PAGE => new CmsVersionPageCollectorStoragePlugin(),
...
];
};
...
?>
CMS user interaction
When a CMS page is published, we also store/show user information for this action. To store and show user information, register two new plugins from the new CmsUserConnector
module.
Add them here: src/Pyz/Zed/Cms/CmsDependencyProvider.php
Code sample:
<?php
namespace Pyz\Zed\Cms;
use Spryker\Zed\Cms\CmsDependencyProvider as SprykerCmsDependencyProvider;
use Spryker\Zed\CmsUserConnector\Communication\Plugin\UserCmsVersionPostSavePlugin;
use Spryker\Zed\CmsUserConnector\Communication\Plugin\UserCmsVersionTransferExpanderPlugin;
use Spryker\Zed\Kernel\Container;
class CmsDependencyProvider extends SprykerCmsDependencyProvider
{
protected function getPostSavePlugins(Container $container)
{
return [
new UserCmsVersionPostSavePlugin()
];
}
protected function getTransferExpanderPlugins(Container $container)
{
return [
new UserCmsVersionTransferExpanderPlugin()
];
}
}
?>
CMS data importer
To publish pages after importing, add this to your CMS Importer class:
Code sample:
<?php
/**
* @param array $data
*
* @return void
*/
protected function importOne(array $data)
{
$page = $this->format($data);
$templateTransfer = $this->findOrCreateTemplate($page[self::TEMPLATE]);
$pageTransfer = $this->createPage($templateTransfer, $page);
foreach ($this->localeFacade->getLocaleCollection() as $locale => $localeTransfer) {
$urlTransfer = new UrlTransfer();
$urlTransfer->setUrl($page[self::LOCALES][$locale][self::URL]);
if ($this->urlFacade->hasUrl($urlTransfer)) {
return;
}
$placeholders = $page[self::LOCALES][$locale][self::PLACEHOLDERS];
$this->createPageUrl($pageTransfer, $urlTransfer->getUrl(), $localeTransfer);
$this->createPlaceholder($placeholders, $pageTransfer, $localeTransfer);
}
$this->cmsFacade->publishWithVersion($pageTransfer->getIdCmsPage()); // Publishing the pages
}
?>
Publishing current pages
To publish the current pages, create a console command that calls the following method:
<?php
protected function publishAllPages()
{
$spyCmsPagesEntities = SpyCmsPageQuery::create()->find();
foreach ($spyCmsPagesEntities as $spyCmsPagesEntity) {
$this->cmsFacade->publishWithVersion($spyCmsPagesEntity->getIdCmsPage());
}
?>
Upgrading from version 2.* to version 3.*
We have extended CMS pages with localized attributes such as name and HTML meta header information. Also, CMS pages can now be marked as searchable. These changes required some changes in the database.
- Before upgrading to the new version, make sure that you do not use any deprecated code from version 2.* Check the description of the deprecated code to see what you will need to use instead.
- Database migration:
vendor/bin/console propel:diff
, also manual review is necessary for the generated migration file.vendor/bin/console propel:migrate
vendor/bin/console propel:model:build
- After running the last command you’ll find some new classes in your project under the
\Orm\Zed\Cms\Persistence
namespace. It’s important to make sure that they are extending the base classes from the core, for example,Orm\Zed\Cms\Persistence\SpyCmsPageLocalizedAttributes
extendsSpryker\Zed\Cms\Persistence\Propel\AbstractSpyCmsPageLocalizedAttributes
, andOrm\Zed\Cms\Persistence\SpyCmsPageLocalizedAttributesQuery
extendsSpryker\Zed\Cms\Persistence\Propel\AbstractSpyCmsPageLocalizedAttributesQuery
.
Thank you!
For submitting the form