Adding Magento CMS Blocks, Pages, and Email Templates Programmatically

Welcome to another installment of our micromodules series for the Magento community - Magently Content Setup. Read on to learn how to add CMS Blocks programmatically.

GitHub link - https://github.com/magently/module-content-setup/

Adding CMS Blocks, Pages, and Email Templates programmatically

There are many existing guides showing how to add your own CMS Block or CMS Page with code in Magento 2. Usually, all you have to do is to use the model and Resource Model or API, inject a few classes, and viola, the CMS Block is added.

What if you need to be able to add new blocks or pages frequently and in different modules (e.g. category page or product page)? Each time you do it, you add more redundant code, injecting the same classes over and over and adding the whole block or page content to PatchData/UpgradeScript. This is neither efficient nor elegant. 

Now, you can standardize how you work with programmatically added content thanks to our free micromodule - Magently Content Setup. 

What does it do?

Magently Content Setup contains classes linking your module to Magento modules. The data is being pulled from HTML and PHP files to preserve code cleanliness.

Each CMS Block, Page, or Email Template is divided into two files - HTML and PHP, and is stored in your module files. You only need to add one line to your UpgradeData/Patch Data to add it to the database while doing setup:upgrade

Adding CMS Blocks programmatically: I’d like to try!

If the vision of a clean and easy-to-manage code sounds like fun, let’s see how you can use the module in your project. 

The installation process is simple and well-explained in the project’s README on GitHub.

Once you have magently/module-content-setup in the project’s vendor, let’s see how to actually use it.

For the sake of this guide, let’s create a Magently_TestModule module:

php
1 2 3 4 5 6 7 8 9 10 11 //file: app/code/Magently/TestModule/registration.php <?php use Magento\Framework\Component\ComponentRegistrar; ComponentRegistrar::register( ComponentRegistrar::MODULE, 'Magently_TestModule', __DIR__ );
php
1 2 3 4 5 6 7 8 9 10 11 //file: app/code/Magently/TestModule/etc/module.xml <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magently_TestModule"> <sequence> <module name="Magently_ContentSetup"/> </sequence> </module> </config>

To add content to the database, it’s usually best to use Patch Data or UpgradeData; so let’s create a proper file:

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 //file: app/code/Magently/TestModule/Setup/Patch/Data/TestMe.php <?php namespace Magently\TestModule\Setup\Patch\Data; use Magently\ContentSetup\Model\ContentSetupFactory; use Magently\ContentSetup\Model\ContentSetup; use Magento\Framework\Setup\Patch\DataPatchInterface; class TestMe implements DataPatchInterface { /** * @var ContentSetupFactory */ private $contentSetupFactory; /** * @param ContentSetupFactory $contentSetupFactory */ public function __construct(ContentSetupFactory $contentSetupFactory) { $this->contentSetupFactory = $contentSetupFactory; } public function apply() { /** @var ContentSetup $setup */ $setup = $this->contentSetupFactory->create(['moduleName' => 'Magently_TestModule']); } /** * @inheritDoc */ public function getAliases() { return []; } /** * @inheritDoc */ public static function getDependencies() { return []; } }

What exactly happened here? First, we injected Model Factory from the Content Setup module into the constructor:

public function __construct(ContentSetupFactory $contentSetupFactory)

The Factory is required because the ContentSetup object stores the name of the module that uses it:

$setup = $this->contentSetupFactory->create(['moduleName' => 'Magently_TestModule']);

Now that you have the ContentSetup object in the $setup variable, you can add content within the Magently_TestModule module. 

Let’s start by adding a test CMS Page. Make a new Content/Page folder in the Setup directory of your module:  app/code/Magently/TestModule/Setup/Content/Page/

You’re going to need this folder to store files that will be used to add or modify CMS Page. Now, let’s add a test page. 

First, we’re going to need its content, so let’s create a test_page.html file,

php
1 2 3 4 5 // file: app/code/Magently/TestModule/Setup/Content/Page/test_page.html <div> Test page content </div>

And also create a file with the data set, test_page.php

php
1 2 3 4 5 6 7 8 9 10 11 12 // file: app/code/Magently/TestModule/Setup/Content/Page/test_page.php <?php return [ 'title' => 'Magently Test Page', 'identifier' => 'test_page', 'is_active' => 0, 'stores' => [], 'meta_keywords' => 'test page, magently test page', 'content_heading' => 'TEST PAGE' ];

What can be included in the data set? Everything that’s possible to save within the CMS Page instance. 

When the content for your new page is ready, let’s go back to the Patch Data and modify the apply method:

php
1 2 3 4 5 6 7 public function apply() { /** @var ContentSetup $setup */ $setup = $this->contentSetupFactory->create(['moduleName' => 'Magently_TestModule']); $setup->setupPage('test_page'); }

That’s it! Now, run  php bin/magento setup:upgrade and your new page will be added to the database.

You can use the exact same process to add CMS Blocks and Email Templates. Just instead of a Page folder, create Block and Email folders accordingly:

php
1 2 3 4 5 6 7 8 // file: app/code/Magently/TestModule/Setup/Content/Email/test_email.php <?php return [ 'template_code' => 'test_email', 'template_subject' => 'TEST EMAIL' ];
html
1 2 3 4 5 // file: app/code/Magently/TestModule/Setup/Content/Email/test_email.html {{ header }} EMAIL TEMPLATE

And in the code:

        $setup->setupEmailTemplate('test_email');

How it works (optional reading):

If you peer into the module codebase, you will notice how simple it is. 

Let’s start at the end - the intermediary between the module’s logic and the place where the class \Magently\ContentSetup\Model\ContentSetup is used. This class contains references to Services classes in the module. Almost all of them are injected as a Factory because we use them to store a reference to the module they’ve been called by. Otherwise, using Content Setup in Module A and Module B would cause conflicts. Going back, this class is the module’s endpoint and contains methods that power the subsequent logic. 

Let’s move to the Services because they are fairly similar to each other other than the fact that they’re based on different entities. \Magently\ContentSetup\Service\CmsPage - it’s a Service providing the adding/editing CMS Page logic. The main method fetches the DataSet (the aforementioned PHP file) and content - the HTML file. Next, all DataSet elements are set up on the object  - either a new or existing one. In the case of a CMS Page, this can be a title, meta_keywords, etc. Next, the content from the HTML file is set, then saved. 

In the meantime, one of the Providers is used in the above - \Magently\ContentSetup\Provider\FileProvider - this class is responsible for properly supplying the content from a PHP or HTML file, which you create in the module. FileProvider uses PathProvider - a class that’s responsible for building a path to files, based on what module it’s used in.

Summary

That’s it - we strongly believe that the module will help make developers’ work easier and more efficient. I’d like to thank Magently Senior Developer Dawid Mączyński, who added a very valuable contribution to the module development.

Get the module

GitHub link - https://github.com/magently/module-content-setup/
Note: Feel free to open Pull Requests or Issues in case of any problems. We look forward to your feedback and suggestions to help make it even more useful.

Do you need a custom Magento module for your store? Talk to us!

Check our previous micromodule: Magently Checkout Swatches