In the previous chapters, we learned how to create documents and objects and implement very simple websites using such features.
In this chapter, we will have a deep dive into the creation of custom CMS pages. In fact, if you are templating a document or creating a standalone page, you have many tools that make the development experience in Pimcore great for you. This chapter will cover many aspects that are fundamental for a full overview of Pimcore's CMS capabilities; such features are basic tools for creating custom web pages, which we have combined to discuss user inputs and templates for creating content.
The chapter is structured as follows:
Let's see Pimcore in action!
As with the previous chapters, there is a demo that you can find in our GitHub repository, which you can find here: https://github.com/PacktPublishing/Modernizing-Enterprise-CMS-using-Pimcore.
All you need to do to run the demo connected with this chapter is to clone it and navigate to the 8. Creating Custom CMS Pages folder and start the Docker environment.
To do so, just follow these instructions:
docker-compose up
docker-compose exec php bash restore.sh
What you will get with this setup is the following:
Now you are ready to play with the demo related to this chapter!
In this section, we will learn how the MVC (Model View Controller) model works and how to use it for creating a standalone web page that works outside the document scope. This is very important for covering all possible needs that you may encounter on your path with Pimcore as a Content Management System.
The MVC principle is very easy. When a URL matches a set of rules (Routing), a controller class is activated and this will compute data (Model) using certain business logic (Controller). The data is then sent to the View that implements the presentation logic and shows content to the user.
In this section, we will cover the most important concepts relating to building embedded web pages using the MVC pattern:
Let's see them in detail.
Pimcore Controllers implement the "C" part of the MVC pattern. Controllers are responsible for business logic or, in simpler words, are the part of your source code that reads, manipulates, and prepares data to be passed to the presentation layers (views). It is good practice to keep all the reusable logic in service classes, but the point that connects presentation with the business layer is, in fact, the controller element.
Pimcore offers an abstract class (FrontendController) that can be used as a base for your controller implementation. This simply means that all your controllers will usually inherit from the frontend controller. The naming convention of your files will follow the generic Symfony rules. In a simple scenario where you use Symfony's standards for your website, you will have the following rules:
Inside controllers, you can create any action you want. Each action is responsible for a single functionality and is linked to a URL. We usually create a controller for each topic or homogeneous group or function (that is, CustomerController, which manages all the customer's features). If you invoke a controller omitting the action name, default will be used.
As we explained in Chapter 4, Creating Documents in Pimcore, we can choose a controller for each document and Pimcore comes out with a ready-to-go controller called DefaultController. We can create controllers that are unrelated to documents and simply implement custom web pages.
Inside controllers, you can access some special variables that can help you to define how to build the desired output:
In the next sections, you will find some examples of controller actions that cover all the most common use cases.
The following example is an action that adds a variable into the view with a value. In this case, the value is just text, but imagine that you could use the input from $request for computing data and add a more complex data object:
public function dataAction(Request $request)
{
$input=$request->get('data');
return array(
'content' =>$input
);
}
Another requirement that we may have is to set some HTTP headers. If these values are fixed, you can add them using an annotation, or you can access and programmatically alter the response object using the addResponseHeader helper:
/**
* @ResponseHeader("X-Foo", values={"123456", "98765"})
*/
public function headerAction(Request $request)
{
$response = new Response();
// using code:
$response->headers->set('X-Foo', 'bar');
return $response;
}
The previous code adds three values for the same header: the first two from the annotation, and the other from the piece of code inside the method.
If you are scared about the fixed conventions of templating, we will reassure you with the next example. In the following snippet, we will override the normal template path by setting it manually:
public function differentPathAction()
{
return $this->render("Default/default.html.twig", ["foo" => "bar"]);
}
Alternatively, you can manually specify the template path to the @Template() annotation and just return the data, as in the following snippet:
/**
* @Template(Default/default.html.twig)
*/
public function differentPathAction()
{
return return ["foo" => "bar"];
}
Even though Pimcore comes with a powerful API engine and Datahub Change this throughout the chapter lets you make anything using GraphQL, you may need to produce APIs manually. This scenario is easy to manage with controllers and the json function. In the next example, you will see how to create a json response starting from plain data:
public function jsonAction(Request $request)
{
return $this->json(array('key' => 'value'));
}
This outputs the json serialization of data as the body of the response and sets the content type to application/json.
As a final fallback, if none of the standard solutions offered by Pimcore will satisfy your needs, there is the bare option to create a Symfony response object manually and return it to the MVC engine. Using this option, you will be free to set all response parameters, for mime type to the raw content without any limitation. In the next example, we will return fixed text:
public function customAction(Request $request)
{
return new Response("Just some text");
}
}
The previous block of code returns a response with text content as an example. Given the Request object, you can implement all the code you want and then produce a custom Response.
All the preceding actions are intended to be contained in a controller class like this:
<?php
namespace AppController;
use PimcoreControllerFrontendController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use PimcoreControllerConfigurationResponseHeader;
class MyController extends FrontendController
{
// Add your actions here
}
This set of samples is not exhaustive but contains the most important features and is a great starting point. You can find more details in the official documentation: https://pimcore.com/docs/pimcore/current/Development_Documentation/MVC/index.html.
This section covers the "V" component of the MVC pattern. Views are the components that receive data and render it by implementing presentation logic. A views file can be written using Twig or a PHP template but, as we told you in Chapter 4, Creating Documents in Pimcore, we will focus only on the Twig solution, which allows a more strict separation between business and presentation logic and makes Pimcore a real detached CMS. The downside of this solution is that the logic that you can implement in a Twig file is limited as it forces you to implement all the business logic in the controller. This strict separation may seem limiting at the beginning, but when you are confident with the pattern, you will agree that it's more clean, reusable, and maintainable. Other than all the platform-specific features, called helpers, Pimcore's Twig files support all the standard features of Twig.
Here is a list of the most important Pimcore helpers.
In the following code, we loaded an object by its id (in our case, 2) and we displayed the Title property:
{% set myObject = pimcore_object(2) %}
{{ myObject.getTitle() }}
Our object has a Title property accessed by the standard getTitle method, so the value will be printed.
In the next snippet, we have loaded a document and printed the title:
{% set myDoc = pimcore_document(3) %}
title: {{ myDoc.getTitle}} </br>
url: {{ myDoc}}
The myDoc element is Pimcore's document and all its properties can be accessed.
This helper loads an asset that can be used in the template. In the next example, we loaded one asset and we displayed the filename and url:
{% set myDoc = pimcore_asset(2) %}
url: {{ myDoc}} <br>
filename: {{ myDoc.getFilename}}
Alternatively, you can find an asset by its path, using the following shortcut:
{% set asset = asset('/path/to/image.jpg') %}
As usual, the assigned variable can be used in the template file for implementing any presentation logic.
This function calls an arbitrary action and prints the result. In the next example, we used the /custom/json example and we rendered the output, passing items=11 as the parameter:
{{ render(controller('App\Controller\CustomController::jsonAction', { items: 11 }))
}}
The parameter order is action, controller, bundle, and parameters.
Pimcore's cache simply implements an in-template caching functionality. You can use this to cache some parts of the HTML page directly in the template, independent of the other global definable caching functionality. This can be useful for templates that need a lot of calculation or require a huge amount of objects (such as navigations, and so on). In the next block of code, we will see caching in action:
{% set cache = pimcore_cache("cache_key", 60, true) %}
{% if not cache.start() %}
<h1>If you refresh the page this date will remain the same</h1>
{{ 'now'|date('y-m-d') }} v{{ 'now'|date('U') }}
{% do cache.end() %}
{% endif %}
The parameter order is the name of the key, the timeout in seconds, and an optional flag for forcing the cache in admin mode.
The pimcore_device function helps when implementing adaptive designs. The next piece of code displays the usage of this helper in a template snippet:
{% if pimcore_device().isPhone() %}
I'm a phone
{% elseif pimcore_device().isTablet() %}
I'm a table
{% elseif pimcore_device().isDesktop() %}
I'm a desktop device
{% endif %}
If you are running this script on your PC, the output will be I'm a desktop device.
From the default Symfony objects, you can access the request data. This can be done using the app.request item that contains all the information that we need. In the next example, we used this method to get the "_dc" URL parameter that you usually have in a page preview:
{{ app.request.get("_dc") }}
This is just a sample, and you can access all the request parameters. You can look at the official documentation for more information, here: https://symfony.com/doc/current/templates.html#the-app-global-variable.
This helper replaces glossary terms with links. The glossary module is a powerful tool, making internal and external linking easy and smart, and which Pimcore has out of the box. As an example, you can connect the word "Pimcore" with the official website, so that each time you use it in HTML, it will be replaced with a link to the website. The pimcore_glossary helper will be used by this helper to render links. To test this feature, follow these steps:
{% pimcoreglossary %}
My content PIMCore loves CMS
{% endpimcoreglossary %}
In the previous snippet, we surround the text with the glossary function. Because we defined the words PIMcore and CMS inside the glossary, they will be transformed into a link on the web page. This is the final result:
This was a simple example to explain the concept, but it shows how powerful this feature is.
This helper adds custom placeholders into the template. The next snippet defines a placeholder called myplaceholder, which is configured for building an H3 tag surrounding the "My content" value:
{% do pimcore_placeholder('myplaceholder')
.setPrefix("<h3>")
.setPostfix("</h3>")
.set("My content") %}
{# Print placeholder #}
{{ pimcore_placeholder('myplaceholder') }}
The output of this is <h3>My content</h3>.
This helper collects a list of head links (stylesheet or any other head link tag) and prints them on the head section of the web page. Based on the presentation logic, the links are collected (you may or may not include a file based on some special conditions) and then printed once.
In the following example, the snippets append a favicon (favorite icon – the logos you see in browser tabs when you surf websites) to the link list:
{% do pimcore_head_link({'rel' : 'icon', 'href' : '/img/favicon.ico'},"APPEND") %}
{# Print head links#}
{{ pimcore_head_link()}}
In the example, we appended a favicon to the list. Using the correct configuration of pimcore_head_link, we can also define the order using relative inclusion (for example, add x file after y file).
This helper prints HTTP meta tags. It can collect a set of items that will be printed once in the header section. In the next block of code, like in the HeadLink helper, the data is collected and printed to the function by the final call:
{% do pimcore_head_meta().appendName('description', 'My SEO description for my awesome page') %}
{# adding addictional properties #}
{% do pimcore_head_meta().setProperty('og:title', 'my article title') %}
{# Print tags #}
{{ pimcore_head_meta() }}
This is the same as HeadLink and Style, but for a JavaScript file. The next block of code adds a script to the list and then prints it on the page:
{% do pimcore_head_script().appendFile("/myscript.js") %}
{# Print tags #}
{{ pimcore_head_script()}}
This helper manages inline styles. It is like HeadLink, but with inline scripts. In the next piece of code, we add a file called ie.css with conditional wrapping that, in this case, limits the CSS action to Internet Explorer before version 11:
{% do pimcore_head_style().appendStyle("ie.css", {'conditional': 'lt IE 11'}) %}
{# Print tags #}
{{ pimcore_head_style() }}
Creates and save the HTML document's <title> for later retrieval and output. The following script adds the special title tag into the page:
{% do pimcore_head_title('My first part') %}
{# Print tags #}
{{ pimcore_head_title() }}
This helper includes a Pimcore document in the page. In the next snippet, we see how the MySnippet document is rendered and added to the page:
{{ pimcore_inc('/MySnippet') }}
This helper adds inline scripts to a page. The usage is very similar to HeadScript, but intended for inline usage. The next piece of code adds myscript.js to the list and then prints it on the page:
{% do pimcore_inline_script().appendFile("/myscript.js") %}
{# Print tags #}
{{ pimcore_inline_script() | raw }}
This helper is responsible for generating and adding a navigation menu. In the next piece of code, you will find an example that prints a menu based on the document hierarchy:
{% set mainNavStartNode = document.getProperty('mainNavStartNode') %}
{% set navigation = pimcore_build_nav({
active: document,
root: mainNavStartNode
}) %}
{# Print tags #}
{{ pimcore_render_nav(navigation) }}
In the snippet, it rendered a menu that starts from mainNavStartNode, which is the root document, and uses the current document as the active one.
You can refer to the official documentation for more information on navigation: https://pimcore.com/docs/pimcore/current/Development_Documentation/Documents/Navigation.html.
This helper directly includes a template inside the page. Look at the following script:
{{ include("Default/include.html.twig", {'par':'value'}) }}
This script includes the Default/include.html.twig template, where you can use the set of parameters passed as the input.
This is a Symfony feature integrated within Pimcore's engine. With the pipe filter, you can transform text using the translations database and replace the original text with the translated version. To do this, do the following:
{% set message ="my-test-key" %}
{{ message|trans }}
This script sets a variable with a string value (my-test-key) and then prints it using the trans filter.
The translation functionality is very useful and easy to manage with its visual interface.
Pimcore has a bucket of configuration keys that are easily editable via the web interface. You can add them from Settings | Website Settings and then you can insert the value into the template or controller. To do this, do the following:
{{ pimcore_website_config('mykey') }}
Using the preceding code, you will get MyValue as the result of the helper.
The visual editing of settings is very useful because it lets us create configurable templates and websites that can be easily managed centrally.
The instanceof construct is useful for checking whether an object is of a given type. In the next piece of code, we will check whether an asset instance is of type Image or not:
{% if item is instanceof('\Pimcore\Model\Asset\Image') %}
{# print the image #}
{% elseif is instanceof('\Pimcore\Model\Asset\Video) %}
{# print the video #}
{% endif %}
This feature is important because you can alter the code flow so it either completes or doesn't complete a task based on the object type. In the previous example, we get an object that could be a video or an image, and we can display it properly based on the type.
When you are working with images, printing them at the right size is fundamental. The Pimcore thumbnail feature helps a lot with templating. What we can do is to define the asset thumbnails from the administration; you should have already discovered this topic in Chapter 5, Exploring Objects and Classes. Let's see this feature in action in a few simple steps:
We used the PNG format, and we set Scale by Height to 200. Now the images that we will upload will have a proper thumbnail.
{% set asset = pimcore_asset(2) %}
{{ asset.getThumbnail('MyThubnails').getHtml() | raw }}
The scripts get the thumbnail (the files generated at the first usage) for the image and print the image tag to the document.
{{ asset.getThumbnail({
width: 50,
format: 'png'
}).getHtml() | raw }}
This example is quite similar to the previous one but uses settings that are not predefined by a thumbnail configuration.
That whole list of functions covers the set of features that Pimcore offers to us out of the box. In addition, you will have all the default Symfony functions, which you can discover using the official documentation: https://symfony.com/doc/current/reference/twig_reference.html.
But what should you do if neither Pimcore nor Symfony implements what you need? The easier solution, if we are talking about business logic, is to manipulate data inside the controller, and produce a clean output for the view. In some cases, we may need to implement some complex presentation logic that is hard to manage with Twig syntax. Moreover, we do not want to move this logic to the controller (where it will be easy to implement) because it will create coupling between the template and controller, and this is not what we want (do you remember when we talked about the benefits of a detached solution in Chapter 1, Introducing Pimcore?). The solution to the problem of implementing complex presentation logic inside templates is provided in the next section where we will see how to add methods to the template engine and implement reusable helpers for our project.
In the previous section, we learned how to use template helpers to generate dynamic content. Now it is time to learn how to extend the template helper system and add your own function inside the set. In the next example, we will create a template helper that will display the current date and time in a given format. You may object that the Twig templating already offers a feature more or less like this, but remember that this is an example to explain the template helper extension, so it is better to keep our case study as simple as possible. Now, follow the next steps:
<?php
…
class Timestamp extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('timestamp', [$this, 'timestamp']),
];
}
public function timestamp(String $format)
{
echo date($format);
}
}
getFunctions is a special function that lists all the helper extensions exposed from this extension. In our case, we have one function, called timestamp. This function computes a textual date representation based on the format that's received from the user. This function is mapped to a command called timestamp and can be used inside the template engine. For simplicity, I used for the helper name the function that computes the result, but you can choose your own.
Services:
…
AppTemplatingHelperTimestamp:
public: true
tags:
- { name: templating.helper, alias: timestamp}
{{ timestamp("Y-m-d") }}
You will get something similar to 2020-11-03.
In this section, we learned how to add functionality to the template engine. This is good for reusing code and overcomes the Twig syntax limitations. In the next section, we will learn how to master routing rules to connect URLs with controllers.
The last step of mastering custom pages is routing. We learned in Chapter 4, Creating Documents in Pimcore, that each document has a path that can be changed by the document editing interface. What if we are not working with documents, but with custom pages? In this section, we will learn about the options that Pimcore offers, which are the following:
Let's look at them in detail.
To add a route, there isn't anything more complex than adding it to the configuration. These settings are contained inside the routing.yml file, and these rules follow the Symfony standard that you can find here: https://symfony.com/doc/current/routing.html.
In the next code fragment, we can see an example of a rule:
custom_rule:
path: /custom/actionname/{parameter}
controller: AppControllerCustomController:myaction
defaults:
parameter: "my default value"
The most relevant settings for our purpose are the following:
controller: This is the controller, including the action name; it supports the Symfony syntax. In the case that you write AppControllerCustomController:myaction, you will activate myaction inside the CustomController controller of the application.
Note that even if we used names with an understandable naming convention, you could define any rule as you want. The parameter inside the URL (or query path) is parsed and delivered to the action.
If you prefer, the second option is to use routing as an annotation of the controller's actions. The equivalent of the previous rule is the following:
/**
* @Route("/custom/actionname/{parameter}", name="custom_ rule")
*/
public function myaction(string $parameter)
{
// ...
}
The routing rule is bidirectional, so it can both produce a URL from the parameter values and give the parameters values from a URL. For example, consider that you have the /product/{id} rule for the product URL. If the user enters /product/5 in the browser, the routing engine is able to tell you that the ID parameter is 5. On the other hand, you can ask the engine to generate a URL for the product with ID 5 and you will get back /product/5. In simple words, you can build the URL automatically from the rule name and parameter. This is easy to do with the path helper:
{{% pimcore_path('custom_rule', {'parameter': 67}); }}
This will generate the full URL, /custom/actionname/67.
The hardcoded rules used in the previous section are a good solution but are static, and you need access to the source code to understand or change them. The other solution offered by Pimcore is to define them visually, using the admin UI. Under Settings | Static Routes, you can find a table where you can enter all values of a routing rule (including the name, pattern, parameter names, and building rules). This makes rule management very easy and you can monitor how many rules you have and their configuration without accessing the source code.
You can follow these steps to see this method in action:
/**
* @Template()
*/
public function dataAction(Request $request)
{
$input=$request->get('data');
$content = "data to show $input";
return array(
'content' => $content,
);
}
This code will take the data parameter from the URL and will pass it to the view using the content variable.
{{ content }}
This script will print the content variable.
In this editor, we can add rules. Each column lets you set the parameter, so enter the values in the previous figure using the following instructions:
Similar to the previous example, we can generate a URL using a rule name and parameters with the template helper.
The settings that you edited through the admin UI are saved in a PHP configuration file (var/config/staticroutes.php), so it's also possible to edit the code directly or save them by committing it to a Git repository. The next piece of code shows what we obtained by adding the previous rules:
<?php
return [
1 => [
"id" => 1,
"name" => "custom",
"pattern" => "/\/custom_data\/(.*)?\//",
"reverse" => "/custom_data/%data/",
"module" => NULL,
"controller" => "@App\Controller\ CustomController:dataAction",
"variables" => "data,",
"defaults" => "empty",
]
];
Configurable routes are very useful when you want to keep URL routing flexible, letting the user choose the paths at runtime. This is essential for matching specification changes from the customer about URLs (maybe due to the SEO requirement changes) without touching a single line of code.
Redirects are a useful feature of Pimcore for directing the user to the correct pages – whether it be for marketing URLs, for redirects after a website's relaunch, or redirects for moved documents.
The process for creating a redirect is very simple. Just follow these steps:
The Type dropdown lets you choose the type of redirection, while Source and Target allow you to choose the mapping rule that redirects the source to the target URL.
In this section, we learned how to create custom web pages using the MVC model. In particular, we covered all the most important topics:
In the next section, we will have a deep dive into Pimcore editables and we will learn how to combine them to get results without writing tons of lines of code.
As we introduced in Chapter 4, Creating Documents in Pimcore, Pimcore's editables are a set of visual components that can be used to enter information in the CMS page and are able to render the output to get the web page ready. You should remember the pimcore_input and pimcore_wysiwyg components; well, the good news is that Pimcore has a lot of components like that and covers most of your user needs. In this section, we will take a look at these.
Important note
In the Editables list, we should have Area and Areablock components, but we won't discuss these in this chapter. These elements, in conjunction with Bricks, are the pillars of code reuse in Pimcore and will be covered in Chapter 10, Creating Pimcore Bricks. The motivation is that without discovering what bricks are, the Area, Areablock, and Block components would be hard to understand.
In the next sections, we will discover a list of the editables available. For each one, we will add a snippet of code to show you how to add it to your code and, for the more complex items, we also have some screenshots to explain the interaction.
The process for testing an editable in your live Pimcore environment is the following:
All these examples are contained in the editable.htm.twig template, used in The Editable Pages that you will find in the demo related to this chapter.
The following editable adds a checkbox that can be checked by the page editor:
{{ pimcore_checkbox('myCheckbox') }}
The value can be used to implement some rendering logic (that is, showing or not showing something basing on the myCheckbox value).
This snippet adds a DateTime picker to the web page:
{{ pimcore_date('myDate', {
'format': 'd.m.Y',
'outputFormat': '%d.%m.%Y'
})
}}
When editing the page, you will see an editable DateTime picker like the one shown here:
During the page rendering, when you are not in edit mode, the function outputs the value that the user selected in the editor. This value can be displayed or be used for implementing presentation logic.
This editable provides the opportunity to add a relation to an object. You can select which types of elements you want to add (for example, asset or object) and which subtypes. In the following example, we allowed only classes of type MyObject:
{{ pimcore_relation("myRelation",{
"types": ["asset","object"],
"subtypes": {
"asset": ["video", "image"],
"object": ["object"],
},
"classes": ["MyObject"]
}) }}
This component, once added to the page, is displayed as a simple edit box where you can drag the items that you want to show, as in the following screenshot:
Then the relation named myRelation will contain your selection value, and you can use it in the template for rendering data.
In the following example, we will add a Link button that navigates to the selected item:
<a href="{{ pimcore_relation("myRelation").getFullPath() }}">{{ "Go to" }}</a>
The previous snippet will display a link in the text.
This editable is like the many-to-one relation but allows you to select references to multiple other elements (such as documents, assets, and objects). The following snippet is the same as the previous use case, but because this editable has multiple references, we used a for loop to list all the selected options:
{% if editmode %}
{{ pimcore_relations("objectPaths") }}
{% else %}
<ul>
{% for element in pimcore_relations("objectPaths") %}
<li><a href="{{ element.getFullPath }}">{{ element.getTitle }}</a></li>
{% endfor %}
</ul>
{% endif %}
Now we can drag and drop multiple items:
The result of the selection is shown in the following table:
With the data in our sandbox, we will have something like this:
This editable gives you a widget where you can upload an image. If you drag a file from the asset folder and drop it on the component, the image will be displayed. The advantages compared with the Relation component are that the image is easier to use and integrated with the thumbnail system.
The next piece of code allows you to upload an image:
{{ pimcore_image("myImage", {
"title": "Drag your image here",
"thumbnail": "myThumbnails"
}) }}
You can upload images or drag and drop them to the widget as shown in the following screenshot:
Input allows the user to input a single line of text, and we can use the input for rendering HTML:
{{ pimcore_input("myHeadline") }}
We used it in Chapter 4, Creating Documents in Pimcore; there isn't anything more to add about this editable.
This is a component for rendering a link:
{{ pimcore_link('blogLink') }}
The following screenshot shows the link in edit mode:
The component comes out with two buttons: the folder that opens the selected link, and the edit button (pencil icon) that opens the settings form. The next screenshot shows the popup and the parameters available for a link:
Once completed, the result is an HTML link with the text and the destination provided.
This snippet adds a select component that lets the user choose between a set of items. The following snippet shows how it works:
{% if editmode %}
{{ pimcore_select("myItem", {
"store": [
["option1", "Option One"],
["option2", "Option 2"],
["option3", "Option 3"]
],
"defaultValue" : "option1"
}) }}
{% else %}
Your choice:{{ pimcore_select("myItem").getData() }}
{% endif %}
The store parameter contains a list of arrays. Each item is composed of an array of two elements: the first is the key, the second is the value. The defaultValue setting configures the default value for the selected list item.
This is what is shown in edit mode:
The Multiselect component is very similar to the Select one, but it allows multiple choices:
{{% pimcore_multiselect("categories", [
"width" => 200,
"height" => 100,
"store" => [
["cars", "Cars"],
["motorcycles", "Motorcycles"],
["accessories", "Accessories"]
]
]) %}}
This is what is shown in edit mode:
This editable is like the Input one, but specifically for numbers:
{{ pimcore_numeric('myNumber') }}
This is what is shown in edit mode:
And in edit mode, it returns the number chosen.
The Renderlet is a special container that is able to render an object. It uses a controller and an action to process the selected items, and the resulting HTML is provided to the user:
{{
pimcore_renderlet('myGallery', {
"controller" : "App\Controller\ CustomController::galleryAction",
"title" : "Drag an asset folder here to get a gallery",
"height" : 400
})
}}
In this case, we need some extra steps to test it. Follow the next steps:
{% if assets %}
{% for asset in assets %}
{% if asset is instanceof(
'\Pimcore\Model\Asset\Image') %}
<div style="border: 1px solid red;
width:200px; padding 10px;
margin:10px;">
{{ asset.getThumbnail(
'MiniIcons').getHTML()|raw}}
</div>
{% endif %}
{% endfor %}
{% endif %}
/**
* @Template()
*/
public function galleryAction(Request $request)
{
$result=array();
if ('asset' === $request->get('type')) {
$asset = Asset::getById($request->get('id'));
if ('folder' === $asset->getType()) {
$result["assets"] = $asset- >getChildren();
}
}
return $result;
}
{{
pimcore_renderlet('myGallery', {
"controller" : "App\Controller\ CustomController::galleryAction",
"title" : "Drag an asset folder here to get a gallery",
"height" : 400
})
}}
This configuration tells Pimcore to use the action galleryAction from the Default controller to render the data.
View the page as a normal user and see the result of your template:
This component is used to include a document or snippet (a special document type) inside the document; this is useful for reusing scripts or pieces of the website on your page:
{{ pimcore_snippet("mySnippet", {"width": 250, "height": 100}) }}
Once you have entered the code, this is how to add a snippet to the web page:
And this is what you will see as a website user:
In the previous screenshot, the text I'm the default template comes from the snippet.
This editable creates a table and lets the editor define the data inside of it. You can specify the default number of columns (cols) or rows (rows). You can also add a matrix of data (array of array) into the data parameter. The user will be able to add rows and columns by itself. Here is an example of the editable:
{{ pimcore_table("productProperties", {
"width": 700,
"height": 400,
"defaults": {
"cols": 3,
"rows": 3,
}
})
}}
This is the resulting editor interface:
And this is the default rendering result:
The previous figure doesn't look nice because the component outputs a simple HTML table without any styles. Of course, based on your CSS theme, you can apply all the styles you want to make it coherent for your website design.
This is very similar to the Input editable but uses a Textarea component instead:
{{ pimcore_textarea("myTextarea",
{"placeholder": "My Description" })
}}
This editable allows you to insert movie assets into your page content. It is very similar to the image component but works with video. You will need to add the following snippet to activate it:
{{ pimcore_video('campaignVideo', {
width: 700,
height: 400
}) }}
Then you will be able to define the source (local assets or an external link) and fine-tune some options:
On the web page, you will see it inside a video player as in the following screenshot:
The video editable is a very powerful solution for letting the user be autonomous in terms of uploading and managing videos without asking for help from a developer.
This WYSIWYG editor is used for entering HTML content. In the next piece of code, there is a sample of editable usage:
{{ pimcore_wysiwyg("specialContent") }}
We used it in Chapter 4, Creating documents in Pimcore; there isn't anything more to add.
In this section, we saw an overview of the most important editables. Using them as base components for the web pages that we create gives us absolute power to customize any aspect of our HTML. Moreover, with this large set of options, cases when you need something more than adding input for the user and then templating the data that you collect are very few. Generally speaking, you do not have to code to satisfy the customer's needs but just template the pages.
For more information about editables, you can consult the official documentation at the following URL: https://pimcore.com/docs/pimcore/current/Development_Documentation/Documents/Editables/.
In the next section, we will discover the usage of blocks, which are simple tools that help to create dynamic pages.
Pimcore blocks are a very clever system for iterating parts of a page. For example, if you have a standard page composed of horizontal bands or titled paragraphs, you can define a piece of HTML that explains how each item should look and let the user add as many items as they want. As another example, you can define a block composed of an H2 title and text, and this will create many titled paragraphs on your web page. Alternatively, you could use a block with an image and create a gallery iterating the blocks. Moreover, you can also manage the visibility of blocks by scheduling their publication.
pimcore_iterate_block can return the list of blocks. All the code that is wrapped inside the for loop can be replicated as many times as the user wants.
The difference between a block iteration and a regular for loop is that in the case of a block, the user defines how many items they want, and all the data input inside the editables is persisted.
The syntax for defining a block is very easy and we can see in the next snippet the usage of the previously mentioned functions:
{% for i in pimcore_iterate_block(pimcore_block('block name')) %}
My content
{% endfor %}
In the next example, we will see a block in action that iterates over a small template with a header and text that builds a web page with titled paragraphs:
{% for i in pimcore_iterate_block(pimcore_block('contentblock')) %}
<h2>{{ pimcore_input('title') }}</h2>
<p>{ pimcore_wysiwyg('content') }}</p>
{% endfor %}
The final result will look like this on the web page:
The scheduled block component is very similar to the block, but it has the option to define an expiration date for the contents. The syntax is the same, as you can see in the next snippet:
{% for i in pimcore_iterate_block(pimcore_scheduledblock('block')) %}
<h2>{{ pimcore_input('blockinput') }}</h2>
<p>{{ pimcore_image('myimage') }}</p>
{% endfor %}
The process for testing this snippet is exactly the same as the standard block that we saw in the previous section.
In the next screenshot, we will see the edit mode for the scheduled block, where we have a DateTime picker near the green + icon that allows the user to define when the content will expire:
The block engine is a very powerful system that allows a lot of opportunities for making dynamic pages easily editable by the user. We will appreciate this feature more in Chapter 10, Creating Pimcore Bricks, when we will use blocks in conjunction with bricks to create fully dynamic templates. Anyway, what we have learned so far is very interesting: we can give the user the opportunity to manage a repetitive template with a well-structured approach.
In this chapter, we learned all the information needed for creating a custom CMS page. We gained an understanding of the MVC model, and we had a deep dive into the templating engine of Pimcore, discovering all the helpers and how to create our own. We also learned how to write backend code in the controller and make it work by defining routing rules. We saw that there are many ways to manage URLs in Pimcore (Hardcoded, Static Route, and Redirect) and they cover all the use cases for a website. Then we had a complete overview of editables, which left us able to master any custom web page just by templating it. Finally, we discovered the block system that allows the iteration of pieces of a template by giving the user the opportunity to manage repetitive patterns in web pages.
This was an intense chapter that provided us with full knowledge of custom CMS pages. What we learned here will be very useful in the next chapter, Chapter 9, Configuring Entities and Rendering Data, where we will use it to render web pages for implementing a simple blog engine.
18.226.150.175