Important

You are browsing the documentation for version 4.1 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.

See our Release Process documentation for more information on the currently supported and upcoming releases.

Customize Product View Page

In this example we look at how to customize product with different product types, and change product page by category.

Every product view page contains the current product_type in the layout context. You can use it in your layout update conditions. When you customize any page, remember to use Symfony Profiler and look into the Layout section, where the current layout context data and actual layout tree can be found.

Please see the Debug Information section for more details.

First, create an import and a template that will be used in a Simple Product and a Configurable Product.

Import:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/imports/oro_product_view/layout.yml
 1 layout:
 2     actions:
 3         - '@setBlockTheme':
 4             themes: 'AcmeProductBundle:layouts:default/imports/oro_product_view/layout.html.twig'
 5
 6         - '@addTree':
 7             items:
 8                 product_view_page:
 9                     blockType: container
10                 product_container:
11                     blockType: product_view_container
12                     options:
13                         product: '=data["product"]'
14                 product_title_mobile:
15                     blockType: text
16                     options:
17                         text: '=data["locale"].getLocalizedValue(data["product"].getNames())'
18                 product_image:
19                     blockType: product_image
20                     options:
21                         product: '=data["product"]'
22                 product_description_container:
23                     blockType: container
24                 product_title_container:
25                     blockType: container
26                 product_specification_container:
27                     blockType: container
28                 product_specification:
29                     blockType: container
30                 product_specification_sku:
31                     blockType: text
32                     options:
33                         text: '=data["product"].getSku()'
34                 product_description_content:
35                     blockType: container
36                 product_description:
37                     blockType: text
38                     options:
39                         text: '=data["locale"].getLocalizedValue(data["product"].getDescriptions())'
40                         escape: false
41             tree:
42                 page_content:
43                     product_view_page:
44                         product_container:
45                             product_image: ~
46                             product_description_container:
47                                 product_title_container: ~
48                                 product_specification_container:
49                                     product_specification:
50                                         product_specification_sku: ~
51                                 product_description_content:
52                                     product_description: ~
53
54         - '@setOption':
55             id: title
56             optionName: params
57             optionValue:
58                 '%name%': '=data["locale"].getLocalizedValue(data["product"].getNames())'
59
60         - '@setOption':
61             id: page_title
62             optionName: defaultValue
63             optionValue: '=data["locale"].getLocalizedValue(data["product"].getNames())'
64
65         - '@move':
66             id: page_title
67             parentId: product_title_container

Template:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/imports/oro_product_view/layout.html.twig
  1 {% block _product_container_widget %}
  2     {% set attr = layout_attr_defaults(attr, {
  3         'data-page-component-module': 'oroui/js/app/components/view-component',
  4         '~data-page-component-options': {
  5             view: 'oroproduct/js/app/views/base-product-view',
  6             modelAttr: product.jsonSerialize()
  7         },
  8         'data-layout': 'separate',
  9         '~class': ' product clearfix'
 10     }) %}
 11
 12     <div {{ block('block_attributes') }}>
 13         {{ block_widget(block) }}
 14     </div>
 15 {% endblock %}
 16
 17 {% block _page_title_widget %}
 18     <h3 class="product-title">{{ text }}</h3>
 19 {% endblock %}
 20
 21 {% block _product_image_widget %}
 22     <div class="product-gallery-widget product-gallery-widget_vertical product-gallery-widget_l_floated">
 23         <div class="product-gallery product-gallery_vertical">
 24             <div class="product-gallery__image-holder">
 25                 <div class="product-gallery__image-holder__carousel" data-product-gallery>
 26                     <div class="product-gallery__image-holder__container">
 27                         {% set productImage = product.imagesByType('main')|length > 0 ? product.imagesByType('main').first.image : null %}
 28                         <img src="{{ product_filtered_image(productImage, 'product_extra_large') }}"
 29                              alt="{{ product.names|localized_value }}"
 30                              width="378"
 31                              height="378"
 32                                 {% if productImage and isDesktopVersion() %}
 33                                     data-zoom-image="{{ product_filtered_image(productImage, 'product_original') }}"
 34                                     {% set options = {
 35                                     widgetModule: 'jquery-elevatezoom',
 36                                     widgetName: 'elevateZoom',
 37                                     scrollZoom: true,
 38                                     zoomWindowWidth: 630,
 39                                     zoomWindowHeight: 376,
 40                                     borderSize: 1,
 41                                     borderColour: '#ebebeb',
 42                                     lensBorderColour: '#7d7d7d',
 43                                     lensColour: '#000',
 44                                     lensOpacity: 0.22
 45                                     }|json_encode() %}
 46                                     data-page-component-module="oroui/js/app/components/jquery-widget-component"
 47                                     data-page-component-options="{{ options }}"
 48                                 {% endif %}
 49                         />
 50                     </div>
 51                     {% for productImage in product.imagesByType('additional') %}
 52                         <img src="{{ product_filtered_image(productImage.image, 'product_small') }}" width="82"  height="82"/>
 53                     {% endfor %}
 54                 </div>
 55             </div>
 56         </div>
 57     </div>
 58 {% endblock %}
 59
 60 {% block _product_description_container_widget %}
 61     <div class="product__description-container">
 62         {{ block_widget(block) }}
 63     </div>
 64 {% endblock %}
 65
 66 {% block _product_title_container_widget %}
 67     {% set attr = attr|merge({
 68         class: attr.class|default('') ~ " product__title-container"
 69     }) %}
 70
 71     <div {{ block('block_attributes') }}>
 72         {{ block_widget(block) }}
 73     </div>
 74 {% endblock %}
 75
 76 {% block _product_specification_container_widget %}
 77     <div class="product__specification-container product__specification-container_l_floated">
 78         {{ block_widget(block) }}
 79     </div>
 80 {% endblock %}
 81
 82 {% block _product_specification_widget %}
 83     <div class="product__specification">
 84         {{ block_widget(block) }}
 85     </div>
 86 {% endblock %}
 87
 88 {% block _product_specification_sku_widget %}
 89     <div>
 90         {{ 'oro.product.frontend.index.item'|trans }} <span class="red">{{ block_widget(block) }}</span>
 91     </div>
 92 {% endblock %}
 93
 94 {% block _product_description_content_widget %}
 95     <div class="product__description-content product__description-content_l_floated">
 96         <div class="product__description">
 97             {{ block_widget(block) }}
 98         </div>
 99     </div>
100 {% endblock %}

Simple Product

In our case a simple product inherits all import properties.

Create a layout update that includes the oro_product_view import and has the conditions to check if the current product has a simple product type.

Our simple product has the following look:

Simple Product example
src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/simple_product.yml
1 layout:
2     imports:
3         - oro_product_view
4
5     actions: []
6
7     conditions: 'context["product_type"] == "simple"'

Configurable Product

Create a layout update that includes the oro_product_view import and has the conditions to check if the current product has a configurable product type.

Add the product variants block and the template.

Our configurable product has the following look:

Configurable Product example

Product Variants Block:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/configurable_product.yml
 1 layout:
 2     imports:
 3         - oro_product_view
 4
 5     actions:
 6         - '@setBlockTheme':
 7             themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/configurable_product.html.twig'
 8
 9         - '@add':
10             id: product_variants
11             blockType: product_variants
12             parentId: product_specification_container
13             siblingId: product_specification
14             options:
15                 variants: '=data["product_variants"].getVariants(data["product"])'
16
17     conditions: 'context["product_type"] == "configurable"'

Template:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/configurable_product.html.twig
 1 {% block _product_variants_widget %}
 2     {% for variant in variants %}
 3         {% set key = variant.name|lower %}
 4         <div class="product__{{ key }}s">
 5             <label for="product_variants_{{ key }}">Select {{ variant.name }}</label>
 6
 7             {% if key == 'color' %}
 8                 {% for key, name in variant.elements %}
 9                     <span class="badge badge_sm product__colors__item color-item_{{ key }}">
10                         <input type="checkbox" name="{{ key }}" alt="{{ name }}" />
11                     </span>
12                 {% endfor %}
13             {% else %}
14                 <select id="product_variants_{{ key }}" class="select select--size-s">
15                     {% for key, name in variant.elements %}
16                         <option value="{{ key }}">{{ name }}</option>
17                     {% endfor %}
18                 </select>
19             {% endif %}
20         </div>
21     {% endfor %}
22 {% endblock %}

Block Types

For this example we need to create a product_variants block type used in the configurable product layout update.

src/Acme/Bundle/ProductBundle/Resources/config/block_types.yml
 1 services:
 2 ...
 3     acme_product.layout.type.product_variants:
 4         parent: oro_layout.block_type.abstract_configurable_container
 5         calls:
 6             - [setOptionsConfig, [{variants: {required: true}}]]
 7             - [setName, ['product_variants']]
 8         tags:
 9              - { name: layout.block_type, alias: product_variants }
10 ...

Data Providers

Also, we need to create a product_variants data provider used in the configurable product layout update.

src/Acme/Bundle/ProductBundle/Resources/config/services.yml
1 services:
2 ...
3     acme_product.layout.data_provider.product_variants:
4         class: Acme\Bundle\ProductBundle\Layout\DataProvider\ProductVariantsProvider
5         tags:
6             - { name: layout.data_provider, alias: product_variants }
7 ...

The following is an example of the data provider:

src/Acme/Bundle/ProductBundle/Layout/DataProvider/ProductVariantsProvider.php
 1 <?php
 2
 3 namespace Acme\Bundle\ProductBundle\Layout\DataProvider;
 4
 5 use Symfony\Component\PropertyAccess\PropertyAccess;
 6
 7 use Oro\Bundle\EntityExtendBundle\Entity\AbstractEnumValue;
 8 use Oro\Bundle\ProductBundle\Entity\Product;
 9
10 class ProductVariantsProvider
11 {
12     /**
13      * @param Product $product
14      *
15      * @return array
16      */
17     public function getVariants(Product $product)
18     {
19         $variants = [];
20         $variantFields = $product->getVariantFields();
21         foreach ($variantFields as $variantField) {
22             $variants[strtolower($variantField)]['name'] = $variantField;
23         }
24
25         $propertyAccessor = PropertyAccess::createPropertyAccessor();
26
27         $variantLinks = $product->getVariantLinks();
28         foreach ($variantLinks as $variantLink) {
29             $childProduct = $variantLink->getProduct();
30             foreach ($variants as $key => $variant) {
31                 /** @var AbstractEnumValue $enumValue */
32                 $enumValue = $propertyAccessor->getValue($childProduct, $key);
33                 $variants[$key]['elements'][$enumValue->getId()] = $enumValue->getName();
34             }
35         }
36
37         return $variants;
38     }
39 }

Change Product Page by Category

Every product view page contains the current category_id and the category_ids in the layout context. You can use these values to evaluate the layout conditions. When you customize any page, remember to use Symfony Profiler and look into the Layout section, where the current layout context data and actual layout tree can be found.

Please see the Debug Information section for more details.

Example 1 (by category ID)

We have a “Headlamps” category and we want to add some static html to all products in this category.

The condition is: conditions: ‘context[“category_id”] == 4’.

The result is:

Change Product Page by Category example 1
src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/headlamps.yml
 1 layout:
 2     actions:
 3         - '@setBlockTheme':
 4             themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/headlamps.html.twig'
 5
 6         - '@add':
 7             id: product_sale_banner
 8             blockType: block
 9             parentId: product_view_main_container
10             siblingId: ~
11             prepend: false
12
13     conditions: 'context["category_id"] == 4'

Template:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/headlamps.html.twig
1 {% block _product_sale_banner_widget %}
2     <div class="text-right">
3             <img src="{{ asset('/bundles/oroproduct/default/images/headlamps.jpg') }}"/>
4         </div>
5         <br />
6 {% endblock %}

Example 2 (by parent category ID)

As an example, we want to add a sale banner to all products in first level category and their children. We have a category called “Furniture” with id = 6.

The condition is: conditions: ‘6 in context[“category_ids”]’.

The result is:

Change Product Page by Category example 2
src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/furniture.yml
 1 layout:
 2     actions:
 3         - '@setBlockTheme':
 4             themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/furniture.html.twig'
 5
 6         - '@add':
 7             id: product_sale_banner
 8             blockType: block
 9             parentId: page_content
10             siblingId: ~
11             prepend: true
12
13     conditions: '6 in context["category_ids"]'

Template:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/furniture.html.twig
1 {% block _product_sale_banner_widget %}
2     <div class="text-center">
3         <img src="{{ asset('/bundles/oroproduct/default/images/furniture_sale.jpg') }}"/>
4     </div>
5     <br />
6 {% endblock %}

Product Page Templates

Please see the Page Templates section for more details.

You can modify the visual presentation of the product view page for every product, or choose a page template for all of them by default.

First, create a config for the page_templates in our theme.

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/config/page_templates.yml
 1 templates:
 2     -
 3         label: Custom page template
 4         description: Custom page template description
 5         route_name: oro_product_frontend_product_view
 6         key: custom
 7     -
 8         label: Parent Additional page template
 9         description: Additional page template description
10         route_name: oro_product_frontend_product_view
11         key: additional
12 titles:
13     oro_product_frontend_product_view: Product Page

Next, add some layout updates:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/page_template/custom/layout.yml
1 layout:
2     actions:
3         - '@remove':
4             id: product_view_attribute_group_images
5         - '@move':
6             id: product_view_specification_container
7             parentId: product_view_aside_container
src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/page_template/additional/layout.yml
1 layout:
2     actions:
3         - '@setBlockTheme':
4             themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/page_template/additional/layout.html.twig'
5         - '@add':
6             id: product_view_banner
7             blockType: block
8             parentId: product_view_content_container

Add some templates:

src/Acme/Bundle/ProductBundle/Resources/views/layouts/default/oro_product_frontend_product_view/page_template/additional/layout.html.twig
1 {% block _product_view_banner_widget %}
2     <div class="text-center">
3         <img src="{{ asset('/bundles/oroproduct/default/images/flashlights.png') }}"/>
4     </div>
5     <br />
6 {% endblock %}

Global Level

To apply a custom page template to all products, go to System > Configuration > Commerce > Design > Theme.

In the Page Templates section, choose Custom page template in the Product Page select.

In the storefront, here is what it will look like:

Global Product View Page with Custom Page Template

Entity level

To apply a custom page template to the selected products, go to Products > Products, find your product and click edit.

In the Design section, choose Additional page template in the Page Template select.

In the storefront, here is what it will look like:

Entity Product View Page with Custom Page Template