Content Widgets 

Content Widgets enable you to render any html text generated by your custom PHP code, such as a marketing banner or a contact form.

Create a Content Widget Type 

We are going to illustrate how to create a content widget type to render the copyright. It should have an option to control the display format (short or long).

You can create a content widget type in four steps outlined below.

1. Extend AbstractContentWidgetType 

To implement a new content widget, create a class that stores content widget type configuration and renders the content widget. The class should extend AbstractContentWidgetType.

namespace Acme\Bundle\CopyrightBundle\ContentWidget;

use Oro\Bundle\CMSBundle\ContentWidget\AbstractContentWidgetType;
use Oro\Bundle\CMSBundle\Entity\ContentWidget;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;

class CopyrightContentWidgetType extends AbstractContentWidgetType
{
    public static function getName(): string
    {
        return 'copyright';
    }

    public function getLabel(): string
    {
        return 'acme.copyright.content_widget.copyright.label';
    }

    public function getSettingsForm(ContentWidget $contentWidget, FormFactoryInterface $formFactory): ?FormInterface
    {
        return $formFactory->createBuilder(FormType::class)
            ->add('isShort', CheckboxType::class, ['label' => 'acme.copyright.settings.is_short.label', 'required' => false])
            ->getForm();
    }

    public function getDefaultTemplate(ContentWidget $contentWidget, Environment $twig): string
    {
        return $twig->render('@AcmeCopyright/CopyrightContentWidget/widget.html.twig', $contentWidget->getSettings());
    }
}

Note

When you want to use an existing form type in you content widget type:

    public function getSettingsForm(ContentWidget $contentWidget, FormFactoryInterface $formFactory): ?FormInterface
    {
        return $formFactory->create(FormType::class);
    }

The form type has the following code:

<?php

namespace Acme\Bundle\CopyrightBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * Copyright content widget form type.
 */
class CopyrightContentWidgetType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add(
            'isShort',
            CheckboxType::class,
            ['label' => 'acme.copyright.settings.is_short.label', 'required' => false]
        );
    }
}

It should be registered in a service container with the oro_cms.content_widget.type tag.

services:
    Acme\Bundle\CopyrightBundle\ContentWidget\CopyrightContentWidgetType:
        tags:
            - {name: 'oro_cms.content_widget.type'}

Note

When autoconfiguration is enabled, tagging the service manually is unnecessary.

services:
    _defaults:
        autoconfigure: true

    Acme\Bundle\CopyrightBundle\ContentWidget\CopyrightContentWidgetType: ~

Add translations to strings in a template.

acme.copyright:
    content_widget:
        copyright:
            label: 'Copyright'

    settings:
        is_short.label: 'Short'

2. Create a Template to Render the Widget in the Storefront 

Create a template to render the content widget in the storefront.

{%- set copyright = isShort ? 'acme.copyright.text.short' : 'acme.copyright.text.default' -%}
<copy>{{ copyright|trans({ '%year%': 'now'|date('Y') }) }}</copy>

Add translations to strings in the template.

acme.copyright:
    text:
        default: '(c) %year%. All rights reserved'
        short: '(c) %year%'

3. (Optionally) Render the Widget Info in the Back-Office 

3.1 Create a Template 

{% import '@OroUI/macros.html.twig' as UI %}

{{ UI.renderProperty('acme.copyright.settings.is_short.label'|trans, settings.isShort ? 'Yes'|trans : 'No'|trans) }}

3.2 Implement the getAdditionalInformationBlock Method in the Content Widget Type 

    protected function getAdditionalInformationBlock(ContentWidget $contentWidget, Environment $twig): string
    {
        return $twig->render(
            '@AcmeCopyright/CopyrightContentWidget/view.html.twig',
            ['settings' => $contentWidget->getSettings()]
        );
    }

Note

To pass additional data to the template, you can override getBackOfficeViewSubBlocks method. The example below illustrates how to add two blocks with two subblocks in each block.

namespace Acme\Bundle\CopyrightBundle\ContentWidget;

use Oro\Bundle\CMSBundle\ContentWidget\AbstractContentWidgetType;
use Oro\Bundle\CMSBundle\Entity\ContentWidget;
use Twig\Environment;

/**
 * Type for the copyright widgets.
 */
class CopyrightContentWidgetType extends AbstractContentWidgetType
{
    public function getBackOfficeViewSubBlocks(ContentWidget $contentWidget, Environment $twig): array
    {
        return [
            [
                'title' => 'oro.cms.contentwidget.sections.additional_information_block1.label',
                'subblocks' => [
                    [
                        'data' => [
                            $twig->render(
                                '@AcmeCopyright/CopyrightContentWidget/acme_template1.html.twig',
                                ['settings' => $contentWidget->getSettings()]
                            ),
                        ]
                    ],
                    [
                        'data' => [
                            $twig->render(
                                '@AcmeCopyright/CopyrightContentWidget/acme_template2.html.twig',
                                ['settings' => $contentWidget->getSettings()]
                            ),
                        ]
                    ],
                ]
            ],
            [
                'title' => 'oro.cms.contentwidget.sections.additional_information_block2.label',
                'subblocks' => [
                    [
                        'data' => [
                            $twig->render(
                                '@AcmeCopyright/CopyrightContentWidget/acme_template3.html.twig',
                                ['settings' => $contentWidget->getSettings()]
                            ),
                        ]
                    ],
                    [
                        'data' => [
                            $twig->render(
                                '@AcmeCopyright/CopyrightContentWidget/acme_template4.html.twig',
                                ['settings' => $contentWidget->getSettings()]
                            ),
                        ]
                    ],
                ]
            ],
        ];
    }
}

4. (Optionally) Pass Custom Data to the Template when Rendering Widget in the Storefront 

Override getWidgetData method in the Content Widget Type.

namespace Acme\Bundle\CopyrightBundle\ContentWidget;

use Oro\Bundle\CMSBundle\ContentWidget\AbstractContentWidgetType;
use Oro\Bundle\CMSBundle\Entity\ContentWidget;
use Oro\Bundle\ProductBundle\Entity\Product;

/**
 * Type for the copyright widgets.
 */
class CopyrightContentWidgetType extends AbstractContentWidgetType
{
    ...

    public function getWidgetData(ContentWidget $contentWidget): array
    {
        // For example, fetch the product from entity manager to pass it to the template
        $product = $this->doctrine->getManagerForClass(Product::class)
              ->find(Product::class, $contentWidget->getSettings()['productId']);

        return ['contentWidget' => $contentWidget, 'product' => $product];
    }
}

5. (Optionally) Add Content Widget Templates 

It is possible to provide multiple templates for some content widget types. This allows the user to select which template to use when creating a content widget instance.

If there is a least one template defined, a list of all templates collected from all themes for this widget type is displayed on the content widget create/edit form drop-down.

During rendering in the storefront, if the template selected by the user is not available in the current theme, the widget is rendered using its default template (set in the getDefaultTemplate method). To add a new layout template for a widget type, follow the steps below:

5.1 Add Template Definition to Theme Configuration 

Create a widgets.yml file for certain theme (e.g., default) in the config folder Resources/views/layouts/default/config/widgets.yml:

layouts:
    copyright:
        first: 'acme.copyright.content_widget.copyright.label'
  • copyright stands for the widget type to which template is added.

  • first key represents a particular theme with its name (translation key) as a value.

5.2 Add Layout Update File and Template 

Create a layout update file in the appropriate content widget folder inside the desired theme. Keep in mind that all widget layout update files should follow a naming convention: content_widget/{your_unique_widget_type_name}, e.g.,: Resources/views/layouts/default/content_widget/copyright/content_widget.yml.

layout:
    actions:
        - '@setBlockTheme':
            themes: 'content_widget.html.twig'
...

Follow the same steps with templates for the layout update with customized markup Resources/views/layouts/default/content_widget/copyright/content_widget.html.twig:

{% block _copyright_content_widget_layout_name_widget %}
    <p>{{ block_widget(block) }}</p>
{% endblock %}

The widget template for the Copyright widget should be available after clearing the cache. To define templates for other themes, apply the same actions making sure you place files in the appropriate theme folders and follow the naming conventions.

Now an administrator can create content widgets of a new type from the UI by following steps outlined in the Content Widgets User Guide user documentation.

Support Content Widgets in Custom Content 

To support widgets in any text, you should apply the render_content filter in a twig template.

For example:

{{ entity.content | oro_html_sanitize | render_content }}

Display Label for Content Widget in Layout 

Make sure that you have completed the data for the labels field of the content widget. Then, in layout, you will have the next variables defaultLabel and labels. You can display these variables in the following ways:

layout:
    actions:
        - '@setOption':
            id: layoutBlockId
            optionName: label
            optionValue:
                '=data["defaultLabel"]'
                # or
                '=data["locale"].getLocalizedValue(data["labels"])'

...