Getting started: Baby steps to create a first web site with the elasticms suite

Prerequisites

  • Having a web server with PHP support, a MySQL server and an elasticsearch cluster (with at least 2 nodes) proprely installed and configured (see system requirements)
  • Having a git client installed
  • Having composer installed

Install elasticms, the contant management application

Retrieve the last version of elasticms from GitHub with the following commands:

cd /var/lib/htdocs
git clone https://github.com/ems-project/elasticms.git admin
cd admin
composer install

Then you can edit the .env file in order to update some environment variables such as the elasticsearch cluster and the RDBMS server ones:

... 
APP_ENV=prod
ELASTICSEARCH_VERSION="5.6" #At least specify the two first number of your version of elasticsearch
ELASTICSEARCH_CLUSTER='["http://localhost:9200"]' #In a production environment it's really better to have a dedicated server per cluster node
DB_DRIVER='mysql'
DB_USER='demo'
DB_PASSWORD='demo'
DB_PORT='3306'
DB_NAME='demo'
INSTANCE_ID=demo_
STORAGE_PATH='/var/lib/ems/assets'
APP_SECRET=ThisIsNotASecret
..

It's time to initiate the database:

php bin/console cache:clear
php bin/console doctrine:migrations:migrate

check that everything is working at http://localhost/admin/public/index.php/status

and create your first super-admin user:

php bin/console fos:user:create --super-admin

You should now be able to login in you brand new elasticms at http://localhost/admin/public/index.php

The elasticms interface

This interface is based on bootstrap v4 and AminLTE:

  1. Instance name
  2. Pendding notification icon
  3. Activty icon
  4. User menu
  5. User avatar (configurable via Gravatar)
  6. System status (green, yellow or red)
  7. User name
  8. Quick search
  9. Menus
    1. The user menu (hidden as empty at this stage) which will contain user's saved searchs
    2. The content type menu (hidden at this stage as non content tape have been defined yet) which list all content type available to the user
    3. The Publisher menu with the compare environment tool
    4. The administrator menu
      1. Content types management tools
      2. Environments management tools
      3. Users access et roles
      4. WYSIWYG: CK Editor configuration profiles
      5. Search configuration tools
      6. I18N: defined internal translation keys
      7. Jobs: Launch and monitor Symfony commands from the web interface
      8. Analyzers: define custom elasticsearch analyzers
      9. Filters: define custom elasticsearch filters
    5. The "other" menu
      1. Link to the system status page
      2. Link to the documentation page
  10. Page content
  11. LInk to this website
  12. Information about the versions of the elasticms core bundle et the version of Symfony

Environments

Before anything you should now define environments. To understand what an environment is we should first talk about documents. The goal of a CMS is to publish content on the web. Right? Website content can have multiple types: informative pages, publications (pdf, ...), news, menu entries and so forth: the content types. Each encoded informative page, news, publication for the differents content types are called documents. And, off course, the content evolve with the time, and documents are updated time to time. Each time that a document is updated a new draft revision of the document is created, once finalized, it wont be possible to update the corresponding revision anymore. In other words, for a document every updates (finalized draft) are stacked, all those update "layers" are called revisions. As author you don't want to do blind updates and you prefers to review your updates in a preview version of the web site. Time to time you will also need to ask some external review within a staging website. With elasticms all those websites directly displays the content published in their corresponding environments: "preview", "staging", "live". So you can define, for a document, which specific revision (and only one) is published in which environmnent. And you can defined as many environments as you want, i.e. each time that a software is released or a specific environment for the tranlators. In this tutorial we suggest you to create the 5 following environments:

  • preview: to allow authors to review their work
  • staging: to ask expert review
  • live: the environement used by "the real" version of the website
  • template: an environement dedicated to the evolutions of the website
  • request: an environment to isolate form submitted from the website itself

To create an environment select the "Environments" entry in the admin. And click on the "Add environment" button. Fill the form with the name of the environment,  keep it lower case starting by a character containing only characters, numbers or underscores. Pick a color such as (but it's not really important, it should be a convention that you share between all your elasticms):

  • preview: aqua
  • staging: light blue
  • live: blue 
  • template: orange
  • request: purple

Click on the "Create"button. Redo this for all environments. At the end your also able to sort them by clicking on the "Reorder environments"button (by drag-n-drop).

First content types

It's time to create our first content type in order to manage website content, select the "Content Types" entry in the admin. Then click on the "Add content type" button and fill the form with this values:

  • Machine name: template (all lower cases, it will be the name of the content type in elasticsearch)
  • Singular name: Template (no contraint on this field)
  • Plural name: Templates (no contraint on this field)
  • Default environment: staging
  • Import from JSON: this template.json file

Click on the "Create" button. Ensure thath the "Silently publish draft and auto-save into the default environment" checkbox is unchecked and scroll down at the end of the page, click on the "Save and Close" button. Then, for the template content type you can  do the "Update mapping" and "Activate" actions.

The content type template is design to handle the website layouts. The HTML ones but not only (RSS, XML, ...). This content type is meant for web designers and developpers, and so in order to avoid those guys to disturb the authors, publisher, ... the default environment is set to template. A default environment is the environment where the last finalized revision of a document is always published!

Do the same for this content type:

  • Machine name: route (all lower cases, it will be the name of the content type in elasticsearch)
  • Singular name: Route (no contraint on this field)
  • Plural name: Routes (no contraint on this field)
  • Default environment: staging
  • Import from JSON: this route.json file

This content type is also always published by default in the template environment as this content type will defined all website's url and routing mecanismes.

First documents

Homepage template

In the "Content types" menu open the "Templates"submenu and click on the "New Template" link. The goal here is to create a Symfony twig for the website's homepage. Fill the form with those value:

  • Key: homepage.html.twig

Body:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>Hello, world!</title>
  </head>
  <body>
    <h1>Hello, world!</h1>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

This is the Getting Started Bootstrap Hello World example, it's your first elasticms Twig piece of code. At this time it a perfectly regular HTML code. The key field is the name of the template. You should see this field as the file name of the Body field and follow the Twig/Symfony best pratices, without specifying any namespace: [{optional path}/]{template name (lower case with underscore)}.{extention of the resutl of the twig rendering}.twig.

About the code editor (ACE), you can list the helpfull shortcuts with the Ctrl-Alt-H (Mac: Cmd-Alt-H) shortcut (i.e. Toggle fullscreen: F11 (Mac:Esc)). 

When it's done click on "Finalize draft". This action publish the document in the template environment, where the "Save draft" button is saving the document as work-in-progress (WIP). Just like Office Word, elasticms autosave the document on every key stroke. So if you forgot to save your WIP progress, when you came back later editing the document elasticms will warn you that your are editing an auto-save. To go back to the last saved version of the draft click ont the "Cancel (last modifications?)" button.

Homepage route

In the "Content types" menu open the "Routes"submenu and click on the "New Route" link. The goal here is to create a Symfony route for the website's homepage. Fill the form with those value:

  • Name: emsch_homepage (the name of the route within the Symfony routing)
  • Static Template: search the Homepage template previously created by typing "homepage" and select it in the dropdown menu of the field

Body:

{
    "path": "/{_locale}"
}

Here the body is a JSON editor. You can specify, in a JSON format, Symfony routes just like specifed in the Symfony route doc. With this document we defined a route asisciating the url  "/" with the homepage.html.twig template.

The skeleton enters on stage

Retrieve the last version of the elasticms skeleton from GitHub with the following commands:

cd /var/lib/htdocs
git clone https://github.com/ems-project/website-skeleton.git site
cd site
composer install

Then you can edit the .env file in order to update some environment variables such as the elasticms url:

... 
APP_ENV=dev
EMSCH_ELASTICSEARCH_CLUSTER='["http://localhost:9200"]' #In a production environment it's really better to have a dedicated server per cluster node
EMSCH_ENVS='{"template": {"regex": "/^.*/", "index": "template"}}'
EMSCH_INSTANCE_ID=website_
STORAGE_PATH='/var/lib/ems/assets'
EMSCH_BACKEND_URL='http://localhost/admin/public/index.php'
DEFAULT_LOCAL='en'
EMSCH_LOCALES='["en","nl", "fr"]'
EMSCH_TEMPLATES='{"template": {"name": "key","code": "body"}}'
EMSCH_TRANSLATION_TYPE=label
EMSCH_REDIRECT_TYPE=redirect
EMSCH_ROUTE_TYPE=route
...

You should now be able to see the Hello Page at http://localhost/site/public/index.php/en

Twig: Extends template

Twig has a very powerful extension mechanism. It's better to structure your templates from the beginning. We recommend you to first create a variables.twig template that will centralize global Twig variables:

{%- spaceless -%}
    {% set locale = app.request.locale %}
{%- endspaceless -%}
{%- block request -%}
{%- endblock request -%}

This template will be the parent of all other base templates. Such as the base.html.twig template:

{% extends '@EMSCH/template/variables.twig' %}

{% block request %}
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title>{% block title %}Hello, world!{% endblock title %}</title>
  </head>
{% block body %}
  <body>
    <h1>Hello, world!</h1>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
{% endblock body %}
{% endblock request %}

Then you can go back to the homepage.html.twig template and extends the base.html.twig template:

{% extends '@EMSCH/template/base.html.twig' %}

{%- block title -%}
    foobar
{%- endblock title -%}

{%- block body -%}
    <h1>foobar</h1>
{%- endblock body -%}

Translation content type

Usually a website supports multiple languages. Even if it's not the case it's a always a good idea to manage templates text as translation. Indeed the webmaster et the web integrator may not be the same person. Select the "Content Types" entry in the admin. Then click on the "Add content type" button and fill the form with this values:

  • Machine name: label (all lower cases, it will be the name of the content type in elasticsearch)
  • Singular name: Translation (no contraint on this field)
  • Plural name: Translations (no contraint on this field)
  • Default environment: staging (if the translation are manage by the author it can be a good idea to specify preview as default environment)
  • Import from JSON: this label.json file

Click on the "Create" button. Click on the "Save and Close" button. Then, for the label content type you can do the "Update mapping" and "Activate" actions. Let's create our first translation key. In the "Content types" menu open the "Translations"submenu and click on the "New Translation" link. Fill the form with those value:

  • Key: site.name
  • Label En: Demo website

Go back to the base.html.twig template ans set the template's translation domain:

{% extends '@EMSCH/template/variables.twig' %}
{% trans_default_domain trans_default_domain %}

{% block request %}
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %}Hello, world!{% endblock title %} | {{ 'site.name'|trans }}</title>
  </head>
&  <body>
{% block body %}
    <h1>Hello, world!</h1>
{% endblock body %}

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

The Skeleton is defining a trans_default_domain variable defining the domain where the translations were loaded. So you shoudl integrate that line in every template:

{% trans_default_domain trans_default_domain %}

Do not forgot to pass that variable to includes.

Page content type

Now that we are able to generate HTML template, to connect them via a route and define translation keys it's now time to create web content for authors. Select the "Content Types" entry in the admin. Then click on the "Add content type" button and fill the form with this values:

  • Machine name: page (all lower cases, it will be the name of the content type in elasticsearch)
  • Singular name: Page (no contraint on this field)
  • Plural name: Pages (no contraint on this field)
  • Default environment: preview
  • Import from JSON: this page.json file

Click on the "Create" button. Click on the "Save and Close" button. Then, for the page content type you can do the "Update mapping" and "Activate" actions. Let's create our first page document. In the "Content types" menu open the "Pages"submenu and click on the "New Page" link. Fill the form with those value:

  • Title En: Homepage
  • Body En: Welcom on this demo website

Click on "Finalize draft". Until now all content (template, route and label) have been published in the template environment by the finalyze action. As the default environment of this content type is 'preview' to see it in template you need to publish in 'template'via the the "Publish in" menu.

Go back to the emsch_homepage route, In the "Content types" menu open the Routes submenu and select the "Search Routes" link. You should see the emsch_homepage route. Click on the "New draft"action button:

  • Update the name field with 'emsch_page'

Config:

{
    "path": "/{slug}",
    "defaults": {
        "_locale": "en",
        "slug": "homepage"
    }
}

Query:

{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "slug_%_locale%": {
                            "value": "%slug%"
                        }
                    }
                },
                {
                    "term": {
                        "_contenttype": {
                            "value": "page"
                        }
                    }
                }
            ]
        }
    },
    "size": 1
}

You see here that we are using the route parameters to defined an elasticsearch query. By defining defaults paramters to the route we do not need the emsch_route any more. If you go check the route content type definition you'll see that there is a computed slug_en field which is the title_en url encoded (see the url_encode filter).

See the page content

Go bach the homepage.htm.twig, create a new draft and edit those fields:

  • Key: page.html.twig

Body:

{% extends '@EMSCH/template/base.html.twig' %}

{%- block title -%}
    {{ source|locale_attr('title_') }}
{%- endblock title -%}

{%- block body -%}
    <h1>{{ source|locale_attr('title_') }}</h1>
    <div>
        {{ source|locale_attr('body_')|emsch_routing }}
    </div>
{%- endblock body -%}

The twig local_attr filter is looking for a field where the parameter is concatenated to the locale: i.e. 'title_' + 'en' → 'title_en'. The  emsch_routing filter is resolving all internal links in a html content. That filter should be always used to display elasticms's WYSIWYG fiels.

Create a link from elasticms to the Skeleton

In the Admin menu select the "Environments" link. Then choose the "Edit" button for the "template" environment. Update the Base URL field with the base url of the websie: http://localhost/site/public/index.php

In the Admin menu select the "Content Types" link. Then choose the "Templates" button for the "Page" content type. Click on the "Add template" and fill the form with:

  • Preview: Preview En
  • Icon: Eye
  • Active: checked
  • Render option: External link

Body:

{% spaceless %}
{{ environment.baseUrl }}/{{ source.slug_en }}
{% endspaceless %}

Now, for every page publised in template, you'll find in the "Action in template" a "Preview En" link.

Create a link from Skeleton to elasticms

Go to the page.html.twig and create a new draft. Update the following line of the body:

    <h1 {{ emsch_admin_menu(emsLink) }}>{{ source|locale_attr('title_') }}</h1>

And add those lignes at the end of the base.html.twig (just before the </body>)

//TODO