Welcome to Roadiz’s documentation!

Roadiz is a polymorphic CMS based on a node system that can handle many types of services. It is based on Symfony components, Doctrine ORM, Twig and Pimple for maximum performances and security.

Roadiz node system allows you to create your data schema and to organize your content as you want. We designed it to break technical constraints when you create tailor-made websites architectures and layouts.

Imagine you need to display your graphic design portfolio and… sell some t-shirts. With Roadiz you will be able to create your content forms from scratch and choose the right fields you need. Images and texts for your projects. Images, texts, prices and even geolocation for your products. That’s why it’s called polymorphic.

Philosophy

When discovering Roadiz back-office interface, you will notice that there aren’t any Rich text editor also called WYSIWYG editors. We chose to promote Markdown syntax in order to focus on content hierarchy and quality instead of content style. Our guideline is to preserve and respect the webdesigners’ and graphic designers’ work.

You’ll see that we built Roadiz as webdesigners and for webdesigners. It will allow you to create really quickly website prototypes using Twig templates. But as the same time you will be able to get the power of the Symfony and Doctrine core components to build complex applications.

We also decided to be really strict about Plugins and other addons modules. How many of you do not upgrade your Wordpress website because of plugin dependencies? We decided not to build Roadiz around a “Plugin” system but a Theme system, as every Roadiz extensions will have to serve a theme’s features. Themes will enable you to create awesome website layouts but also great back-office additions for your customers. You will be able to centralize all your custom code in one place, so you can use a versioning tool such as Git.

Roadiz theme system will allow you to daisy-chain themes and dispatch features on multiple code. As our CMS is built on Pimple dependency injection container, Roadiz can merge every available themes on the same website. For example, you will be able to create one portfolio theme using Node-system Urls and unlimited static themes which will use a static routing scheme, for a Forum or a Blog or even both! Theme system will even allow you to create additional Doctrine entities and extend our back-office. Yes, just sit on your theme code and you can extend Roadiz to create a manager for your Forum. Cherry on the cake, you can assign each theme to a specific domain name to create mobile or media specific layouts. Believe me, this cake is not a lie.

We want Roadiz to be a great tool for designers and developers to build strong web experiences together. But we thought of editors too! Roadiz back-office theme “Rozier” has been designed to offer every back-users a great writing and administrating experience.

User documentation

User documentation

Note

User documentation is on the way. We invite you to send us questions on our Gitter account or to leave some documentation suggestions on our Github repository.

First of all, you will need to connect to Roadiz’ backoffice in order to make changes to your website contents. To connect, you just have to write /rz-admin after your website domain name, in your browser address bar. Then you will be able to enter your username and password that you chose during Roadiz installation or that you received by email.

_images/login-page.jpg

Here you can choose to keep your connection active for a couple of days, if your browser accepts cookies.

If you forgot your credentials, the Forgot password? section will ask you an email to send you a password reset link.

Table of contents

Write in Markdown

Markdown is a lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. […] The key design goal is readability – that the language be readable as-is, without looking like it has been marked up with tags or formatting instructions, unlike text formatted with a markup language, such as Rich Text Format (RTF) or HTML, which have obvious tags and formatting instructions.

—Wikipedia article — https://en.wikipedia.org/wiki/Markdown

Titles

Add two hashtag # or more according to your title importance level. Backoffice shortcut buttons allow to directly insert your titles marks before your selected text. Make sure to leave a blank line before each new title you write.

## Architecture
### Modern architecture

Be careful not to use only one hashtag to create a first-level title as this is usually used for pages main title.

Alternate syntax

Main title and second level titles can be written using = and - as underline characters.

Architecture
============

Modern architecture
-------------------
Bold

Insert two stars * before and after your text to set in bold. Backoffice shortcut button allows to insert directly the 4 characters around your selected text.

This is a **bold text.** And a normal one.

Be careful not to leave whitespaces inside your stars group (in the same way you do with parenthesis) otherwise your text won’t be styled.

Italic

Insert one star * before and after your text to set in italic. Backoffice shortcut button allows to insert directly the 2 characters around your selected text.

This is an *italic text.* And a normal one.

Bold and italic marks can of course be combined using 3 stars before and after your selected text.

What if * character is already in use

Bold and italic markup can be performed using _ (underscore) character too if you actually need to write a star character in your text.

A __3* Bed & Breakfast__ has just opened its doors in middletown.
Strike-through

Insert two tildes ~ before and after your text to strike-through.

This is ~~striked-through text~~.
Ordered and unordered lists

Insert a star * or a dash - followed by a single whitespace for each of your list item. One item per line. Leave a blank line before and after your list. For ordered list, use a digit followed by a dot and a whitespace instead.

* A line
- An other line
* A unknown line

1. The first item
2. The second item
3. The third item

If you need to break an item into several lines, you’ll need to use the line-break markup.

Nested list

You can insert a second/third/… level for your list, just by leaving four spaces before your new list-item mark.

- A list item
    - A sub-item
    - A second sub-item
        1. An ordered sub-sub-item
        2. The second sub-sub-item
New paragraph and line-break

A simple line-break is always ignored by Markdown language because it makes a difference between a paragraph and a line-break. To simply create a line-break without creating a new paragraph, leave at least 3 spaces at the end of your text line then go to a new line.

Address:<space><space><space>
26 rue Burdeau<space><space><space>
69001 Lyon<space><space><space>
France

To create a new paragraph, always leave a blank line between your text blocks. Any additional blank line will be ignored.

Nullam quis risus eget urna mollis ornare vel eu leo.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.

Aenean eu leo quam. Pellentesque ornare sem lacinia
quam venenatis vestibulum.

According to your website design (CSS), new paragraphs may have no visual margins between them. Inserting more than one blank line won’t add any additional visual space as Markdown ignores it.

Block quotes

Insert a > sign before each new paragraph and a space to wrap your text in a quote block. You can then use all other Markdown symbols inside your quote.

> ### Donec ullamcorper nulla non metus auctor fringilla.
> Aenean lacinia **bibendum** nulla sed consectetur.
> Vestibulum id ligula porta felis euismod semper.
Images

Images use the link syntax with an exclamation mark prefix !. For external images do not forget to write full URL with protocol http:// or https://.

![A cat](/files/cat.jpg)

![A cat from an other website](https://www.example.com/images/cat.jpg)

Be careful, images will be displayed as is, unless your webdesigner planned to adapt image size coming from Markdown fields using CSS. As links, an external image may break if its owner deletes the original image. Make sure to host critical images directly on your website and to use relative URL.

Footnotes

Footnotes are not supported with basic Markdown syntax, but the Markdown Extra one. So before using them, make sure your webdesigner used the right Markdown parser in your theme.

Praesent commodo cursus magna[^note], Sed posuere consectetur est at
lobortis. Vel scelerisque nisl consectetur et[^othernote].

[^note]: This a footnote
[^othernote]: This a second footnote

Markdown will automatically generate anchor links between your footnote and its reference. It will automatically use numbers as footnote reference labels, so you don’t have to bother to write numbers yourself but easy-to-remember markers labels.

Managing nodes

Nodes are the most important part of Roadiz CMS. They are your content which can be shaped as you want according to your node-types definitions. A node can be a blog-post, a content page, a photo gallery, even a shop product. This is why we called it “Node”, it’s an abstract data container interconnected with other node in a tree.

Node-tree navigation

Each node has a place in your website, that’s why we chose to arrange your content in a tree-shaped way. It’s intuitive and it works the same as your computer files.

  • To edit a node’ content, simply click on it.
  • To move a node across your tree, drag & drop using its handle (round or rombus shape). You can drop a node after or before an other one. You can also drop inside just by moving your mouse a bit on the right, you should see the node shadow to shift right.
  • Other actions are available from each node’ contextual menu. Right click on the node or click on the arrow at the right when you pass your mouse over.
Contextual menu actions
  • Add child node: to create a content inside the current node.
  • Edit node: links to the current node “edit content” page.
  • Move to first position: to move a node at the first position inside its parent node.
  • Move to last position: basically the same for the last position.
  • Delete node: to move current node to the trashcan. A confirmation page will be prompt before really deleting a node.
  • Hide/Show: Change a node’ visibility. A hidden node won’t be displayed in Urls and your website, even if you are an administrator.
  • Publish/Unpublish: Change a node’ publication status. Unpublished nodes aren’t visible to anonymous visitors, but visible for back-office users using preview.php entry point.
  • Publish offspring: Publish a node and all its children nodes recursively.
  • Duplicate: Copy all current node’ content and relationships into a new node.
Creating a node

To add a blank node to your node-tree, you will need to choose your location. In Roadiz you can add a content at the root of your tree or choose a “parent-node”. In both cases you will be asked to choose a node-type and a node-name before creating your node.

  • Node name is the global identifier for your node. It must be unique and won’t change from one translation to an other. It can be changed later except if your developer locked it up. Node name is usually used to build your pages URL.
  • Node-type defines what fields will be available in your content. Choose well as you won’t be able to change it later, you ’ll have to delete and recreate an other node.
Edit an existing node

Node edition page is composed in several tabs.

  • Node content
  • Node parameters
  • Tags
  • SEO
  • Tree, if your node is set up as a container
Node content
_images/node-edit-page.png

Contents tab is basically the main part where you will edit your node specific data, using node-type fields such as text fields, or documents fields, etc.

This tab will display different content over translations.

Node parameters
_images/node-parameters-page.png

Parameters are global data such as your node name. They are used for managing your node visibility according to each user role and node back-office’ settings. This section should not be used so often as parameters will be set once by your developer.

This tab will display the same content over translations.

Tags
_images/node-tags-page.png

This tab will display the same content over translations.

SEO
_images/node-seo-page.png

This tab will display different content over translations.

Nodes publication system

During its lifecycle, every nodes can have a different publication status. When you create a new content, it will be automatically set as Draft by Roadiz so that you can edit it without bothering your visitors and sharing unfinished work.

Available statuses:
  • Draft: First status for new nodes
  • Pending validation: It’s a medium status for user that do not have permission to publish nodes
  • Published: That’s the most important status, it will set the green light to your visitor to view your content
  • Archived: When you don’t want to publish a node but you don’t want to delete it either
  • Deleted: It’s the last status for your nodes. Before emptying your node trashcan, every content will wait with this status.

To improve status visibility, draft and pending nodes have a rhombus shape and published nodes have a circle shape.

Preview unpublished nodes

As unpublished nodes are not viewable for anonymous visitors, Roadiz allows backend users to preview them using a dedicated entry point called preview.php, yes this is not very original. We decided to create a different entry point not to share the same URL with your public website as it could create confusing errors if your website is hosted behing a reverse proxy engine.

For example, if your my-news page is not published yet, connecting to http://mywebsite.com/my-news will lead to a 404 page for your anonymous visitors, as well as you too. If you want to preview it, you’ll have to connect to http://mywebsite.com/preview.php/my-news. This URL will only allow authentified backend users, other people will be blocked.

Managing node-types

This is a simple guide on how to create and manage nodes using Roadiz CLI, add and remove node fields, or even how to import nodes.

First and foremost, you need to create a new node-type before creating any kind of node.

If you want to know more about what a node-type is, please visit the other section of the developer documentation.

When working with Roadiz in the back-office, you can easily manage node-types via the Construction icon in the toolbar.

_images/manage_nodetype_toolbar.png
Add node-type

Once you have landed on the Node-Types page (https://mywebsite.com/rz-admin/node-types), you can create node-types by clicking on Add a node-type.

Note

You can export and import a node-type if you have a .json file. See Other action for more information.

_images/create_nodetype_button.png

Upon filling the two mandatory settings Name (that developpers will use) and Display Name (that back-office users will see), you are now ready to create your first node type.

Warning

Be careful when you name your node-type though, Name field can’t be changed once the node-type is created. See Delete node-type section to know how to delete a node-type.

_images/add_nodetype.png

Other options (Visible, Newsletter node-type, Node-type hides its nodes and customizable color) aren’t required and can always be altered later on.

You have now created your first node-type! It now appears on the node-type page along other node-types and you can now manage its fields.

_images/created_nodetype.png
Delete node-type

Made a typo when creating a node-type? No longer in need of a node-type ? You can delete it by simply clicking the trashcan icon on the Node Types page (https://mywebsite.com/rz-admin/node-types).

_images/delete_nodetype.png
Adding node-type field

To add fields to your newly-created node-type, click the Manage node-type fields icon.

_images/manage_nodetype_fields.png

Then click on ‘Add a node-type field’ button.

_images/add_nodetype_field_menu.png

Fill in the form to create a new field :

  • Name: what developers will use
  • Label: what back-office users will see
  • Type: single choice option that will define the content (basic text, markdown text, documents, email, number, single or multiple choice, children nodes etc.)
  • Description, Group name, Visible, Indexed, Default values, Min length, Max length (optional)

Note

Default values is an interesting field as you can specify what kind of node-types that can be linked to this node-type. You can also use it as a filter in the explorer, and only show those default values.

_images/add_nodetype_field.png
Other actions

From one website to another, you can export node-types as .rzt files.

_images/export_nodetype.png

An .rzt file should look like this when you open it in any editor (it is basically a .json file):

{
    "name": "Page",
    "displayName": "Page",
    "description": null,
    "visible": true,
    "newsletterType": false,
    "hidingNodes": true,
    "fields": [
        {
            "name": "excerpt",
            "label": "Excerpt",
            "description": null,
            "visible": true,
            "type": 4,
            "indexed": false,
            "virtual": false,
            "default_values": null,
            "group_name": null
        },
        {
            "name": "image",
            "label": "Image",
            "description": null,
            "visible": true,
            "type": 9,
            "indexed": false,
            "virtual": true,
            "default_values": null,
            "group_name": null
        },
        {
            "name": "children",
            "label": "Children",
            "description": null,
            "visible": true,
            "type": 16,
            "indexed": false,
            "virtual": true,
            "default_values": "",
            "group_name": null
        }
    ]
}

Notice the three fields that have been added to this Page node-type.

You can write an .rzt file yourself if you feel like it, but it is probably easier to simply export node-types from existing website, then import it on your new website. It can be easily done by clicking on Import node-type button, then selecting it via your explorer.

_images/import_nodetype.png

You are close to fully master Roadiz’ back-office powers ! Keep refering to this documention if you have any problem, and let us know if any information goes missing.

Managing documents

You can manage documents via the Document icon in the toolbar. You can either upload , embed, randomly downloaded documents, and even have a look at unused documents in your website.

_images/toolbar_document.png
Upload document

Uploading a document has never been this easy : just drag and drop the document in the designated area.

_images/upload_document.png
Embed document

Here is the trickiest part of the Documents section. You can have embedded documents from Youtube, SoundCloud, Vimeo and Dailymotion.

_images/embed_document.png

Warning

You may need to have a look at Youtube‘s and SoundCloud‘s documentation, as you need an access token to have embedded documents from both of them.

Random document

Random is a cool feature that allows to download random documents to your website from Splashbase.

_images/random_document.png
Unused document

Clicking Unused document allows you to gather every unused documents on your website, so you can clean your database and get rid of useless documents.

_images/unused_document.png

Managing users

This is a simple guide on how to create and manage users using Roadiz CLI.

There are two ways of adding users, via the back-office and in command-line, both will be displayed in each section.

When working with Roadiz in the back-office, you can easily manage users via the User system icon in the toolbar.

_images/add_user_toolbar.png
Add user

You can add users simply by clicking Add an user button.

_images/add_user_button.png

The command-line bin/roadiz users:create loginname starts a new interactive user creation session. You will create a new user with login and email, you can also choose if it’s a backend user and if it’s a superadmin.

Delete user

You can remove users by clicking the trashcan icon.

_images/remove_user.png

The command bin/roadiz users:delete loginname delete the user “loginname”.

Adding role

You can edit a users profile the same way you edit a node-type. You can add roles in the Roles tab.

_images/add_role_user.png

If you want to add ROLE_SUPERADMIN role to “test” user, it would look like this in command-line:

bin/roadiz users:roles --add loginname
# You will be prompted to choose the ROLE with auto-complete feature.
Other action

It is possible to enable or disable users with users:enable or users:disable command. If a user doesn’t remember his password, you can regenerate it with the users:password command. For more informations and more actions, we invite you to check available commands with:

bin/roadiz list users

Developer documentation

Developer documentation

Developer documentation deals with Roadiz’ inside and how to create your own themes. It requires a minimum of Twig templating knowledge and oriented-object PHP skills.

First steps

Requirements

Roadiz is a web application running with PHP. It requires an HTTP server for static assets and SSH access with out/ingoing allowed connections. Here is a short summary of mandatory elements before installing Roadiz:

  • Nginx or Apache, with a dedicated virtual host as described below.
  • PHP 5.4.3+ required, PHP 7+ recommended
  • php-gd extension
  • php-intl extension
  • php-xml extension
  • php-curl extension
  • JSON needs to be enabled
  • ctype needs to be enabled
  • Your php.ini needs to have the date.timezone setting
  • You need to have at least version 2.6.21 of libxml
  • PHP tokenizer needs to be enabled
  • mbstring functions need to be enabled
  • PHP OPcache + APCu (APC 3.0.17+ or another opcode cache needs to be installed)
  • php.ini recommended settings
    • short_open_tag = Off
    • magic_quotes_gpc = Off
    • register_globals = Off
    • session.auto_start = Off
  • MariaDB/MySQL/PostgreSQL or SQLite database (do not forget to install php-xxxsql extension according to your database driver flavor)
  • Zip/Unzip
  • cUrl
  • Composer
  • Git

Note

If you are using a shared hosting plan, make sure that your server’s SSH connection allows external connections. You can verify with a simple ping google.com. If you get request timeouts, your hosting provider might be blocking your SSH connectivity. You should consider using at least a VPS-like hosting. If you really need to setup Roadiz on a simple shared-hosting plan, we encourage you to install it on your own computer and send it with SFTP/FTP (it might take a long time) or rsync it.

For Nginx users

If you are using Nginx, you don’t have to enable any extensions. You only need to create your virtual host using our example file /samples/nginx.conf.

For Apache users

If you are using Apache do not forget to enable these mods:

  • mod_rewrite: enabling Roadiz front-controller system.
  • mod_expires: enabling http cache headers on static assets.

And do not use built-in mod_php, prefer PHP-FPM 😉!

Then use /samples/apache.conf template to create your virtual host configuration file. It shows how to set rewrite and secure private folders from being viewed from public visitors.

If you do not have access to your Apache virtual host configuration, you can use the built-in htaccess generator:

bin/roadiz generate:htaccess

This command will generate .htaccess files in each critical folder to enable PHP scripts or deny public access to forbidden folders.

Standard Edition

bin/roadiz generate:htaccess is not needed anymore with Roadiz Standard edition as you will configure your Apache/Nginx root to web/ folder only. No source or configuration files will be exposed anymore.

CMS Structure

Standard Edition

  • bin/: Contains the Roadiz CLI executable
  • app/: Contains every runtime resources from configuration to app cache and nodes-sources entities
    • cache/: Every cache file for Twig templates and Intervention Request images (this folder must be writable for PHP)
    • conf/: Your setup configuration file(s) (this folder must be writable for PHP)
    • gen-src/: Generated PHP code for Doctrine and your Node-types entities (this folder must be writable for PHP)
    • logs/: Monolog logs folder
    • files/: Private documents and font files root (this folder must be writable for PHP)
  • samples/: This folder contains useful configuration and example files for Apache or Nginx webservers
  • web/: Your website root, it contains your application entry-points and your public assets
    • files/: Public documents (this folder must be writable for PHP)
    • themes/: public assets mirror for each theme, this folder contains symlinks to your themes/YourTheme/static folder
  • themes/: Contains your themes and system themes such as Rozier and Install
  • vendor/: Dependencies folder managed by Composer

Source Edition

  • bin/: Contains the Roadiz CLI executable
  • cache/: Every cache file for Twig templates and Intervention Request images (this folder must be writable for PHP)
  • conf/: Your setup configuration file(s) (this folder must be writable for PHP)
  • files/: Documents and font files root (this folder must be writable for PHP)
  • gen-src/: Generated PHP code for Doctrine and your Node-types entities (this folder must be writable for PHP)
  • samples/: This folder contains useful configuration and example files for Apache or Nginx webservers
  • src/: Roadiz CMS logic and core source code
  • tests/: PHP Unit tests root
  • themes/: Contains your themes and system themes such as Rozier and Install
  • vendor/: Dependencies folder managed by Composer
  • logs/: Monolog logs folder
Install Roadiz Standard Edition

For new projects Roadiz can be easily setup using create-project command.

# Create a new Roadiz project
composer create-project roadiz/standard-edition
# Create a new theme for your project
cd standard-edition
bin/roadiz themes:generate FooBar

Composer will prompt you if you want to can versioning history. Choose the default answer no as we definitely want to replace standard-edition Git with our own versioning. Then you will be able to customize every files in your projects and save them using Git, not only your theme. Of course we added a default .gitignore file to prevent your configuration setting and entry points to be commited in your Git history. That way you can have different configuration on development and on your production server without bothering about merge conflicts.

Dealing with Roadiz environments
Installation environment

Once you’ve succedded to download Roadiz and its dependencies. You’ll have to setup its database and every informations needed to begin your website.

As every Symfony applications do, Roadiz works using environments. By default, there is a production environment which is handled by index.php entry point. At this moment, if you try to connect to your fresh new Roadiz website, you will get an error as we did not install its database and its essential data.

To be able to use Roadiz install interface, you’ll need to call the install entry point. An install.php file has been generated when you executed composer install command. This environment will be reachable at the Url http://mywebsite.com/install.php.

For security reasons, we added an IP filtering in this entry point, you can add your own IP address in the following array: array('127.0.0.1', 'fe80::1', '::1'). This IP filtering is very important if you are working on a public server, no one except you should be able to access install entry point.

At the end of the install process, you will be invited to remove the install.php file and to connect to your website final URL.

Development environment

Roadiz production environment is not made for developing your own themes and extending back-office features. As the same way as install environment, we prepared a dev environment to disable resources caching and enable debug features. You’ll find a dev.php file at your website root which was generated at composer install command. As well as install.php entry point, you’ll need to add your own IP address to filter who can access to your dev environment.

Preview environment

The preview environment is not a real one as it only adds a flag to Roadiz’ Kernel to enable back-office users to see unpublished nodes. By default, it is available using preview.php entry point, unless you decide to remove it.

Production environment

This is the default index.php entry point which will be called by all your visitors. There is no restriction on it and it will wake up Roadiz application using the strongest caching policies. So it’s not recommended for development usage (you would have to flush caches each time your change something in the code).

Clear cache environment

The clear_cache environment is only meant to empty Roadiz cache without waking up the whole application. It can be useful if you are using a op-code cache like APC or native PHP OPcache. These special caches can’t be purged from command line utilities, so you need to call a PHP script from your browser or via curl to empty them. Like install and dev environment, clear_cache.php is IP-restricted not to allow everyone to flush your dear caches. You’ll need to add your own IP address to filter who can access it.

Install Roadiz Source Edition (deprecated)

Warning

This part is applicable for projects created prior v0.17. New projects should be created with Roadiz Standard Edition.

Roadiz Source edition can be downloaded in two different ways:

  • The Good one — using Git and Composer (needs an SSH connection to your server and Git)
  • The Easy one — using a bundled Zip archive with composer dependencies.
Using Git

First you will have to setup properly your server virtual host. You can either use Apache or Nginx with Roadiz. An example virtual host is provided in source-code for each server:

  • samples/apache.conf
  • samples/nginx.conf

You just have to customize your root path and server name. Nginx has built-in support for php-fpm whereas Apache must be configured with fastcgi to do the same.

These example files will provide basic security configuration for private access folders: such as conf or files/fonts folders. They will also configure your server to redirect all non static requests to Roadiz front-controller.

Note

For shared hosting plan owners, if you can’t modify your virtual host definition, don’t panic, Roadiz has a built-in CLI command to generate .htaccess files for you. Just execute bin/roadiz generate:htaccess after cloning Roadiz sources and running Composer. In the other hand, if you are using Apache and have access to your virtual host, we strongly recommend you to use our sample configuration and disable .htaccess files: performances are at their best without them.

When your HTTP server is ready to go, download Roadiz latest version using Git:

cd your/webroot/folder;
git clone git@github.com:roadiz/roadiz.git ./;

Use Composer to download Roadiz dependencies and to build PHP class autolader. We even set up some post-scripts which will copy a new config.yml, dev.php, clear_cache.php and install.php files for you.

# Install Roadiz dependencies, prepare a fresh config file and your
# own dev and install entry points.
composer install --no-dev -o;

When your virtual host is ready and every files have been downloaded you can go to the next part to access the `install environment`_.

The quick and dirty way: using a Zip archive

This method must be used if you have to work on your own computer with softwares like MAMP, WAMP or if you need to setup your website on a shared hosting plan without any SSH or Git.

If you downloaded Roadiz on the Github release page or directly from our website, you should get a bundled Zip containing every Roadiz files and Composer dependencies. We even generated .htaccess files and a conf/config.yml file for you.

If you can unzip directly on your server, that is cool. It will save you time, if not, just unzip it on your desktop and upload files to your server via FTP.

Warning

When you transfer your Roadiz site via FTP make sure .htaccess files are copied into each important folders (./, ./conf, ./src, ./files/fonts, etc). If you are using an Apache setup, this will prevent unwanted access to important files.

Once you unzipped and moved your Roadiz files into your webserver folder, just launch the Install tool with your Internet browser by typing your new website address. If you are working on your own computer with MAMP, WAMP or other easy-server tool, just type http://localhost:8888/roadiz-folder in your browser (the port may change according to your server settings).

You have to understand that using Zip archive way with FTP transfers will make updating Roadiz harder. If you have a dedicated server or a VPS, we highly recommend you to use Git and Composer to install Roadiz. That way, you will be able to upgrade Roadiz just by typing git pull origin master.

Using Vagrant for development

Roadiz comes with a dedicated Vagrantfile which is configured to run the official roadiz/standard-edition box with a LEMP stack (Nginx + PHP7.0-FPM + MariaDB), a phpMyAdmin, a Mailcatcher and an Apache Solr server. This will be useful to develop your website on your local computer.

Once you’ve created your Roadiz project, Composer should has copied samples/Vagrantfile.sample file as Vagrantfile at your project root. Then do a vagrant up in Roadiz’ folder. Then Vagrant will run your code in /var/www and you will be able to completely use bin/roadiz commands without bloating your computer with lots of binaries.

Once vagrant VM has provisioned you will be able to use:

  • http://192.168.33.10/install.php to proceed to install.
  • http://192.168.33.10:8983/solr to use Apache Solr admin.
  • http://192.168.33.10/phpmyadmin for your MySQL db admin.
  • http://192.168.33.10:1080 for your Mailcatcher tool.

Do not hesitate to add an entry in your /etc/hosts file to use a local domain name instead of using the private IP address (eg. http://site1.dev). And for each Vagrant website, do not forget to increment your private IP.

# /etc/hosts
# Vagrant hosts
192.168.33.10    site1.dev
192.168.33.11    site2.dev
# …

Note

Be careful, Windows users, this Vagrantfile is configured to use a NFS fileshare. Disable it if you did not setup a NFS emulator. For OS X and Linux user this is built-in your system, so have fun!

Access entry-points

web/install.php, web/clear_cache.php and web/dev.php entry points are IP restricted to localhost. To be able to use them with a Vagrant setup, you’ll need to add your host machine IP to the $allowedIp array. We already set two IP for you that should work for forwarded and private requests. Just uncomment the following lines in these files and edit them if necessary.

$allowedIp = [
    '10.0.2.2',     // vagrant host (forwarded)
    '192.168.33.1', // vagrant host (private)
    '127.0.0.1', 'fe80::1', '::1' // localhost
];
Manual configuration

This section explains how main configuration file app/conf/config.yml works. It is way more more convenient than launching Install theme for each configuration update.

Your app/conf/config.yml file is built using YAML syntax. Each part matches a Roadiz service configuration.

Note

By default, every Roadiz environment read app/conf/config.yml configuration file. But you can specify different files for dev and test environments. Just create a app/conf/config_dev.yml or app/conf/config_test.yml file to override default parameters. You will be able to use a different database, mailer or Solr instance not to pollute your production environment.

Source Edition

Roadiz Source edition stores configuration files in conf/ folder.

Doctrine

The most important configuration section deals with database connection which is handled by Doctrine:

doctrine:
    driver: "pdo_mysql"
    host: "localhost"
    user: ""
    password: ""
    dbname: ""

Roadiz uses Doctrine ORM to store your data. It will directly pass this YAML configuration to Doctrine so you can use every available drivers and options from its documentation at http://doctrine-dbal.readthedocs.org/en/latest/reference/configuration.html

Cache drivers

When set as null, cache drivers will be automatically chosen by Roadiz according to your PHP setup and available extensions.

Sometimes, if a cache extension is available but you don’t want to use it, you’ll have to specify a cache driver type (use array to disable caches). This is a known case when using OVH shared hosting plans which provide memcached PHP extension but does not let you log in.

cacheDriver:
    type: null
    host: null
    port: null

Available cache types are:

  • apc
  • xcache
  • memcache (requires host and port configuration)
  • memcached (requires host and port configuration)
  • redis (requires host and port configuration)
  • array
Monolog handlers

By default, Roadiz writes its logs to app/logs/ folder in a file named after your running environment (eg. roadiz_prod.log). But you can also customize Monolog to use three different handlers. Pay attention that using custom log handlers will disable default Roadiz logging (except for Doctrine one) so it could be better to always use default handler along a custom one.

Available handler types:

  • default: Reproduce the Roadiz default handler which writes to app/logs/ folder in a file named after your running environment
  • stream: Defines a log file stream on your local system. Your path must be writable!
  • syslog: Writes to system syslog.
  • gelf: Send GELF formatted messages to an external entry point defined by url value. Roadiz uses a fault tolerant handler which won’t trigger any error if your path is not reachable, so make sure it’s correct. It’s a good idea to combine a gelf handler with a local logging system if your external entry point is down.

type and level values are mandatory for each handlers.

Here is an example configuration:

monolog:
    handlers:
        default:
            type: default
            level: INFO
        file:
            type: stream
            # Be careful path must be writable by PHP
            path: /var/log/roadiz.log
            level: INFO
        syslog:
            type: syslog
            # Use a custom identifier
            ident: my_roadiz
            level: WARNING
        graylog:
            type: gelf
            # Gelf HTTP entry point url (with optional user:passwd authentification)
            url: http://graylog.local:12202/gelf
            level: WARNING
Solr endpoint

Roadiz can use an Apache Solr search-engine to index nodes-sources. Add this to your config.yml to link your CMS to your Solr server:

solr:
    endpoint:
        localhost:
            host: "localhost"
            port: "8983"
            path: "/solr"
            core: "mycore"
            timeout: 3
            username: ""
            password: ""

Roadiz CLI command can easily handle Solr index. Just type ./bin/roadiz solr:check to get more informations.

Entities paths

Roadiz uses Doctrine to map object entities to database tables. In order to make Roadiz more extensible, you can add your own paths to the entities part.

entities:
    - "../vendor/roadiz/roadiz/src/Roadiz/Core/Entities"
    - "../vendor/roadiz/roadiz/src/Roadiz/Core/AbstractEntities"
    - "gen-src/GeneratedNodeSources"
Configure mailer

Roadiz uses Swift Mailer to send emails. This awesome library is built to enable different kinds of mail transports and protocols. By default, Roadiz uses your PHP sendmail configuration but you can tell it to use another transport (such as an external SMTP server) in your app/conf/config.yml file.

You can use SSL, TLS or no encryption at all.

mailer:
    type: "smtp"
    host: "localhost"
    port: 25
    encryption: false
    username: ""
    password: ""

Note

Pay attention that many external SMTP services (Mandrill, Mailjet…) only accept email from validated domains. So make sure that your application uses a known From: email sender not to be blacklisted or blocked by these services. If you need your emails to be replied to an anonymous address, use ReplyTo: header instead.

Images processing

Roadiz use Image Intervention library to automatically create a lower quality version of your image if they are too big. You can define this threshold value in the assetsProcessing section. driver and defaultQuality will be also use for the on-the-fly image processing with Intervention Request library.

assetsProcessing:
    # gd or imagick (gd does not support TIFF and PSD formats)
    driver: gd
    defaultQuality: 90
    # pixel size limit () after roadiz
    # should create a smaller copy.
    maxPixelSize: 1280
    # Path to jpegoptim binary to enable jpeg optimization
    jpegoptimPath: ~
    # Path to pngquant binary to enable png optimization (3x less space)
    pngquantPath: ~
Console command

Roadiz can be executed as a simple CLI tool using your SSH connection. This is useful to handle basic administration tasks with no need of backoffice administration.

./bin/roadiz

If your system is not configured to have php located in /usr/bin/php use it this way:

php ./bin/roadiz

Default command with no arguments will show you the available commands list. Each command has its own parameters. You can use the argument --help to get more informations about each tool:

./bin/roadiz install --help

We even made Doctrine CLI tools directly available from Roadiz Console. Be careful, these are powerful commands which can alter your database and make you lose precious data. Especially when you will need to update your database schema after a Theme or a Core update. Always make a database back-up before any Doctrine operation.

Additional commands

If you are developing your own theme, you might need to create some custom CLI commands. Roadiz can handle additional commands if you add them in your app/conf/config.yml as you would do for any additional entities. Make sure that every additional commands extend Symfony\Component\Console\Command\Command class.

additionalCommands:
    - \Themes\DefaultTheme\Commands\DefaultThemeCommand
Upgrading

Note

Always do a database backup before upgrading. You can use the mysqldump or pg_dump tools to quickly export your database as a file.

  • With Roadiz command (MySQL only): bin/roadiz database:dump -c will generate a SQL file in app/ folder
  • With a MySQL server: mysqldump -u[user] -p[user_password] [database_name] > dumpfilename.sql
  • With a PostgreSQL server: pg_dump -U [user] [database_name] -f dumpfilename.sql

Standard Edition

Use Composer to update dependencies or Roadiz itself with Standard edition

composer update -n --no-dev;

Source Edition

If you are using Roadiz Source edition: download latest version using Git

cd your/webroot/folder;
git pull origin master;

In order to avoid losing sensible node-sources data. You should regenerate your node-source entities classes files:

bin/roadiz generate:nsentities;

Then run database schema update, first review migration details to see if no data will be removed:

bin/roadiz orm:schema-tool:update --dump-sql;

Then, if migration summary is OK (no data loss), perform the following changes:

bin/roadiz orm:schema-tool:update --force;
# Clear cache for each environment
bin/roadiz cache:clear -e dev
bin/roadiz cache:clear -e prod
bin/roadiz cache:clear -e prod --preview

Note

If you are using an OPcode cache like XCache or APC, you’ll need to purge cache manually because it can’t be done from a CLI interface as they are shared cache engines. As a last chance try, you can restart your php5-fpm service.

Moving a website to another server

Before moving your website, make sure you have backed up your data:

  • Dump your database, using classic mysqldump or pg_dump tools. If you’re using MySQL bin/roadiz database:dump -c command can speed-up the process by naming automatically your file against your app-namespace.
  • Archive your files using bin/roadiz files:export, Roadiz will create a ZIP file with your public/private documents and fonts.
Moving to a SSH+Git hosting plan or an other development machine

From this point you can install your new webserver, as described in Install section. Pay attention that if your theme needs some additionnal composer dependencies you should clone/copy it into your themes/ folder before running composer install --no-dev. That way composer will download theme libraries at the same time as Roadiz’ ones (See how to use Composer in your themes).

Then import your dump and files into your new server.

Once you’ve imported your database, you must edit manually your conf/config.yml, you can reuse the former server’s one and adapt its database credentials.

Warning

Do not perform any schema update if no gen-src\GeneratedNodeSources classes is available, it will erase your NodesSources data as their entities files haven’t been generated yet.

When you have edited your app/conf/config.yml file, regenerate your Doctrine entities class files:

bin/roadiz generate:nsentities;

Now you can perform a schema update without losing your nodes data:

bin/roadiz orm:schema-tool:update --dump-sql;
bin/roadiz orm:schema-tool:update --force;
bin/roadiz cache:clear -e prod
bin/roadiz cache:clear -e prod --preview

Note

If you are using an OPcode cache like XCache or APC, you’ll need to purge cache manually because it can’t be done from a CLI interface as they are shared cache engines. The most effective way is to restart your PHP-FPM service or Apache if your are using mod_php.

Synchronize documents and fonts

You can move your files/ folder using SFTP but the best way is to use rsync command as it will upload only newer files and it is much faster.

# This will synchronize files on your production server from your local Roadiz setup.
# Do not forget ending slash after each path!
rsync -avcz -e "ssh -p 22" /path/to/roadiz/files/ user@my-prod-server.com:/path/to/roadiz/files/
rsync -avcz -e "ssh -p 22" /path/to/roadiz/web/files/ user@my-prod-server.com:/path/to/roadiz/web/files/

It works in the other way too. If you want to work on your local copy with up to date files and fonts, you can download actual files from the production website:

# This will synchronize files on your local development server from your production server.
# Do not forget ending slash after each path!
rsync -avcz -e "ssh -p 22" user@my-prod-server.com:/path/to/roadiz/files/ /path/to/roadiz/files/
rsync -avcz -e "ssh -p 22" user@my-prod-server.com:/path/to/roadiz/web/files/ /path/to/roadiz/web/files/
Moving to a non-SSH hosting plan

You have nearly finished your brand new website using Roadiz. You have been working on your own server using Git and Composer, up to this point everthing went well.

Now you have to push to production, but your prod-server has no SSH connection. You are stuck with an SFTP connection or worst, an old FTP one. Don’t panic, it will take a little more time but it is still possible.

Warning

Many shared-plan hosters offer you only one or two databases. When moving a Roadiz website, make sure that your database is empty and do not contain orphan tables, you must respect the rule “One app = One database”.

Note

If you can ZIP on your production server or if you are going to push your files via FTP, do not forget to exclude .git and node_modules folders! These folders have lots of useless files for a production SSH-less environnement. Here is a sample ZIP command to exclude them: zip -r mywebsite.zip mywebsite/ -x "mywebsite/.git/*" "mywebsite/themes/**/static/node_modules/*".

  • Before transfering your website, make sure you have .htaccess file in every sensitive folders. You can use the bin/roadiz generate:htaccess on your computer.
  • If you have at least SFTP, you should have to rights to zip/unzip on your distant server. So zip the whole Roadiz folder.
  • If you only have FTP, you must be prepared to transfer your Roadiz folder, file-by-file. Just get yourself a nice cup of coffee.
  • Once everything is copied on your production server, verify than you have the same files as on your dev-server.
  • Import your database dump with phpmyadmin or pgmyadmin.
  • Edit your conf/config.yml to match your new database credentials.
  • Verify that root .htaccess file contains every informations to enable Apache url-rewriting.
  • Try to connect to your website
  • If it doesn’t work or display anything, read your PHP log file to understand where the problem comes from. It might be your database credentials or an oudated PHP version. Check that your hoster has installed every needed PHP extensions, see requirements.
Using Docker for production

Once you’ve developed your Roadiz website with your dedicated theme with Vagrant you’ll need to push your website to production. We’ve built a Docker image to ease up and speed up deployment.

Note

Docker deployment requires knowledge with Docker and some sys-admin skills. We invite you to familiarize with this technology, there is a plenty of documentation on the subject.

As a Roadiz website requires a database server and some SSH protocol to transfer your local data, it will be more convenient to deploy using Docker Compose. It will orchestrate each container with their volumes and link them together.

A simple docker-compose example

In a blank folder named site/ create a docker-compose.yml file with the following content:

version: '2'
services:
  MAIN:
    hostname: site
    image: roadiz/standard-edition
    # Only if you are using a proxy or/and backup container
    #network_mode: "bridge"
    ports:
      # For production only without a proxy
      - "80:80"
    volumes:
      - DATA:/data
    links:
      - DB:mariadb
    depends_on:
      - DB
    # For production only
    restart: always
  DB:
    image: ambroisemaupate/mariadb
    environment:
      MARIADB_USER: "username"
      MARIADB_PASS: "password"
    # Only if you are using a proxy or/and backup container
    #network_mode: "bridge"
    volumes:
      - DBDATA:/data
  # SSH container linked to db to
  # export or import mysqldumps and
  # sync your files from/to your local server
  SSH:
    image: ambroisemaupate/light-ssh
    # Only if you are using a proxy or/and backup container
    #network_mode: "bridge"
    environment:
      PASS: "password"
    volumes:
      - DATA:/data
    links:
      - DB:mariadb
    depends_on:
      - DB
    ports:
      - "22/tcp"
volumes:
  DATA:
  DBDATA:

Then launch your container network with docker-compose up -d. This will create:

  • site_MAIN_1 container
  • site_DB_1 container
  • site_SSH_1 container
  • site_DATA volume
  • site_DBDATA volume

After your container launch, you’ll find a blank /data/http folder in which you’ll have to clone your Roadiz application. Then you’ll be able to import your database and your files (bin/roadiz files:import yourZipFile.zip).

Using a deploy/access key for Github/Gitlab

Roadiz docker image is configured to look for your SSH public key in /data/secure/ssh. Pay attention to generate you ssh-key as core user: su -s /bin/bash core before doing anything in your /data folder.

# On your Docker host, access to your main container
# You must impersonate core user
docker exec -ti --user=core site_MAIN_1 bash

# On your docker container…
# Generate public/private keys
ssh-keygen -t rsa -b 2048 -N '' -f /data/secure/ssh/id_rsa \
           -C "Deploy key ($HOSTNAME) for private repository"
# Add the generated /data/secure/ssh/id_rsa.pub key to your Github/Gitlab account

# Clone your Roadiz standard edition application
cd /data/http/
git clone git@github.com:private-account/my-roadiz-app.git ./
# Install composer dependencies
composer install --no-dev
composer dump-autoload --no-dev -o -a
Configure Roadiz

To configure your Roadiz website, edit your /data/http/app/conf/config.yml with nano editor. If you get some “Unknown terminal error”, you have to edit your TERM environment variable: export TERM=xterm.

Database
doctrine:
    driver: pdo_mysql
    # Pay attention that DB host is not localhost but
    # mariadb as we defined an alias in our
    # docker-compose.yml file.
    host: mariadb
    user: username
    password: password
    # DB name will automatically be named after username
    dbname: username
    port: null
    unix_socket: null
    path: null
Mailer

Roadiz docker image does not provide any mail transport agent. You’ll need to subscribe to an external SMTP service if your website needs to send emails. You can also link your Roadiz container with a dockerized Postfix service. In every cases you’ll have to fill in mailer details in configuration.

mailer:
    type: smtp
    host: smtp-provider.com
    port: 25
    encryption: false
    username: ''
    password: ''
Logs

See manual configuration documentation section about Monolog handlers.

Copy data from your local environment with the SSH container

Note

We assume that you won’t do a fresh install of your website with Docker. So you won’t need to access to the install.php entry point.

To copy your data from your local environment you will use your SSH container to perform some scp and rsync commands between your computer and your Docker container. Using a SSH container has the great advantage to start and stop the server whenever you need it and to completely secure your data from outside. Obviously, your Docker host SSH account must be securized too (public key only connection for root or sudo only connections).

Pushing database
  1. Export a MySQL dump from your Vagrant or other local development: mysqldump -ulocaluser -p localdb > local/path/site_2016_10_07.sql.
  2. Make sure your SSH container is started and find its public port: docker start site_SSH_1.
  3. Copy from your computer to your Docker container: scp -P XXXXX local/path/site_2016_10_07.sql core@site.com:/data/secure/.
  4. Connect to your Docker container: ssh -p XXXXX core@site.com.
  5. Import your dump: cd /data/secure; mysql -hmariadb -uusername -p username < site_2016_10_07.sql;.
  6. Regenerate your entities: cd /data/http; bin/roadiz generate:entities;.
Pushing documents and fonts
  1. Make sure your SSH container is started and find its public port: docker start site_SSH_1.
  2. Send your .zip archive generated with bin/roadiz files:export command to your Docker container.
  3. Execute bin/roadiz files:import yourZipArchive.zip command to store files in Roadiz folders.
Clear cache
  1. Connect to your real Docker Roadiz container. Not the SSH one: docker exec -ti --user=core site bash.
  2. Call the clear_cache.php entry point with curl command: curl http://localhost/clear_cache.php.
Use a proxy to secure your containers

For better security and SSL support with awesome and free Let’s Encrypt certificates, you can use jwilder/nginx-proxy and JrCs/docker-letsencrypt-nginx-proxy-companion Docker images. Then you won’t need to publish your Roadiz ports anymore but to declare environment variables called VIRTUAL_HOST, LETSENCRYPT_HOST and LETSENCRYPT_EMAIL to bind nginx front proxy to your container.

Note

As Docker Compose encapsulates every composed services in their own network, you have to explicitely set network_mode: "bridge" mode. Without this setting, your front proxy container won’t be able to reach your Roadiz container. This network mode is also required if you need to run temporary containers linked to your database, for example a backup service. If you are using Docker compose also for your Nging proxy setup, do not forget to add it in its docker-compose.yml too.

version: '2'
services:
  MAIN:
    hostname: site
    image: roadiz/standard-edition
    network_mode: "bridge"
    environment:
      # Bind nginx proxy to listen these domains
      VIRTUAL_HOST: site.com,www.site.com
      # Create and renew SSL cert for these domains
      LETSENCRYPT_HOST: site.com,www.site.com
      # Mandatory administration email for renewal notifications
      LETSENCRYPT_EMAIL: admin@site.com
      # …

You have to understand that using a front-proxy will obfuscate your visitors IP inside your Roadiz container. You’ll have to trust the proxy request to get real remote IP and protocol. (See Running Roadiz behind a reverse proxy)

Use Solr

See Solr docker image documentation.

version: '2'
services:
  MAIN:
    hostname: site
    image: roadiz/standard-edition
    environment:
    # Only if you are using a proxy or/and backup container
    #network_mode: "bridge"
    ports:
      # For production only without a proxy
      - "80:80"
    volumes:
      - DATA:/data
    links:
      - DB:mariadb
      - SOLR:solr
    depends_on:
      - DB
      - SOLR
    # For production only
    restart: always
  SOLR:
    image: solr
    # Only if you are using a proxy or/and backup container
    #network_mode: "bridge"
    entrypoint:
      - docker-entrypoint.sh
      - solr-precreate
      - site
    volumes:
      - SOLRDATA:/opt/solr/server/solr/mycores
#
# …
#
volumes:
  DATA:
  DBDATA:
  SOLRDATA:

Then configure you Roadiz website to connect it to your Solr server (see Solr endpoint). Do not forget to use solr hostname and site core name.

Node system

Node-types, nodes-sources and nodes

This part is the most important part of Roadiz. Quite everything in your website will be a node.

Let’s check this simple node schema before explain it.

_images/node-struct.svg

Now, it’s time to explain how it’s working!

What is a Node-type

A node-type is the blueprint for your node-source. It will contain all fields that Roadiz will use to generate an extended node-source class.

_images/NSPage-php.svg

For example, a node-type “Page” will contain “content” and “header image” fields. The “title” field is always available as it is hard-coded in NodesSources class. After saving your node-type, Roadiz generates a NSPage class which extends the NodesSources class. You will find it in the gen-src/GeneratedNodeSources (or app/gen-src/GeneratedNodeSources with Roadiz Standard edition). Then Roadiz calls Doctrine update tool to migrate your database schema. Do not modify the generated class. You’ll have to update it by the backend interface.

Here is a schema to understand how node-types can define custom fields into node-sources:

_images/NSPage-schema.svg

Most of node-types management will be done in your backoffice interface. You will be able to create, update node-types objects and each of their node-type fields independently. But if you prefer, you can use CLI commands to create types and fields. With Roadiz CLI commands you get several tools to manage node-types. We really encourage you to check the commands with --help argument, as following:

bin/roadiz nodetypes:add-fields
bin/roadiz nodetypes:create
bin/roadiz nodetypes:delete
bin/roadiz nodetypes:list

Keep in mind that each node-type or node-type fields operation require a database update as Doctrine have to create a specific table per node-type. Do not forget to execute bin/roadiz orm:schema-tool:update tools to perform updates. It’s very important to understand that Doctrine needs to see your node-types generated classes before upgrading database schema. If they don’t exist, it won’t able to create your custom types tables, or worst, it could delete existing data since Doctrine won’t recognize specific tables.

Now let’s have a look on node-sources.

Node-sources and translations

Once your node-type created, its definition is stored in database in node_types and node_type_fields tables. This informations will be only used to build your node-sources edition forms in backoffice and to build a custom database table.

Inheritance mapping

With Roadiz, each node-types data (called node-sources) is stored in a different table prefixed with ns_. When you create a Page node-type with 2 fields (content and excerpt), Roadiz tells Doctrine to build a ns_page table with 2 columns and one primary key column inherited from nodes_sources table. It’s called inheritance mapping: your ns_page table extends nodes_sources table and when you are querying a Page from database, Doctrine mix the data coming from these 2 tables to create a complete node-source.

At the end your node-source Page won’t contain only 2 fields but many more as NodesSources entity offers title, metaTitle, metaDescription, metaKeywords and others useful data-fields which can be used among all node-types.

Translations

Node-sources inheritance mapping is not only used to customize data but to make data translations available. As you saw in the first picture, each nodes can handle many node-sources, one per translation.

Node-type fields

Roadiz can handle many types of node-type fields. Here is a complete list:

Note

Title, meta-title, meta-description and keywords are always available since they are stored directly inside NodesSources entity. Then you will be sure to always have a title no matter the node-type you are using.

Simple data

This following fields stores simple data in your custom node-source database table.

  • Single-line text
  • Date
  • Date and time
  • Basic text
  • Markdown text
  • Boolean
  • Integer number
  • Decimal number
  • Email
  • Color
  • Single geographic coordinates
  • JSON code
  • CSS code
  • Country code (ISO 3166-1 alpha-2)
  • YAML code

Note

Single geographic coordinates field stores its data in JSON format. Make sure you don’t have manually writen data in its input field.

Warning

To use Single geographic coordinates you must create a Google API Console account with Maps API v3 activated. Then, create a Browser key and paste it in “Google Client ID” parameter in Roadiz settings to enable geographic node-type fields. If you didn’t do it, a simple text input will be display instead of Roadiz Map Widget.

_images/field-types.png
Virtual data

Virtual types do not really store data in node-source table. They display custom widgets in your editing page to link documents, nodes or custom-forms with your node-source.

  • Documents
  • Nodes references
  • Custom form
Complex data

These fields types must be created with default values (comma separated) in order to display available default choices for “select-box” types:

  • Single choice
  • Multiple choices
  • Children nodes

Children node field type is a special virtual field that will display a custom node-tree inside your editing page. You can add quick-create buttons by listing your node-types names in default values input, comma separated.

Universal fields

If you need a field to hold exactly the same data for all translations, you can set it as universal. For example for documents, numeric and boolean data that do not change from one language to another.

It will duplicate data at each save time from default translation to others. It will also hide the edit field from non-default translation to avoid confusion.

YAML field

When you use YAML field type, you get an additional method to return your code already parsed. If your field is named data, your methods will be generated in your NSEntity as getData() and getDataAsObject().

  • getData() method will return your YAML code as string.
  • getDataAsObject() will return a mixed data,array or stdObject according to your code formatting. This method will throw a \Symfony\Component\Yaml\Exception\ParseException if your YAML code is not valid.
Handling nodes and their hierarchy

By default, if you use Entities API methods or trasversing Twig filters, Roadiz will automatically handle security parameters such as node.status and preview mode.

// Secure method to get node-sources
// Implicitly check node.status
$this->get('nodeSourceApi')->getBy([
    'node.nodeType' => $blogPostType,
    'translation' => $translation,
], [
    'publishedAt' => 'DESC'
]);

This first code snippet is using Node-source API. This will automatically check if current user is logged-in and if preview mode is ON to display or not unpublished nodes.

// Insecure method to get node-sources
// Doctrine raw method will get all node-sources
$this->get('em')->getRepository('GeneratedNodeSources\NSBlogPost')->findBy([], [
    'publishedAt' => 'DESC',
    'translation' => $translation,
]);

This second code snippet uses standard Doctrine Entity Manager to directly grab node-sources by their entity class. This method does not check any security and will return every node-sources, even unpublished, archived and deleted ones.

Hierarchy

To trasverse node-sources hierarchy, the easier method is to use Twig filters on your nodeSource entity. Filters will implicitly set translation from origin node-source.

{% set children = nodeSource|children %}
{% set nextSource = nodeSource|next %}
{% set prevSource = nodeSource|previous %}
{% set parent = nodeSource|parent %}

{% set children = nodeSource|children({
    'visible': true
}) %}

All these filters will take care of publication status and translation, but not publication date-time neither visibility.

{% set children = nodeSource|children({
    'visible': true,
    'publishedAt': ['>=', date()],
}, {
    'publishedAt': 'DESC'
}) %}

If you need to trasverse node-source hierarchy from your controllers you can use the NodesSourcesHandler class.

use RZ\Roadiz\Core\Handlers\NodesSourcesHandler;
// …
$nodeSourceHandler = new NodesSourcesHandler($nodeSource);

$children = $nodeSourceHandler->getChildren([
    'visible' => true,
    'publishedAt' => ['>=', new \DateTime()],
    'translation' => $nodeSource->getTranslation(),
],[
    'publishedAt' => 'DESC'
]);

Or directly use Entity API, this method is preferred as NodesSourcesHandler will be deprecated in future Roadiz versions.

$children = $this->get('nodeSourceApi')->getBy([
    'node.parent' => $nodeSource,
    'visible' => true,
    'publishedAt' => ['>=', new \DateTime()],
    'translation' => $nodeSource->getTranslation(),
],[
    'publishedAt' => 'DESC'
]);
Visibility

There are two parametres that you must take care of in your themes and your controllers, because they are not mandatory in all website cases:

  • Visibility
  • Publication date and time

For example, publication date and time won’t be necessary in plain text pages and not timestampable contents. But we decided to add it directly in NodesSources entity to be able to filter and order with this field in Roadiz back-office. This was not possible if you manually create your own publishedAt as a node-type field.

Warning

Pay attention that publication date and time (publishedAt) and visibility (node.visible) does not prevent your node-source from being viewed if you did not explicitly forbid access to its controller. This field is not deeply set into Roadiz security mechanics.

If you need so, make sure that your node-type controller checks these two fields and throws a ResourceNotFoundException if they’re not satisfied.

class BlogPostController extends MyAwesomeTheme
{
    public function indexAction(
        Request $request,
        Node $node = null,
        Translation $translation = null
    ) {
        $this->prepareThemeAssignation($node, $translation);

        $now = new DateTime("now");
        if (!$nodeSource->getNode()->isVisible() ||
            $nodeSource->getPublishedAt() < $now) {
            throw new ResourceNotFoundException();
        }

        return $this->render(
            'types/blogpost.html.twig',
            $this->assignation
        );
    }
}

Tag system

Nodes are essentially hierarchical entities. So we created an entity to link nodes between them no matter where/what they are. Tags are meant as tag nodes, we couldn’t be more explicit. But if you didn’t understand here is a schema:

_images/tags.svg

You can see that tags can gather heterogenous nodes coming from different types (pages and projects). Tags can be used to display a category-navigation on your theme or to simply tidy your backoffice node database.

Did you notice that Tags are related to Nodes entities, not NodesSources? We thought that it would be easier to manage that way not to forget to tag a specific node translation. It means that you won’t be able to differenciate tag two NodesSources, if you absolutely need to, we encourage you to create two different nodes.

Translate tags

You will notice that tags work the same way as nodes do. By default, tags names can’t contain special characters in order to be used in URLs. So we created TagTranslation entities which stand for Tag’s sources:

_images/tag-translations.svg

In that way you will be able to translate your tags for each available languages.

Tag hierarchy

In the same way as Nodes work, tags can be nested to create tag groups.

Displaying node-source’ tags with Twig
{% set tags = nodeSource|tags %}

<ul>
{% for tag in tags %}
    {% set tagTranslation = tag.translatedTags.first %}
    <li id="{{ tag.tagName }}">{{ tagTranslation.name }}</li>
{% endfor %}
</ul>

Themes

Creating a theme

Roadiz themes are one of the main parts of the CMS. They allow you to create your really personal website. You can duplicate an existing theme to customize stylesheets and images. Or you can start from ground and build your very own theme using our API. Every visible part of Roadiz is a theme. Even backoffice interface is a theme, and it’s called Rozier according to the street name where REZO ZERO created it.

Each theme is a folder which must be placed in themes/ folder. Roadiz comes with 3 default themes :

  • Install : It’s the first page theme you see when you launch Roadiz in your browser for the first time.
  • Rozier : Here is the REZO ZERO designed backoffice for Roadiz, it’s available from rz-admin/ url and protected by firewall.
  • DefaultTheme : It’s a demo theme which is mainly used to demonstrate basic Roadiz features and to try the back-office editing capabilities.

As these 3 themes come bundled with Roadiz, you can’t edit or update their files. Your changes would be overrode the next time you update Roadiz via Git or direct download. If you want to create your own Backoffice, you can. Just name it differently and hook it in backoffice or using CLI commands.

Source Edition

If you are using Roadiz Source edition, we configured Git versioning tool to ignore every additional theme you create in /themes folder. So you can initialize your a new git repository per custom theme you create. That way you can use code versioning independently from Roadiz updates.

Preparing your own frontend theme

To start from a fresh and clean foundation, we built a BaseTheme to fit our needs with many starter node-types and a front-end framework using ES6 and Webpack.

# Use Roadiz command to pull and rename BaseTheme after your own project
bin/roadiz themes:generate MyAwesome

Your theme will be generated as /themes/MyAwesomeTheme with /themes/MyAwesomeTheme/MyAwesomeThemeApp.php class.

Standard Edition

Roadiz Standard edition will create a symbolic link into web/ folder to publish your new theme public assets as /web/themes/MyAwesomeTheme/static. Make sure that your system supports symbolic links.

Edit your main class informations (MyAwesomeThemeApp.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
 /*
  * Copyright REZO ZERO 2016
  *
  * Description
  *
  * @file MyAwesomeThemeApp.php
  * @copyright REZO ZERO 2014
  * @author Ambroise Maupate
  */
 namespace Themes\MyAwesomeTheme;

 use RZ\Roadiz\CMS\Controllers\FrontendController;
 use RZ\Roadiz\Core\Entities\Node;
 use RZ\Roadiz\Core\Entities\Translation;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;

 /**
  * MyAwesomeThemeApp class
  */
 class MyAwesomeThemeApp extends FrontendController
 {

     protected static $themeName =      'My awesome theme';
     protected static $themeAuthor =    'Ambroise Maupate';
     protected static $themeCopyright = 'REZO ZERO';
     protected static $themeDir =       'MyAwesomeTheme';
     protected static $backendTheme =    false;

     //…
 }

Then you will be able to add your fresh new theme into Roadiz backoffice or through Roadiz install.

Theme specific dependencies

Imagine that you need some extra Composer requirements for your theme. Basically, you need to display a social network feed with Twitter and some Instagram duck-face photos. You will need the rezozero/mixedfeed library to be loaded with Composer but you can’t touch the main Roadiz composer.json… How do we do? Roadiz uses the wikimedia/composer-merge-plugin which do some magic stuff with multiple composer.json files. So you just have to create a new composer.json file inside your theme directory and call composer update from the Roadiz root folder.

Warning

Do not use the composer command inside your Theme folders but only at the Roadiz root folder level. If not, Composer will download and install specific dependencies inside your theme and the main autoloader won’t find your new PHP classes.

Static routing

Before searching for a node’s Url (Dynamic routing), Roadiz will parse your theme route.yml to find static controllers and actions to execute. Static actions just have to comply with the Request / Response scheme. It is adviced to add $_locale and $_route optional arguments to better handle multilingual pages.

foo:
    path:     /foo
    defaults: { _controller: Themes\MyAwesomeTheme\Controllers\FooBarController::fooAction }
bar:
    path:     /{_locale}/bar
    defaults: { _controller: Themes\MyAwesomeTheme\Controllers\FooBarController::barAction }
    requirements:
        # Use every 2 letter codes
        _locale: "[a-z]{2}"
public function fooAction(Request $request) {

    $translation = $this->bindLocaleFromRoute($request, 'en');
    $this->prepareThemeAssignation(null, $translation);

    return $this->render('foo.html.twig', $this->assignation);
}

public function barAction(
    Request $request,
    $_locale = null,
    $_route = null
) {
    $translation = $this->bindLocaleFromRoute($request, $_locale);
    $this->prepareThemeAssignation(null, $translation);

    return $this->render('bar.html.twig', $this->assignation);
}
Dynamic routing

Note

Every node-types will be handled by a specific Controller. If your created a “Page” type, Roadiz will search for a …\Controllers\PageController class and it will try to execute the indexAction method.

An indexAction method must comply with the following signature. It will take the HttpFoundation’s Request as first then a Node and a Translation instances. These two last arguments will be useful to generate your page information and to render your current node.

/**
 * Default action for any Page node.
 *
 * @param Symfony\Component\HttpFoundation\Request $request
 * @param RZ\Roadiz\Core\Entities\Node              $node
 * @param RZ\Roadiz\Core\Entities\Translation       $translation
 *
 * @return Symfony\Component\HttpFoundation\Response
 */
public function indexAction(
    Request $request,
    Node $node = null,
    Translation $translation = null
) {
    $this->prepareThemeAssignation($node, $translation);

    return $this->render(
        'types/page.html.twig',  // Twig template path
        $this->assignation      // Assignation array to fill template placeholders
    );
}

As Symfony controllers do, every Roadiz controllers actions have to return a valid Response object. This is the render method purpose which will generate a standard html response using a Twig template and an assignation array.

Note

It’s very easy to create JSON responses for your API with Roadiz. You just have to replace $this->render($template, $assignation); method with $this->renderJson($data);. This method is a shortcut for new JsonResponse($data);.

Home page case

Homepage is always a special page to handle. With Roadiz you have the choice to handle it as a static page or as a dynamic page. In both case you’ll need to setup a static route in your theme Resources/routes.yml file.

homePage:
    path:     /
    defaults: { _controller: Themes\MyAwesomeTheme\MyAwesomeThemeApp::homeAction }
homePageLocale:
    path:     /{_locale}
    defaults: { _controller: Themes\MyAwesomeTheme\MyAwesomeThemeApp::homeAction }
    requirements:
        # Use every 2 letter codes
        _locale: "[a-z]{2}"

Now you can code your homeAction method in MyAwesomeThemeApp class. It will need 2 arguments:

  • A Request object: $request
  • An optional locale string variable $_locale = null
Dynamic home

If your home page is built with a node. You can tell Roadiz to handle home request as a Page request (if your home is a page type node) using $this->handle($request); method. This method will use the PageController class and page.html.twig template to render your home. This can be useful when you need to switch your home page to an other page, there is no need to make special ajustments.

/**
 * {@inheritdoc}
 */
public function homeAction(
    Request $request,
    $_locale = null
) {
    /*
     * Get language from static route
     */
    $translation = $this->bindLocaleFromRoute($request, $_locale);
    $home = $this->getHome($translation);

    /*
     * Render Homepage according to its node-type controller
     */
    return $this->handle($request, $home, $translation);
}
Static home

Imagine now that your home page has a totally different look than other pages. Instead of letting handle() method returning your Response object, you can create it directly and use a dedicated home.html.twig template. The fourth argument static::getThemeDir() is optional, it explicits the namespace to look into. It becames useful when you mix several themes with the same templates names.

/**
 * {@inheritdoc}
 */
public function homeAction(
    Request $request,
    $_locale = null
) {
    /*
     * Get language from static route
     */
    $translation = $this->bindLocaleFromRoute($request, $_locale);
    $home = $this->getHome($translation);

    /*
     * Render Homepage manually
     */
    $this->prepareThemeAssignation($home, $translation);

    return $this->render('home.html.twig', $this->assignation, null, static::getThemeDir());
}

Keep in ming that prepareThemeAssignation method will assign for you some useful variables no matter you choice a dynamic or a static home handling:

  • node
  • nodeSource
  • translation
Using Twig

Note

Twig is the default rendering engine for Roadiz CMS. You’ll find its documentation at http://twig.sensiolabs.org/doc/templates.html

When you use Dynamic routing within your theme, Roadiz will automatically assign some variables for you.

  • request — [object] Symfony request object which contains useful data such as current URI or GET parameters
  • head
    • ajax — [boolean] Tells if current request is an Ajax one
    • cmsVersion — [string]
    • cmsVersionNumber
    • cmsBuild — [string]
    • devMode — [boolean]
    • baseUrl — [string] Server base Url. Basically your domain name, port and folder if you didn’t setup Roadiz at you server root
    • filesUrl — [string]
    • resourcesUrl — [string] Your theme Resources url. Useful to reach your assets.
    • ajaxToken — [string]
    • universalAnalyticsId — [string]
    • useCdn - [boolean]
    • fontToken — [string]
  • session
    • messages — [array]
    • id — [string]
    • user — [object]
  • authorizationChecker — [object]
  • tokenStorage — [object]

There are some more content only available from FrontendControllers.

  • _default_locale — [string]
  • meta
    • siteName — [string]
    • siteCopyright — [string]
    • siteDescription — [string]

Then, in each dynamic routing actions you will need this line $this->storeNodeAndTranslation($node, $translation); in order to make page content available from your Twig template.

  • node — [object]
  • nodeSource — [object]
  • translation — [object]
  • pageMeta
    • title — [string]
    • description — [string]
    • keywords — [string]

All these data will be available in your Twig template using {{ }} syntax. For example use {{ pageMeta.title }} inside your head’s <title> tag. You can of course call objects members within Twig using the dot separator.

<article>
    <h1><a href="{{ path(nodeSource) }}">{{ nodeSource.title }}</a></h1>
    <div>{{ nodeSource.content|markdown }}</div>

    {# Use complex syntax to grab documents #}
    {% set images = nodeSource.handler.documentsFromFieldName('images') %}
    {# or Shortcut syntax #}
    {% set images = nodeSource.images %}

    {% for image in images %}
        {% set imageMetas = image.documentTranslations.first %}
        <figure>
            {{ image|display({'width':200 }) }}
            <figcaption>{{ imageMetas.name }}{{ imageMetas.copyright }}</figcaption>
        </figure>
    {% endfor %}
</article>
Generating paths and url

Standard Twig path and url methods are both working for static and dynamic routing. In Roadiz, these methods can take either a string identifier or a NodesSources instance. Of course optional parameters are available for both, they will automatically create an http query string when using a node-source.

{# Path generation with a Symfony route  #}
{# Eg. /fr  #}
{{ path('homePageLocale', {_locale: 'fr'}) }}

{# Path generation with a node-source  #}
{# Eg. /en/about-us  #}
{{ path(nodeSource) }}

{# Url generation with a node-source  #}
{# Eg. http://localhost:8080/en/about-us  #}
{{ url(nodeSource) }}

{# Path generation with a node-source and parameters  #}
{# Eg. /en/about-us?page=2  #}
{{ path(nodeSource, {'page': 2}) }}
Handling node-sources with Twig

Most of yout front-end work will consist in editing Twig templating, Twig assignations and… Twig filters. Roadiz core entities are already linked together so you don’t have to prepare your data before rendering it. Basically, you can access nodes or node-sources data directly in Twig using the “dot” seperator.

There is even some magic about Twig when accessing private or protected fields: just write the fieldname and it will use the getter method instead: {{ nodeSource.content|markdown }} will be interpreted as {{ nodeSource.getContent|markdown }} by Twig.

Note

Roadiz will transform your node-type fields names to camel-case to create getters and setters into you NS class. So if you created a header_image field, getter will be named getHeaderImage(). However, if you called it headerimage, getter will be getHeaderimage()

You can access methods too! You will certainly need to get node-sources’ documents to display them. Instead of declaring each document in your PHP controller before, you can directly use them in Twig:

{% set images = nodeSource.images %}
{% for image in images %}
    {% set imageMetas = image.documentTranslations.first %}
    <figure>
        {{ image|display({ 'width':200 }) }}
        <figcaption>{{ imageMetas.name }}{{ imageMetas.copyright }}</figcaption>
    </figure>
{% endfor %}
Loop over node-source children

With Roadiz you will be able to grab each node-source children using custom children Twig filter. This filter is a shortcut for childBlock->getHandler()->getChildren(null, null, $authorizationChecker).

{% set childrenBlocks = nodeSource|children %}
{% for childBlock in childrenBlocks %}
<div class="block">
    <h2>{{ childBlock.title }}</h2>
    <div>{{ childBlock.content|markdown }}</div>
</div>
{% endfor %}

getChildren method must be called with a valid AuthorizationChecker instance if you don’t want anonymous visitors to see unpublished contents. Its first parameters can be set to filter over children and override default ordering. If your are using |children filter, authorization-checker is automatically passed to getChildren method.

{#
 # This statement will only grab *visible* children node-sources and
 # will order them ascendently according to their *title*.
 #}
{% set childrenBlocks = nodeSource|children(
    {'node.visible': true},
    {'title': 'ASC'}
) %}

Note

Calling getChildren() from a node-source handler or |children filter will always return NodesSources objects from the same translation as their parent.

Additional filters

Roadiz’s Twig environment implements some useful filters, such as:

  • markdown: Convert a markdown text to HTML
  • inlineMarkdown: Convert a markdown text to HTML without parsing block elements (useful for just italics and bolds)
  • markdownExtra: Convert a markdown-extra text to HTML (footnotes, simpler tables, abbreviations)
  • centralTruncate(length, offset, ellipsis): Generate an ellipsis at the middle of your text (useful for filenames). You can decenter the ellipsis position using offset parameter, and even change your ellipsis character with ellipsis parameter.
NodesSources filters

These following Twig filters will only work with NodesSources entities… not Nodes. Use them with the pipe syntax, eg. nodeSource|next.

  • children: shortcut for $source->getHandler()->getChildren()
  • next: shortcut for $source->getHandler()->getNext()
  • previous: shortcut for $source->getHandler()->getPrevious()
  • firstSibling: shortcut for $source->getHandler()->getFirstSibling()
  • lastSibling: shortcut for $source->getHandler()->getLastSibling()
  • parent: shortcut for $source->getHandler()->getParent()
  • parents: shortcut for $source->getHandler()->getParents()
  • tags: shortcut for $source->getHandler()->getTags()
  • render(themeName): initiate a sub-request for rendering a given block NodesSources
Documents filters

These following Twig filters will only work with Document entities. Use them with the pipe syntax, eg. document|display.

  • url: returns document public URL as string. See document URL options.
  • display: generates an HTML tag to display your document. See document display options.
  • imageRatio: return image size ratio as float.
  • imageSize: returns image size as array with width and height.
  • imageOrientation: get image orientation as string, returns landscape or portrait.
  • path: shortcut for document real path on server.
  • exists: shortcut to test if document file exists on server. Returns boolean.
Translations filters

These following Twig filters will only work with Translation entities. Use them with the pipe syntax, eg. translation|menu.

  • menu: shortcut for $translation->getViewer()->getTranslationMenuAssignation().

This filter returns some useful informations about current page available languages and their urls. See getTranslationMenuAssignation method definition. You do not have to pass it the current request object as the filter will grab it for you. But you can specify if you want absolute urls or not.

Standard filters and extensions are also available:

  • {{ path('myRoute') }}: for generating static routes Url.
  • truncate and wordwrap which are parts of the Text Extension .
Create your own Twig filters

Imagine now that your are rendering some dynamic CSS stylesheets with Twig. Your are listing your website projects which all have a distinct color. So you’ve created a CSS route and a dynamic-colors.css.twig.

{% for project in projects %}
.{{ project.node.nodeName }} h1 {
    color: {{ project.color }};
}
{% endfor %}

This code should output a CSS like that:

.my-super-project h1 {
    color: #FF0000;
}
.my-second-project h1 {
    color: #00FF00;
}

Then you should see your “super project” title in red on your website. OK, that’s great. But what should I do if I need to use a RGBA color to control the Alpha channel value? For example, I want to set project color to a <div class="date"> background like this:

.my-super-project .date {
    background-color: rgba(255, 0, 0, 0.5);
}
.my-second-project .date {
    background-color: rgba(0, 255, 0, 0.5);
}

Great… I already see coming guys complaining that “rgba” is only supported since IE9… We don’t give a shit!…

Hum, hum. So you need a super filter to extract decimal values from our backoffice stored hexadecimal color. Roadiz enables us to extend Twig environment filters thanks to dependency injection!

You just have to extend setupDependencyInjection static method in your main theme class. Create it if it does not exist yet.

// In your SuperThemeApp.php
public static function setupDependencyInjection(\Pimple\Container $container)
{
    parent::setupDependencyInjection($container);

    // We extend twig filters
    $container->extend('twig.filters', function ($filters, $c) {

        // The first filter will extract red value
        $red = new \Twig_SimpleFilter('red', function ($hex) {
            if ($hex[0] == '#' && strlen($hex) == 7) {
                return hexdec(substr($hex, 1, 2));
            } else {
                return 0;
            }
        });
        $filters->add($red);

        // The second filter will extract green value
        $green = new \Twig_SimpleFilter('green', function ($hex) {
            if ($hex[0] == '#' && strlen($hex) == 7) {
                return hexdec(substr($hex, 3, 2));
            } else {
                return 0;
            }
        });
        $filters->add($green);

        // The third filter will extract blue value
        $blue = new \Twig_SimpleFilter('blue', function ($hex) {
            if ($hex[0] == '#' && strlen($hex) == 7) {
                return hexdec(substr($hex, 5, 2));
            } else {
                return 0;
            }
        });
        $filters->add($blue);

        // Then we return our extended filters collection
        return $filters;
    });
}

And… Voilà! You can use red, green and blue filters in your Twig template.

{% for project in projects %}
.{{ project.node.nodeName }} .date {
    background-color: rgba({{ project.color|red }}, {{ project.color|green }}, {{ project.color|blue }}, 0.5);
}
{% endfor %}
Use custom Twig extensions

Just like you did to add your own Twig filters, you can add your own Twig extensions. Instead of extending twig.filters service, just extend twig.extensions service.

// In your SuperThemeApp.php
public static function setupDependencyInjection(\Pimple\Container $container)
{
    parent::setupDependencyInjection($container);

    // We extend twig extensions
    $container->extend('twig.extensions', function ($extensions, $c) {
        $extensions->add(new MySuperThemeTwigExtension());
        return $extensions;
    });
}
Displaying documents

Did you noticed that images relation is available directly in nodeSource object? That’s a little shortcut to nodeSource.handler.documentFromFieldName('images'). Cool, isn’t it? When you create your documents field in your node-type, Roadiz generate a shortcut method for each document relation in your GeneratedNodesSources/NSxxxx class.

Now, you can use DocumentViewer to generate HTML view for your documents no matter they are images, videos or embed. Two Twig filters are available with Documents:

  • |display is a shortcut for getViewer()->getDocumentByArray($options). It generates an HTML tag to display your document.
  • |url is a shortcut for getViewer()->getDocumentUrlByArray($options). It generates an Url to reach your document.
{# Grab only first document from “images” field #}
{% set image = nodeSource.images[0] %}

{# Always test if document exists #}
{% if image %}
{{ image|display({
    'width':200,
    'height':200,
    'crop':"1:1",
    'quality':75,
    'embed':true
}) }}
{% endif %}
HTML output options
  • absolute (true|false), generates an absolute URL with protocol, domain-name and base-url. This must be used for social network images.
  • embed (true|false), display an embed as iframe instead of its thumbnail
  • identifier
  • class
  • alt: If not filled, it will get the document name, then the document filename
  • lazyload (true|false), fill image src in a data-src attribute instead of src to prevent it from loading.
Images resampling options
  • width
  • height
  • crop (ratio: {w}:{h}, for example : 16:9)
  • fit (fixed dimensions: {w}x{h}, for example : 100x200), if you are using fit option, Roadiz will be able to add width and height attributes to your <img> tag.
  • grayscale (boolean)
  • quality (1-100)
  • blur (1-100) (can be really slow to process)
  • sharpen (1-100)
  • contrast (1-100)
  • background (hexadecimal color without #)
  • progressive (boolean), it will interlace the image if it’s a PNG file.
  • noProcess (boolean): Disable image processing
Audio / Video options
  • autoplay
  • controls
Using src-set attribute for responsive images

Roadiz can generate a srcset attribute to create a responsive image tag like the one you can find on these examples.

  • srcset (Array) Define for each rule an Array of format. Specifications
{% set image = nodeSource.images[0] %}
{% if image %}
{{ image|display({
    'fit':'600x600',
    'quality':75,
    'srcset': [
        {
            'format': {
                'fit':'200x200',
                'quality':90
            },
            'rule': '780w',
        },
        {
            'format': {
                'fit':'600x600',
                'quality':75
            },
            'rule': '1200w',
        }
    ],
    'sizes': [
        '(max-width: 780px) 200px',
        '(max-width: 1200px) 600px',
    ],
}) }}
{% endif %}

This will output an img tag like the following one:

<img src="/assets/f600x600-q75/image.jpg"
     srcset="/assets/f600x600-q75/image.jpg 1200w, /assets/f200x200-q90/image.jpg 780w"
     sizes="(max-width: 780px) 200px, (max-width: 1200px) 600px"
     alt="A responsive image">
More document details

You can find more details in our API documentation.

  • If document is an image: getDocumentByArray method will generate an <img /> tag with a src and alt attributes.
  • If it’s a video, it will generate a <video /> tag with as many sources as available in your document database. Roadiz will look for same filename with each HTML5 video extensions (filename.mp4, filename.ogv, filename.webm).
  • Then if document is an external media and if you set the embed flag to true, it will generate an iframe according to its platform implementation (Youtube, Vimeo, Soundcloud).
Extending your Twig assignation

For a simple website theme, base assignation will work for almost every cases. Using node or nodeSource data from your Twig template, you will be able to render all your page fields.

Now imagine you need to load data from another node than the one being requested. Or imagine that you want to create a complex homepage which displays a summary of your latest news. You will need to extend existing assignated variables.

For example, create a simple node-type called Page. Add several basic fields inside it such as content and images. If you well-understood how to create a theme section you will create a PageController.php which look like this:

<?php
namespace Themes\MyTheme\Controllers;

use Themes\MyTheme\MyThemeApp;
use RZ\Roadiz\Core\Entities\Node;
use RZ\Roadiz\Core\Entities\Translation;
use Symfony\Component\HttpFoundation\Request;

/**
 * Frontend controller to handle Page node-type request.
 */
class PageController extends MyThemeApp
{
    /**
     * Default action for any Page node.
     *
     * @param Symfony\Component\HttpFoundation\Request $request
     * @param RZ\Roadiz\Core\Entities\Node              $node
     * @param RZ\Roadiz\Core\Entities\Translation       $translation
     *
     * @return Symfony\Component\HttpFoundation\Response
     */
    public function indexAction(
        Request $request,
        Node $node = null,
        Translation $translation = null
    ) {
        $this->prepareThemeAssignation($node, $translation);

        return $this->render('types/page.html.twig', $this->assignation);
    }
}

You will be able to render your page using themes/MyTheme/Resources/views/types/page.html.twig template file:

{% extends '@MyTheme/base.html.twig' %}

{% block content %}

<h1>{{ nodeSource.title }}</h1>
<div class="content">{{ nodeSource.content|markdown }}</div>
<div class="images">
    {% for image in nodeSource.images %}
        <figure>
            {{ image|display }}
        </figure>
    {% endfor %}
</div>
{% endblock %}
Use theme-wide assignation

Custom assignations are great but what can I do if I have to use the same variables in several controllers? We added a special extendAssignation method which is called at the end of your theme preparation process (prepareThemeAssignation and prepareNodeSourceAssignation). Just override it in your MyThemeApp main class, then every theme controllers and templates will be able to use these variables.

For example, you can use this method to make <head> variables available for each of your website pages.

/**
 * {@inheritdoc}
 */
protected function extendAssignation()
{
    parent::extendAssignation();

    $this->assignation['head']['facebookUrl'] = SettingsBag::get('facebook_url');
    $this->assignation['head']['facebookClientId'] = SettingsBag::get('facebook_client_id');
    $this->assignation['head']['instagramUrl'] = SettingsBag::get('instagram_url');
    $this->assignation['head']['twitterUrl'] = SettingsBag::get('twitter_url');
    $this->assignation['head']['googleplusUrl'] = SettingsBag::get('googleplus_url');
    $this->assignation['head']['googleClientId'] = SettingsBag::get('google_client_id');
    $this->assignation['head']['maps_style'] = SettingsBag::get('maps_style');
    $this->assignation['head']['themeName'] = static::$themeName;
    $this->assignation['head']['themeVersion'] = static::VERSION;
}
Use Page / Block data pattern

At REZO ZERO, we often use complex page design which need removable and movable parts. At first we used to create long node-types with a lot of fields, and when editors needed to move content to an other position, they had to cut and paste text to another field. It was long and not very sexy.

So we thought about a modulable way to build pages. We decided to use one master node-type and several slave node-types instead of a single big type. Here is what we call Page/Block pattern.

This pattern takes advantage of Roadiz node hierarchy. We create a very light Page node-type, with an excerpt and a thumbnail fields, then we create an other node-type that we will call BasicBlock. This block node-type will have a content and image fields.

The magic comes when we add a last field into Page master node-type called children_nodes. This special field will display a node-tree inside your edit page. In this field parameter, we add BasicBlock name as a default value to tell Roadiz that each Page nodes will be able to contain BasicBlock nodes.

So you understood that all your page data will be allocated in several BasicBlock nodes. Then your editor will just have to change block order to re-arrange your page content. That’s not all! With this pattern you can join images to each block so that each paragraph can be pictured with a Document field. No need to insert image tags right into your Markdown text as you would do in a Wordpress article.

How to template Page / Block pattern

Now that you’ve structured your data with a Page node-type and a BasicBlock, how do render your data in only one page and only one URL request? We will use custom assignations!

Open your PageController.php file to assign the node-type you need to filter children nodes:

$this->prepareThemeAssignation($node, $translation);

// Get BasicBlock node-type entity to filter
// over current node children
$this->assignation['basicBlockType'] = $this->get('nodeTypeApi')
                       ->getOneBy(['name' => 'BasicBlock']);

Note

If you reuse any node-type entity more than once in several controllers, it’s better to publish your basicBlockType as a service in your theme, instead of duplicating your code.

You can directly assign your children blocks at the beginning of your Twig template.

{% set blocks = nodeSource|children({
    node.nodeType : basicBlockType,
}) %}

Note

You can use different block types in the same page. Just create as many node-types as you need and add their name to your Page children_node default values. Then add each node-type into children criteria using an array instead of a single value: node.nodeType : [basicBlockType, anotherBlockType]. That way, you will be able to create awesome pages with different looks but with the same template (basic blocks, gallery blocks, etc).

Now we can update your types/page.html.twig template to use your assignated blocks.

{% if blocks %}
<section class="page-blocks">
{% for pageBlock in blocks %}
    {% include '@MyTheme/blocks/' ~ pageBlock.node.nodeType.name|lower ~ '.html.twig' with {
        'nodeSource': pageBlock,
        'parentNodeSource': nodeSource,
        'themeServices': themeServices,
        'head': head,
        'node': pageBlock.node,
        'nodeType': pageBlock.node.nodeType,
        'loop': loop,
        'blocksLength':blocks|length
    } only %}
{% endfor %}
</section>
{% endif %}

Whaaat? What is that include? This trick will save you a lot of time! We ask Twig to include a sub-template according to each block type name. Eg. for a BasicBlock node, Twig will include a blocks/basicblock.html.twig file. It’s even more powerful when you are using multiple block types because Twig will automatically choose the right template to render each part of your page.

Then create each of your blocks templates files in blocks folder:

{# This is file: blocks/basicblock.html.twig #}

<div class="basicblock {% if loop.index0 is even %}even{% else %}odd{% endif %}">
    {#
     # Did you notice that 'pageBlock' became 'nodeSource' as
     # we passed it during include for a better compatibility
     #}
    <h3>{{ nodeSource.title }}</h3>
    <div class="content">{{ nodeSource.content|markdown }}</div>

    <div class="images">
    {% for image in nodeSource.images %}
        <figure>
            {{ image|display({'width':200}) }}
        </figure>
    {% endfor %}
    </div>
</div>

Voilà! This is the simplest example to demonstrate you the power of Page / Block pattern. If you managed to reproduce this example you can now try it using multiple block node-types, combining multiple sub-templates.

Use block rendering

A few times, using Page / Block pattern won’t be enough to display your page blocks. For example, you will occasionally need to create a form inside a block, or you will need to process some data before using them in your Twig template.

For this we added a render filter which basically create a sub-request to render your block. This new request make possible to create a dedicated Controller for your block.

Let’s take the previous example about a page with several basic blocks inside. Imagine you have a new contact block to insert in your page, then how would you create your form? The following code shows how to “embed” a sub-request inside your block template.

{#
 # This is file: blocks/contactblock.html.twig
 #}
<div class="contactblock {% if loop.index0 is even %}even{% else %}odd{% endif %}">

    <h3>{{ nodeSource.title }}</h3>
    <div class="content">{{ nodeSource.content|markdown }}</div>

    {#
     # We created a display_form node-type field to enable/disable form
     # but this is optional
     #}
    {% if nodeSource.displayForm %}
        {#
         # “render” twig filter initiate a new Roadiz request
         # using *nodeSource* as primary content. It takes one
         # argument to locate your block controller
         #}
        {{ nodeSource|render('MyTheme') }}
    {% endif %}
</div>

Then Roadiz will look for a Themes\MyTheme\Controllers\Blocks\ContactBlockController.php file and a blockAction method inside.

namespace Themes\MyTheme\Controllers\Blocks;

use RZ\Roadiz\Core\Entities\NodesSources;
use RZ\Roadiz\Core\Exceptions\ForceResponseException;
use Symfony\Component\HttpFoundation\Request;
use Themes\MyTheme\MyThemeApp;

class ContactBlockController extends MyThemeApp
{
    function blockAction(Request $request, NodesSources $source, $assignation)
    {
        $this->prepareNodeSourceAssignation($source, $source->getTranslation());

        $this->assignation = array_merge($this->assignation, $assignation);

        // If you assignate session messages here, do not assignate it in your
        // MyThemeApp::extendAssignation() method before.
        $this->assignation['session']['messages'] = $this->get('session')->getFlashBag()->all();

        /*
         * Add your form code here, for example
         */
        $form = $this->createFormBuilder()->add('name', 'text')
                                          ->add('send_name', 'submit')
                                          ->getForm();
        $form->handleRequest($request);
        if ($form->isValid()) {
            // some stuff
            throw new ForceResponseException($this->redirect($request->getUri()));
        }

        $this->assignation['contactForm'] = $form->createView();

        return $this->render('form-blocks/contactblock.html.twig', $this->assignation);
    }
}

Then create your template form-blocks/contactblock.html.twig:

<div class="contact-form">
    {% for messages in session.messages %}
        {% for message in messages %}
            <p class="alert alert-success">{{ message }}</p>
        {% endfor %}
    {% endfor %}

    {{ form(contactForm) }}
</div>
Paginate entities using EntityListManager

Roadiz implements a powerful tool to display lists and paginate them. Each Controller class allows developer to use createEntityListManager method.

In FrontendController inheriting classes, such as your theme ones, this method is overriden to automatically use the current authorizationChecker to filter entities by status when entities are nodes.

createEntityListManager method takes 3 arguments:

  • Entity classname, i.e. RZ\Roadiz\Core\Entities\Nodes or GeneratedNodeSources\NSArticle. The great thing is that you can use it on a precise NodesSources class instead of using Nodes or NodesSources then filtering on node-type. Using a NS entity allows you to filter on your own custom fields too.
  • Criteria array, (optional)
  • Ordering array, (optional)

EntityListManager will automatically grab the current page looking for your Request parameters. If ?page=2 is set or ?search=foo, it will use them to filter your list and choose the right page.

If you want to handle pagination manually, you always can set it with setPage(page) method, which must be called after handling EntityListManager. It is useful to bind page parameter in your routing configuration.

projectPage:
    path:     /articles/{page}
    defaults: { _controller: Themes\MyAwesomeTheme\Controllers\ArticleController::listAction, page: 1 }
    requirements:
        page: "[0-9]+"

Then, build your listAction method.

public function listAction(
    Request $request,
    $page,
    $_locale = 'en'
) {
    $translation = $this->bindLocaleFromRoute($request, $_locale);
    $this->prepareThemeAssignation(null, $translation);

    $listManager = $this->createEntityListManager(
        'GeneratedNodeSources\\NSArticle',
        ['sticky' => false], //sticky is a custom field from Article node-type
        ['node.createdAt' => 'DESC']
    );
    /*
     * First, set item per page
     */
    $listManager->setItemPerPage(20);
    /*
     * Second, handle the manager
     */
    $listManager->handle();
    /*
     * Third, set current page manually
     * AFTER handling entityListManager
     */
    if ($page > 1) {
        $listManager->setPage($page);
    }

    $this->assignation['articles'] = $listManager->getEntities();
    $this->assignation['filters'] = $listManager->getAssignation();

    return $this->render('types/articles-feed.html.twig', $this->assignation);
}

Then create your articles-feed.html.twig template to display each entity paginated.

{# Listing #}
<ul class="article-list">
    {% for article in articles %}
        <li class="article-item">
            <a class="article-link" href="{{ path(article) }}">
                <h2>{{ article.title }}</h2>
            </a>
        </li>
    {% endfor %}
</ul>

{# Pagination #}
{% if filters.pageCount > 1 %}
    <nav class="pagination">
        {% if filters.currentPage > 1 %}
            <a class="prev-link" href="{{ path('projectPage', {page: filters.currentPage - 1}) }}">
                {% trans %}prev.page{% endtrans %}
            </a>
        {% endif %}
        {% if filters.currentPage < filters.pageCount %}
            <a class="next-link" href="{{ path('projectPage', {page: filters.currentPage + 1}) }}">
                {% trans %}next.page{% endtrans %}
            </a>
        {% endif %}
    </nav>
{% endif %}
Add a firewall in your theme

You may need to add a secured area in your website or application, even for none-backend users. Roadiz uses Symfony security components to handle firewalled requests. You will be able to extend the firewall map in your Theme setupDependencyInjection method.

Before create your firewall map entry, you must understand that Roadiz already has 2 firewall areas:

  • ^/rz-admin area, which naturally matches every back-office sections
  • ^/ area which is required for previewing unpublished node and get user informations across the whole website

The last firewall request matcher can be tricky to deal with, especially if you want to add another secured area as it listen to every requests. When you’ll add your new firewall map entry, always call parent::setupDependencyInjection($container); after your custom configuration to be sure that ^/ request matcher has the lowest priority.

/**
 * {@inheritdoc}
 */
public static function setupDependencyInjection(Container $container)
{
    /*
     * Your custom firewall map entry configuration
     * goes here
     */


    /*
     * Always setup general setupDependencyInjection AFTER
     * theme because of requestMatcher order.
     */
    parent::setupDependencyInjection($container);
}
Configuring a firewall map entry with FirewallEntry class

Before copy and pasting the following lines, think about it a little time… A firewall map entry defines severals mandatory routes:

  • A base path for your firewall to be triggered
  • A login path, which must be outside of your firewall map
  • A login_check path, which must be inside of your firewall map
  • A logout path, which must be inside of your firewall map
  • A new role describing your secured area purpose (i.e. ROLE_ACCESS_PRESS for a private press kit area), you should create this role in Roadiz backoffice before.

If this example I will use:

  • /press as my base path for secured area
  • /signin for my login page, notice that it’s not in my firewall
  • /press/login_check
  • /press/logout
  • ROLE_ACCESS_PRESS

Here is the code to add in your theme’ setupDependencyInjection method. Do not forget to add the matching use statement in your file header.

use RZ\Roadiz\Utils\Security\FirewallEntry;

/**
 * {@inheritdoc}
 */
public static function setupDependencyInjection(Container $container)
{
    $firewallBasePattern = '^/press';
    $firewallBasePath = '/press';
    $firewallLogin = '/signin';
    $firewallLogout = '/press/logout';
    $firewallLoginCheck = '/press/login_check';
    $firewallBaseRole = 'ROLE_ACCESS_PRESS';

    $firewallEntry = new FirewallEntry(
        $container,
        $firewallBasePattern,
        $firewallBasePath,
        $firewallLogin,
        $firewallLogout,
        $firewallLoginCheck,
        $firewallBaseRole
        // You can add a special AuthenticationSuccessHandler
        // if you need to do some stuff for your theme at visitor login
        //'Themes\YourTheme\Authentification\AuthenticationSuccessHandler'
    );
    // Allow anonymous authentification
    $firewallEntry->withAnonymousAuthenticationListener();
    // Allow switch user feature
    $firewallEntry->withSwitchUserListener();

    /*
     * Finally add this entry to the Roadiz
     * firewall map.
     */
    $container['firewallMap']->add(
        $firewallEntry->getRequestMatcher(),
        $firewallEntry->getListeners(),
        $firewallEntry->getExceptionListener()
    );

    /*
     * Always setup general setupDependencyInjection AFTER
     * theme because of requestMatcher order.
     */
    parent::setupDependencyInjection($container);
}
Add login routes

After configuring your Firewall, you’ll need to add your routes to your theme routes.yml file. Logout and login_check won’t need any controller setup as they will be handled directly by Roadiz firewall event dispatcher. The only one you need to handle is the login page.

themeLogout:
    path:     /press/logout
themeLoginCheck:
    path:     /press/login_check
themeLoginPage:
    path:     /signin
    defaults: { _controller: Themes\MySuperTheme\Controllers\LoginController::loginAction }

In your LoginController, just add error handling from the securityAuthenticationUtils service to display a feedback on your login form:

/**
 * {@inheritdoc}
 */
public function loginAction(
    Request $request,
    $_locale = 'en'
) {
    $translation = $this->bindLocaleFromRoute($request, $_locale);
    $this->prepareThemeAssignation(null, $translation);
    $helper = $this->get('securityAuthenticationUtils');
    $this->assignation['last_username'] = $helper->getLastUsername();
    $this->assignation['error'] = $helper->getLastAuthenticationError();

    return $this->render('press/login.html.twig', $this->assignation);
}

Then, you can create your login form as you want. Just use the required fields:

  • _username
  • _password

And do not forget to set your form action to {{ path('themeLoginCheck') }} and to use POST method.

{% if error %}
    <div class="alert alert-danger"><i class="fa fa-warning"></i> {{ error.message|trans }}</div>
{% endif %}
<form id="login-form" class="form" action="{{ path('themeLoginCheck') }}" method="post">
    <div class="form-group">
        <label class="control-label" for="_username">{% trans %}username{% endtrans %}</label>
        <input class="form-control" type="text" name="_username" id="_username" placeholder="{% trans %}username{% endtrans %}" value="" />
    </div>
    <div class="form-group">
        <label class="control-label" for="_password">{% trans %}password{% endtrans %}</label>
        <input class="form-control" type="password" name="_password" id="_password" placeholder="{% trans %}password{% endtrans %}" value="" />
    </div>
    <div class="form-group">
        <label class="control-label" for="_remember_me">{% trans %}keep_me_logged_in{% endtrans %}</label>
        <input class="form-control" type="checkbox" name="_remember_me" id="_remember_me" value="1" />
    </div>
    <div class="form-group">
        <button class="btn btn-primary" type="submit"><i class="fa fa-signin"></i> {% trans %}login{% endtrans %}</button>
    </div>
</form>

Forms

Roadiz uses Symfony forms logic and API. However, we made ready-made contact and custom forms builders to ease up your development and even make form-building available for your website editors.

Building contact forms

With Roadiz you can easily create simple contact forms with ContactFormManager class. Your controller has a convenient shortcut to create this manager with $this->createContactFormManager() method.

If you want to add your own fields, you can use the manager’ form-builder with $contactFormManager->getFormBuilder();. Then add your field using standard Symfony form syntax. Do not forget to use Constraints to handle errors.

One contact-form for one action

Here is an example to create your contact form in your controller action.

 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
 use Symfony\Component\Validator\Constraints\File;

 
 // Create contact-form manager and add 3 default fields.
 $contactFormManager = $this->createContactFormManager()
                            ->withDefaultFields();
 /*
  * (Optional) Add custom fields…
  */
 $formBuilder = $contactFormManager->getFormBuilder();
 $formBuilder->add('callMeBack', 'checkbox', [
         'label' => 'call.me.back',
         'required' => false,
     ])
     ->add('document', 'file', [
         'label' => 'document',
         'required' => false,
         'constraints' => [
             new File([
                 'maxSize' => $contactFormManager->getMaxFileSize(),
                 'mimeTypes' => $contactFormManager->getAllowedMimeTypes(),
             ]),
         ]
     ])
     ->add('send', 'submit', [
         'label' => 'send.contact.form',
     ]);

 /*
  * This is the most important point. handle method will perform form
  * validation and send email.
  *
  * Handle method should return a Response object if everything is OK.
  */
 if (null !== $response = $contactFormManager->handle()) {
     return $response;
 }

 $form = $contactFormManager->getForm();

 // Assignate your form view to display it in Twig.
 $this->assignation['contactForm'] = $form->createView();

In this example, we used withDefaultFields method which add automatically email, name and message fields with right validation contraints. This method is optional and you can add any field you want manually, just keep in mind that you should always ask for an email.

Add session messages to your assignations:

// Get session messages
$this->assignation['session']['messages'] = $this->get('session')
                                                 ->getFlashBag()
                                                 ->all();

Then in your contact page Twig template

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 {#
  # Display contact errors
  #}
 {% if session.messages|length %}
     {% for type, msgs in session.messages %}
         {% for msg in msgs %}
             <div class="alert alert-{% if type == "confirm" %}success{% elseif type == "warning" %}warning{% else %}danger{% endif %}">
                 <p>{{ msg }}</p>
             </div>
         {% endfor %}
     {% endfor %}
 {% endif %}
 {#
  # Display contact form
  #}
 {% form_theme contactForm '@MyTheme/forms.html.twig' %}
 {{ form(contactForm) }}
Using contact-form in block controllers

If you want to use contact-forms in blocks instead of a full page, you will need to make your redirection response bubble through Twig render. The only way to stop Twig is to throw an exception and to pass your Redirect or Json response within your Exception.

Roadiz makes this possible with RZ\Roadiz\Core\Exceptions\ForceResponseException. For example, in a Themes\MyAwesomeTheme\Controllers\Blocks\ContactBlockController, instead of returning the contactFormManager response, you will have to throw a ForceResponseException with it as an argument.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 // ./themes/MyAwesomeTheme/Controllers/Blocks/ContactBlockController.php

 use RZ\Roadiz\Core\Exceptions\ForceResponseException;

 
 // Create contact-form manager and add 3 default fields.
 $contactFormManager = $this->createContactFormManager()
                            ->withDefaultFields();

 if (null !== $response = $contactFormManager->handle()) {
     /*
      * Force response to bubble through Twig rendering process.
      */
     throw new ForceResponseException($response);
 }

 $form = $contactFormManager->getForm();

 // Assignate your form view to display it in Twig.
 $this->assignation['contactForm'] = $form->createView();

 return $this->render('blocks/contactformblock.html.twig', $this->assignation);

Then, in your master controller (i.e. PageController), render method will automatically catch your ForceResponseException exception in order to extract the forced response object. Then it will return your response instead of your page twig rendered output.

Securing your form with Google reCAPTCHA

Roadiz can seamlessly use Google reCAPTCHA to secure your contact form against robots. All you need to do is to register on https://www.google.com/recaptcha/ to ask for a sitekey and a secret. Once you’ve got these two keys, add them to your Roadiz settings.

_images/recaptcha-settings.png

Then, just use withGoogleRecaptcha() method on your contact-form manager.

// Create contact-form manager, add 3 default fields and add a reCAPTCHA.
$contactFormManager = $this->createContactFormManager()
                           ->withDefaultFields()
                           ->withGoogleRecaptcha();

Do not forget to add recaptcha form-template and to embed google’s javascript.

<script src='https://www.google.com/recaptcha/api.js'></script>
{# In your theme’ forms.html.twig file #}
{% block recaptcha_widget -%}
    <div class="g-recaptcha" data-sitekey="{{ configs.publicKey }}"></div>
{%- endblock recaptcha_widget %}
Building custom forms

Building a custom form looks like building a node but it is a lot simpler! Let’s have a look at structure image.

_images/custom-form.svg

After creating a custom form, you add some question. The questions are the CustomFormField type.

The answer is saved in two entities:
  • in CustomFormAnswer
  • in CustomFormFieldAttribute

The CustomFormAnswer will store the IP and the submitted time. While question answer will be in CustomFormFieldAttribute with the CustomFormAnswer id and the CustomFormField id.

Adding custom form to your theme

If you want to integrate your custom-forms into your theme, you can use Roadiz CustomFormHelper class to generate a standard FormInterface and to create a view into your theme templates.

First you must create a dedicated action for your node or your block if you used {{ nodeSource|render(@AwesomeTheme) }} Twig filter.

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
 use RZ\Roadiz\Core\Entities\CustomForm;
 use RZ\Roadiz\Core\Exceptions\EntityAlreadyExistsException;
 use RZ\Roadiz\Core\Exceptions\ForceResponseException;
 use RZ\Roadiz\Utils\CustomForm\CustomFormHelper;
 use Symfony\Component\Form\FormError;
 use Symfony\Component\HttpFoundation\JsonResponse;

 // …

 /*
  * Get your custom form instance from your node-source
  * only if you added a *custom-form reference field*.
  */
 $customForms = $this->nodeSource->getCustomformReference();
 if (isset($customForms[0]) && $customForms[0] instanceof CustomForm) {
     /** @var CustomForm $customForm */
     $customForm = $customForms[0];

     /*
      * Verify if custom form is still open
      * for answers
      */
     if ($customForm->isFormStillOpen()) {
         /*
          * CustomFormHelper will generate Symfony form against
          * Roadiz custom form entity.
          * You can add a Google Recaptcha passing following options.
          */
         $helper = new CustomFormHelper($this->get('em'), $customForm);
         $form = $helper->getFormFromAnswer($this->get('formFactory'), null, true, [
             'recaptcha_public_key' => SettingsBag::get('recaptcha_public_key'),
             'recaptcha_private_key' => SettingsBag::get('recaptcha_private_key'),
             'request' => $request,
         ]);
         $form->handleRequest($request);

         if ($form->isSubmitted() && $form->isValid()) {
             try {
                 $answer = $helper->parseAnswerFormData($form, null, $request->getClientIp());

                 if ($request->isXmlHttpRequest()) {
                     $response = new JsonResponse([
                         'message' => $this->getTranslator()->trans('form_has_been_successfully_sent')
                     ]);
                 } else {
                     $this->publishConfirmMessage(
                         $request,
                         $this->getTranslator()->trans('form_has_been_successfully_sent')
                     );
                     $response = $this->redirect($this->generateUrl($this->nodeSource->getHandler()->getParent()));
                 }
                 /*
                  * If you are in a BlockController use ForceResponseException
                  */
                 throw new ForceResponseException($response);
                 /*
                  * Or directly return redirect response.
                  */
                 //return $response;
             } catch (EntityAlreadyExistsException $e) {
                 $form->addError(new FormError($e->getMessage()));
             }
         }

         $this->assignation['session']['messages'] = $this->get('session')->getFlashBag()->all();
         $this->assignation['form'] = $form->createView();
     }
 }

If you didn’t do it yet, create a custom form theme in your views/ folder:

 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
 {#
  # AwesomeTheme/Resources/views/form.html.twig
  #}
 {% extends "bootstrap_3_layout.html.twig" %}

 {% block form_row -%}
     <div class="form-group form-group-{{ form.vars.block_prefixes[1] }} form-group-{{ form.vars.name }}">
         {% if form.vars.block_prefixes[1] != 'separator' %}
             {{- form_label(form) -}}
         {% endif %}
         {{- form_errors(form) -}}
         {#
          # Render field description inside your form
          #}
         {% if form.vars.attr['data-description'] %}
             <div class="form-description">
                 {{ form.vars.attr['data-description']|markdown }}
             </div>
         {% endif %}
         {{- form_widget(form) -}}
     </div>
 {%- endblock form_row %}

 {% block recaptcha_widget -%}
     <div class="g-recaptcha" data-sitekey="{{ configs.publicKey }}"></div>
 {%- endblock recaptcha_widget %}

In your main view, add your form and use your custom form theme:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 {#
  # AwesomeTheme/Resources/views/form-blocks/customformblock.html.twig
  #}
 {% if form %}
     {% form_theme form '@AwesomeTheme/form.html.twig' %}
     {{ form_start(form) }}
     {{ form_widget(form) }}
     <div class="form-group">
         <button class="btn btn-primary" type="submit">{% trans %}send_form{% endtrans %}</button>
     </div>
     {{ form_end(form) }}
 {% else %}
     <p class="alert alert-warning">{% trans %}form_is_not_available{% endtrans %}</p>
 {% endif %}

Services

Roadiz is built upon Pimple dependency injection container. Thanks to this architecture, all Core and Backoffice services are available from any controller in your themes.

$this->get('nameOfService');
  • Doctrine entity manager: $this->get('em')
  • Twig rendering environment: $this->get('twig.environment')
  • Translator: $this->get('translator')
  • Url matcher: $this->get('urlMatcher')
  • Url generator: $this->get('urlGenerator')
  • Authorization checker: $this->get('securityAuthorizationChecker')
  • User token storage: $this->get('securityTokenStorage')
  • Firewall: $this->get('firewall')
  • Assets packages: $this->get('assetPackages')
Entity APIs

All these services are Doctrine repository wrappers meant to ease querying entities inside your themes and according to AuthorizationChecker. This will implicitely check if nodes or node-sources are published when you request them without bothering to insert the right criteria in your findBy calls.

Each of these implements AbstractApi methods getBy and getOneBy

Using Solr API

Solr is a really powerful tool to search over your node database with a clever plain-text search engine and the ability to highlight your criteria in the search results. Before going further, make sure that a Solr server is available and that it is well configured in your config.yml. You can use the bin/roadiz solr:check command to verify and then bin/roadiz solr:reindex command to force synchronizing your node database with Solr index.

You can use the solr.search.nodeSource service and its two methods to get node-sources from a search query.

Simple search results

$this->get('solr.search.nodeSource')->search() method will return an array of NodesSources

$criteria = [];
$results = $this->get('solr.search.nodeSource')
                ->search(
                    $request->get('q'), # Use ?q query parameter to search with
                    $criteria, # a simple criteria array to filter search results
                    10, # result count
                    true # Search in tags too
                );

foreach ($results as $nodeSource) {
    # NodesSources object
    echo $nodeSource->getTitle();
}
Search results with highlighting

$this->get('solr.search.nodeSource')->searchWithHighlight() method will return an array of array with a simple structure: nodeSource for the NodesSources object and highlighting for the html data with highlighted text wrapped in span.solr-highlight html tag.

$criteria = [];
$results = $this->get('solr.search.nodeSource')
                ->searchWithHighlight(
                    $request->get('q'), # Use ?q query parameter to search with
                    $criteria, # a simple criteria array to filter search results
                    10, # result count
                    true # Search in tags too
                );

foreach ($results as $result) {
    # NodesSources object
    $nodeSource = $result['nodeSource'];
    # String object (HTML)
    $hightlight = $result['highlighting'];
}

Case studies

Some step-to-step guides to work with Roadiz.

Download a website on my computer to work with Vagrant

This case study is meant to get a fresh development environment from an existing Roadiz Source edition website and theme. Following code snippets are using some variables data, in theses examples I’ll use:

  • MYUSER as the MySQL database user.
  • MYPASSWORD as the MySQL database user password.
  • MYDATABASE as the MySQL database name.
  • ~/Documents/Websites as the working directory on your own computer.
  • database-YYYY-mm-dd.sql is the mysql dump file name, replace YYYY-mm-dd with the current date.
  • mysuperwebsite is your website root folder.
  • git@github.com:johndoe/SuperTheme.git is an example Github repository for your theme.
  • SuperTheme is an example theme name and folder.
On the production server:
  1. Generate a database dump on your production server.
mysqldump -uMYUSER -pMYPASSWORD MYDATABASE > database-YYYY-mm-dd.sql

Then download it on your computer. You can also use phpmyadmin web tool to export your database tables. Make sure to disable foreign key verification and add the DROP IF EXISTS directive on phpmyadmin export form.

On your computer:
  1. Clone Roadiz on your favorite folder, choose well between master or develop branch if you want the stable version or the latest features.
cd ~/Documents/Websites;
# Here I choose the develop branch, because I’m warrior
git clone -b develop https://github.com/roadiz/roadiz.git mysuperwebsite;
  1. Clone your website theme in Roadiz themes/ folder, choose well your branch too. If you already have a develop branch, clone with -b develop option.
cd ~/Documents/Websites/mysuperwebsite/themes;
# My theme already has a develop branch so…
git clone -b develop git@github.com:johndoe/SuperTheme.git SuperTheme;
  1. [Optional] Initialize git-flow on the theme. You should always work on develop. Master branch is only for releases. If you don’t have git-flow on your computer, you can find some help on the official documentation.
cd ~/Documents/Websites/mysuperwebsite/themes/SuperTheme;
# You must fetch every available branches before initializing git flow
git checkout master;
git checkout develop;
git flow init;
# Follow instructions
# Git flow should checkout on develop branch for you
  1. Install Roadiz’ Composer dependencies (after cloning the theme to be sure that all composer dependencies are loaded)
cd ~/Documents/Websites/mysuperwebsite;
composer install --no-dev;
  1. Launch your Vagrant environment. Do not to automatically provision your VM if you want to choose what tool to install.
vagrant up --no-provision;
# ... lots of lines, bla bla bla

Choose tools to install on your VM, roadiz provisioner is mandatory… obviously, devtools provisioner will install Composer, Node.js, Grunt and Bower commands. If you have lots of website on your computer, it’s better to install these tools directly on your host machine, they will be more effective than on the VM. And you will be able to take advantage of Composer and NPM cache between your dev websites.

# Everything
vagrant provision --provision-with roadiz,phpmyadmin,mailcatcher,solr,devtools
# OR on a dev computer
vagrant provision --provision-with roadiz,phpmyadmin,mailcatcher,solr
  1. Import your database dump. First, you’ll need to copy it into your Roadiz website to make it available within your Vagrant VM. Then import it in your VM using the mysql tool.
mv ~/Downloads/database-YYYY-mm-dd.sql ~/Documents/Websites/mysuperwebsite/database-YYYY-mm-dd.sql;
cd ~/Documents/Websites/mysuperwebsite;
# Enter your VM
vagrant ssh;
# Your website is located in /var/www folder
cd /var/www;
mysql -uroadiz -proadiz roadiz < database-YYYY-mm-dd.sql;
# Exit your VM
exit;
  1. Update your conf/config.yml file to fill in your mysql credentials.
cd ~/Documents/Websites/mysuperwebsite;
# composer should have create a starter config file for you
subl conf/config.yml; # If you work SublimeText
  1. Use the bin/roadiz generate:nsentities to regenerate Doctrine entities existing in database but not as files.
cd ~/Documents/Websites/mysuperwebsite;
vagrant ssh;
cd /var/www;
bin/roadiz generate:nsentities;
# You may have to check database schema if your production website is not up to
# date with latest Roadiz
bin/roadiz orm:schema-tool:update --dump-sql --force;
  1. Download your production documents to your dev VM. You don’t have to do this within your VM.
cd ~/Documents/Websites/mysuperwebsite/files;
rsync -avcz -e "ssh -p 22" myuser@superwebsite.com:~/path/to/roadiz/files/ ./
# do not forget ending slashes in both paths.
  1. If you are using a Vagrant VM you have to add your IP address to the dev.php file to authorize your host computer to use the development environment.

11. Connect to http://localhost:8080/dev.php to begin. Every outgoing emails should be catched by Mailcatcher. You can see them at address http://localhost:1080.

Contributing

If you want to contribute to Roadiz project by reporting issues or hacking code, let us thank you! You are awesome!

Reporting issues

When you encounter an issue with Roadiz we would love to hear about it. Because thanks to you, we can make the most awesome and stable CMS! If you submit a bug report please include all informations available to you, here are some things you can do:

  • Try to simplify the things you are doing until getting a minimal set of actions reproducing the problem.
  • Do not forget to join a screenshot or a trace of your error.
Running tests

If you developed a new feature or simply want to try out an installation of Roadiz you can run unit-tests. For this you will need to install the testing dependencies, this can easily be done using:

composer update --dev

You have to run unit-tests on a dedicated database not to lose any existing Roadiz website. You can create a conf/config_test.yml YAML configuration which will be read only for this environment. Then, wire this configuration to a blank database. Unit-tests can be launched by the following command:

bin/phpunit -v --bootstrap=tests/bootstrap.php tests/

If your are writing a feature, don’t forget to write a unit test for it. You can find some example in the folder tests. In Roadiz, there are 4 types of tests:

  • Standard tests which must extend \PHPUnit_Framework_TestCase. These tests should only test simple logic methods and classes as they won’t require Roadiz kernel to boot up.
  • Kernel dependent tests which must extend RZRoadizTestsKernelDependentCase`. These tests should only test logic classes and methods inside Roadiz kernel without any database concern.
  • Schema dependent tests which must extend RZ\Roadiz\Tests\SchemaDependentCase. These tests should only test low level database methods and classes without relying on node-types or translations. Use this type of testing if you want to test Roadiz entities and repositories methods except for Nodes and NodeTypes.
  • DefaultTheme dependent tests which must extend RZ\Roadiz\Tests\DefaultThemeDependentCase. These tests rely on a complete Roadiz installation with existing node-types and translation. They are longer to prepare as PHPUnit must install a fresh Roadiz with DefaultTheme at each case.

Note

Each SchemaDependentCase and DefaultThemeDependentCase will provision a fresh Roadiz database then drop it. Make sure to use a dedicated database. If you execute unit-tests from an existing Roadiz website, you’ll have to run bin/roadiz generate:nsentities at the end of your testing session to build your NodesSources classes again (every environment share the same gen-src folder).

Coding style

The code you contributed to the project should respect the guidelines defined in PHP PSR2 standard. If you install the requirements for devs by the command composer update --dev, you can use phpcs to check your code. You can copy and paste the following command-lines to check easily:

bin/phpcs --report=full --report-file=./report.txt \
            --extensions=php --warning-severity=0 \
            --standard=PSR2 \
            --ignore=*/node_modules/*,*/.AppleDouble,*/vendor/*,*/cache/*,*/gen-src/*,*/tests/*,*/bin/* \
            -p ./

Or you can use phpcbf to automatically fix code style issues.

bin/phpcbf --report=full --report-file=./report.txt \
            --extensions=php --warning-severity=0 \
            --standard=PSR2 \
            --ignore="*/node_modules/*,*/.AppleDouble,*/vendor/*,*/cache/*,*/gen-src/*,*/tests/*,*/bin/*" \
            -p ./

Please take those rules into account, we aim to have a clean codebase. A coherent codestyle will contribute to Roadiz stability. Your code will be checked when we will be considering your pull requests.

Troubleshooting

Empty caches manually for an environment

If you experience errors only on a dedicated environment such as prod, dev or install, it means that cache is not fresh for these environments. As a first try, you should always call bin/roadiz cache:clear -e prod; (replace prod by your environment) in command line.

If you still get errors from a specific env and you are using an OPcode cache or var cache (APC, XCache), call clear_cache.php entry point from your browser or execute curl http://localhost/clear_cache.php from your command line.

Problem with entities and Doctrine cache?

After each Roadiz upgrade you should always upgrade your node-sources entity classes and upgrade database schema.

bin/roadiz generate:nsentities;
bin/roadiz orm:schema-tool:update --dump-sql --force;
bin/roadiz cache:clear -e prod;

If you are using a OPCode var cache like APC, XCache, you should purge it as Roadiz stores doctrine configuration there for better performances, call clear_cache.php entry point from your browser or curl http://localhost/clear_cache.php from your command line.

Running Roadiz behind a reverse proxy

If you are behind a reverse-proxy like Varnish or Nginx proxy on a Docker environment, IP addresses, domain name and proto (https/http) could not be correctly set. So you will have to tell Roadiz to trust your proxy in order to use X_FORWARDED_* env vars.

Add this line to your index.php and preview.php files after $request = Request::createFromGlobals(); line.

$request = Request::createFromGlobals(); // Existing line to get request
// Trust incoming request IP as your reverse proxy
Request::setTrustedProxies(array($request->server->get('REMOTE_ADDR')));
Find help before posting an issue on Github

Join us on Gitter: https://gitter.im/roadiz/roadiz

Extension documentation

Extension system

Extending Roadiz

It is time to see how to extend Roadiz! As you read in Roadiz Philosophy part, we won’t ship “plugin” or “module” like others CMS. But you will be able to add of lot of features using the part that really matters: Themes!

Theme powered CMS

We coded the Theme system to be the core of your extending experience. You don’t need to change something else than your theme. So you can use a versioning tool or backup easily your work which will be only at one place.

You can add new entities. If so, don’t forget to add your Entities namespace in Roadiz config file. With theses additional entities, you maybe will need to create a back-office entry to manage them. It’s easy! Let’s see how to.

Create your own database entities

You can create a theme with your own entities. Just add your Entities folder to the global configuration file (app/conf/config.yml).

entities:
    - ../src/Roadiz/Core/Entities
    - ../src/Roadiz/Core/AbstractEntities
    - gen-src/GeneratedNodeSources
    - ../themes/MyTheme/Entities

Verify if everything is OK by checking migrations:

bin/roadiz orm:schema-tool:update --dump-sql;

If you see your entities being created and no system database erased, just apply your migration with --force. If Doctrine send some error, you probably need to clear metadata cache:

bin/roadiz cache:clear -e prod;

Clearing cache from command line will not empty op-code cache. Be sure to call clear_cache.php entry point to actually clear PHP-FPM related caches. You can use an curl command if your website is accessible from localhost:

curl http://localhost/clear_cache.php;
Add back-office entry

At first, create a controller into your theme folder, for example themes/MyTheme/AdminControllers/AdminController.

Example:

namespace Themes\MyTheme\AdminControllers;

use Themes\Rozier\RozierApp;
use Themes\MyTheme\MyThemeApp;
use Symfony\Component\HttpFoundation\Request;

class AdminController extends RozierApp
{
    public function listAction(
        Request $request
    ) {
        return $this->render(
            'admin/test.html.twig',
            $this->assignation,
            null,
            MyThemeApp::getThemeDir()
        );
    }
}

If you look at this exemple you can see the class extends RozierApp not your MyThemeApp class! This will enable you to “inject” your code into Rozier Back-stage DOM and Style. But be careful to use MyThemeApp::getThemeDir() as your template namespace.

Now let’s have a look to your twig template file admin/test.html.twig.

{% if not head.ajax %}{% set baseTemplate = '@Rozier/base.html.twig' %}{% else %}{% set baseTemplate = '@Rozier/ajaxBase.html.twig' %}{% endif %}{% extends baseTemplate %}

{% block customStyles %}
<style>
    /* Custom styles here */
</style>
{% endblock %}

{% block customScripts %}
<script>
    /* Custom Stripts here */
</script>
{% endblock %}

{% block content %}
<section class="content-global add-test">
    <header class="content-header header-test header-test-edit">
        <h1 class="content-title test-add-title">{% trans %}Test admin{% endtrans %}</h1>
    </header>

    <article class="content content-test">
        <p>This page is created from MyTheme to show you how to extend backoffice features.</p>
    </article>
</section>
{% endblock %}

The first line is for inheriting from Rozier base template, you can notice that we explicitely choose @Rozier namespace.

The two next blocks are made for you to add some CSS or Javascript. For CSS, the block customStyle can be use to link an external file with a <link> tag, the path must be something like that {{ request.baseUrl ~ "/themes/MyTheme/static/css/customstyle.css" }}, or add directly some CSS with “<style>” tag. For JS, the block customScripts work as is, just link an external JS file or write your <script> tag.

Then create your own content, do not hesitate to give a look at Rozier back-stage theme Twig files to use the right DOM structure. For simple features, you wouldn’t have to extend JS nor CSS if you follow the same HTML coding style.

Linking things together

Add the route in the theme route.yml file.

In this case the route will be:

adminTestPage:
    # Setting your path behind rz-admin will activate Firewall
    path: /rz-admin/test
    defaults:
        _controller: Themes\MyTheme\AdminControllers\AdminController::listAction
Inject your own entries in back-stage

The last thing to do is to add your new admin entry in the back-office menu.

Go to your MyThemeApp.php main class and override setupDependencyInjection method, or create it if it doesn’t exist.

public static function setupDependencyInjection(Container $container)
{
    parent::setupDependencyInjection($container);

    $container->extend('backoffice.entries', function (array $entries, $c) {

        /*
         * Add a customAdmin entry in your Backoffice
         */
        $entries['customAdmin'] = [
            'name' => 'customAdmin',
            'path' => $c['urlGenerator']->generate('adminTestPage'),
            'icon' => 'uk-icon-cube',
            'roles' => null,
            'subentries' => null
        ];

        return $entries;
    });
}

Do not forget to add use Pimple\Container; in your file header.

setupDependencyInjection method is called statically at boot time when Roadiz’s kernel is running all available Themes to setup services. In the code above, you will extend backoffice.entries service which define every buttons available in Rozier backstage main-menu.

If you want to have a category and sub-entries, just change the path at null value and create your subentries array as described in the next example:

$entries['customAdmin'] = [
    'name' => 'customAdmin',
    'path' => null,
    'icon' => 'uk-icon-cube',
    'roles' => null,
    'subentries' => [
        'customAdminPage' => [
            'name' => 'customAdmin page',
            'path' => $c['urlGenerator']->generate('adminTestPage'),
            'icon' => 'uk-icon-cube',
            'roles' => null
        ],
        // Add others if you want
    ]
];

You can restrict buttons to users with specific roles. Just replace 'roles' => null with 'roles' => array('ROLE_ACCESS_NODES'). You can even create your own roles to take full power of Roadiz extension system.

Warning

Adding roles in backoffice.entries service will only restrict buttons display in Rozier backstage interface. To really protect your controllers from unwanted users add $this->validateAccessForRole('ROLE_ACCESS_MY_FEATURE'); at the first line of your back-ofice controller‘s actions. This will kick non-granted users from your custom back-office parts. Give a look at Rozier theme controllers to see how we use it.

Events

Roadiz node system implements several events. So you will be able to create and inject your own event subscribers inside Roadiz dispatcher.

To understand how the event dispatcher works, you should read the Symfony documentation at before.

Nodes events

RZ\Roadiz\Core\Events\NodeEvents

  • node.created: NodeEvents::NODE_CREATED
  • node.updated: NodeEvents::NODE_UPDATED
  • node.deleted: NodeEvents::NODE_DELETED
  • node.undeleted: NodeEvents::NODE_UNDELETED
  • node.tagged: NodeEvents::NODE_TAGGED This event is triggered for tag and un-tag action.
  • node.visibilityChanged: NodeEvents::NODE_VISIBILITY_CHANGED This event is triggered each time a node becomes visible or unvisible.
  • node.statusChanged: NodeEvents::NODE_STATUS_CHANGED This event is triggered each time a node status changes.

Every node events methods will accept a RZ\Roadiz\Core\Events\FilterNodeEvent object as argument. This object contains the current Node entity. You will get it using $event->getNode().

NodesSources events

RZ\Roadiz\Core\Events\NodesSourcesEvents

  • nodeSource.created: NodesSourcesEvents::NODE_SOURCE_CREATED
  • nodeSource.updated: NodesSourcesEvents::NODE_SOURCE_UPDATED
  • nodeSource.deleted: NodesSourcesEvents::NODE_SOURCE_DELETED

Every node-source events methods will accept a RZ\Roadiz\Core\Events\FilterNodesSourcesEvent object as argument. This object contains the current NodesSources entity. You will get it using $event->getNodeSource().

Note

You will find a simple subscriber example in Roadiz back-office theme which is called Themes\Rozier\Events\SolariumSubscriber. This subscriber is useful to update or delete your Solr index documents against your node-source database.

Tags events

RZ\Roadiz\Core\Events\TagEvents

  • tag.created: TagEvents::TAG_CREATED
  • tag.updated: TagEvents::TAG_UPDATED
  • tag.deleted: TagEvents::TAG_DELETED

Every tag events methods will accept a RZ\Roadiz\Core\Events\FilterTagEvent object as argument. This object contains the current Tag entity. You will get it using $event->getTag().

Translations events

RZ\Roadiz\Core\Events\TranslationEvents

  • translation.created: TranslationEvents::TRANSLATION_CREATED
  • translation.updated: TranslationEvents::TRANSLATION_UPDATED
  • translation.deleted: TranslationEvents::TRANSLATION_DELETED

Every tag events methods will accept a RZ\Roadiz\Core\Events\FilterTranslationEvent object as argument. This object contains the current Translation entity. You will get it using $event->getTranslation().

UrlAlias events

RZ\Roadiz\Core\Events\UrlAliasEvents

  • urlAlias.created: UrlAliasEvents::URL_ALIAS_CREATED
  • urlAlias.updated: UrlAliasEvents::URL_ALIAS_UPDATED
  • urlAlias.deleted: UrlAliasEvents::URL_ALIAS_UPDATED

Every tag events methods will accept a RZ\Roadiz\Core\Events\FilterUrlAliasEvent object as argument. This object contains the current UrlAlias entity. You will get it using $event->getUrlAlias().