Is this article helpful?
 
(optional) let us know why:

Introducing template plugins

On this page, you will find an overview of relevant information about template plugins. In the first chapter, we will help you set up an IDE with all bits and bobs required to develop template plugins. In the second chapter, you will find a short description for each plugin feature that you can use to create your own template plugins. We will differentiate between features that define the core structure of the plugin usually found in the src folder and features for the design found in the resources folder. The programming language PHP 7 is mainly used for creating the files in the src folder. Technologies used in context with the resources folder are:

Further reading

Setting up an IDE for template plugins

We recommend using PhpStorm for developing Ceres. PhpStorm is a comprehensive IDE which supports all PHP language features. PhpStorm also includes a wide range of leading edge front-end technologies, such as CSS, Sass, Javascript, as well as Twig syntax highlighting. PhpStorm supports ESLint, a linting utility plugin for JavaScript.

Installing Node.js

For JavaScript packages, such as Gulp, we recommend installing Node.js with its package manager npm.

  1. Download and install Node.js.
    → We recommend using Node.js v6.9.1 LTS.
  2. To use Node.js properly, a command line tool is required. PhpStorm comes with a built-in terminal, but you can use any other command line tool.

Installing PhpStorm

Next, install PhpStorm and the Twig language plugin.

  1. Download and install PhpStorm.
    → We recommend using PhpStorm v2016.2.2.
  2. Open PhpStorm.
    → The Welcome to PhpStorm window will open.
  3. Click on Configure » Plugins.
    → The Plugins window will open.
  4. Click on Install JetBrains plugin....
    → The Browse JetBrains Plugins will open.
  5. Install the Twig Support plugin.

Configuring PhpStorm for Ceres

Clone or download the Ceres and IO plugins, e.g. with GitHub Desktop or SourceTree.

  1. Open PhpStorm.
    → The Welcome to PhpStorm window will open.
  2. Click on Open.
    → The Open File or Project window will open.
  3. Select your Ceres folder and click OK.
  4. Open the terminal and go to the Ceres project folder.
    → When using the built-in terminal of PhpStorm, you are already in the right project folder.
  5. Run npm install in the terminal.
    → All dependencies that are defined in the package.json file are installed.
  6. Click on PhpStorm » Preferences... in the menu bar.
  7. Go to Languages & Frameworks » JavaScript » Code Quality Tools » ESLint.
  8. Enable ESLint for the Ceres project.

Integrating the plugin interface into PhpStorm

Learn how to integrate the plentymarkets plugin interface into your IDE to support auto-completion.

  1. Clone or download the plentymarkets plugin interface.
  2. Select the stable7 branch of the repository if you have a stable plentymarkets 7 system.
    → If you work with a beta system, select the beta7 branch.
  3. Open PhpStorm.
    → The Welcome to PhpStorm window will open.
  4. Open the Ceres project.
  5. In the project tree area on the left, click on External Libraries.
    → A new window will open.
  6. Under Development environment, select the PHP language level 7.
  7. Click on Add in the Include path area.
  8. Select the plentymarkets plugin interface folder.
  9. Click on OK.
    → The plugin interface will be added as an external library.
  10. Click on Apply and then on OK.
    → The plugin interface is now a new source under External Libraries » PHP.

Gulp tasks for Ceres

Gulp is a streaming build system that helps you automating your JavaScript tasks. The following Gulp tasks are available in the Ceres project.

Task Description
watch:js includes the build:vendor task. This task monitors all .js files in the resources/js/src/app folder and builds .js files in the resources/js/dist folder in case of changes.
watch:sass monitors all .scss files in the resources/scss folder and builds .css files in the resources/css folder in case of changes.
build includes the build:bundle and build:sass-min tasks
build:bundle includes the build:app, build:vendor and build:lang tasks. This task builds the .js and .min.js files in the resources/js/dist folder.
build:app includes the build:lint task. This task builds -app.js files in the resources/js/dist folder.
build:lang builds the .js files in the resources/js/lang folder
build:vendor builds -vendor.js files in the resources/js/dist folder
build:lint executes ESLint and lints .js files in the resources/js/src folder
build:sass-min includes the build:sass task. This task builds .min.css files in the resources/css folder.
build:sass builds .css files in the resources/css folder

Core features

Let's discuss the core structure of a plugin based on the src folder of the plentymarkets IO plugin and the sub-folders and files contained in this folder.

API

The Api folder contains resources similar to controllers. The ApiResource.php is a class that extends a controller and enables self-defined REST calls with the related PHP methods. A list of response codes and functions for event registration are saved in ApiResponse.php. Specific REST calls, such as the update ( string $shippingProfileId ) function in the example below, are defined in files in the Resources sub-folder.

IO/src/Api/Resources/ShippingResource.php
<?php

namespace IO\Api\Resources;

use Symfony\Component\HttpFoundation\Response as BaseResponse;
use Plenty\Plugin\Http\Response;
use Plenty\Plugin\Http\Request;
use IO\Api\ApiResource;
use IO\Api\ApiResponse;
use IO\Api\ResponseCode;
use IO\Services\ShippingService;

/**
 * Class ShippingResource
 * @package IO\Api\Resources
 */
class ShippingResource extends ApiResource
{

    /**
     * @var ShippingService
     */
    private $shippingService;

    /**
     * ShippingResource constructor.
     * @param Request $request
     * @param ApiResponse $response
     * @param ShippingService $shippingService
     */
    public function __construct(Request $request, ApiResponse $response, ShippingService $shippingService)
    {
        parent::__construct($request, $response);
        $this->shippingService = $shippingService;
    }

    // Put/Patch
    /**
     * Set the shipping profile
     * @param string $shippingProfileId
     * @return BaseResponse
     */
    public function update(string $shippingProfileId):BaseResponse
    {
        $this->shippingService->setShippingProfileId((int)$shippingProfileId);
        return $this->response->create(ResponseCode::OK);
    }

}

Builder

The builder class helps you work with interfaces. ItemColumnBuilder.php, for example, builds an array of ItemDataLayer columns that can be requested with the search method. The fields for this array are defined in the related files in the Fields folder.

IO/src/Builder/Item/ItemColumnBuilder.php
<?php

namespace IO\Builder\Item;

use IO\Builder\Item\Fields\ItemBaseFields;

...

/**
 * Build array of ItemDataLayer columns to request from ItemDataLayerRepository::search
 */
class ItemColumnBuilder
{
    /**
     * @var array>
     */
    private $columnFields = [];
    /**
     * @var array>
     */
    private $columnParams = [];

    public function defaults():ItemColumnBuilder
    {
        return $this
            ->withItemBase([
                               ItemBaseFields::ID,
                               ItemBaseFields::RATING,
                               ItemBaseFields::RATING_COUNT,
                               ItemBaseFields::STORE_SPECIAL,
                               ItemBaseFields::PRODUCER,
                               ItemBaseFields::PRODUCING_COUNTRY_ID,
                               ItemBaseFields::CONDITION,
                               ItemBaseFields::AGE_RESTRICTION,
                               ItemBaseFields::CUSTOMS_TARIFF_NUMBER
                           ])
}

...

This array can be included in services, such as ItemService.php. Builders also help you validate your code through auto-completion.

IO/src/Services/ItemService.php
<?php

namespace IO\Services;

...

    public function getItems(array $itemIds):RecordList
    {
        $columns = $this->columnBuilder
            ->defaults()
            ->build();

            ...

    }

Constants

Constants, e.g. available languages or category types, are organised in the Constants folder.

Controllers

Controllers are the interface between the core and the design. The sub-folder Controllers contains the different controllers necessary to render Ceres. The file LoginController.php, for example, contains the showLogin function for rendering the login.twig template.

IO/src/Controllers/LoginController.php
<?php

namespace IO\Controllers;

use IO\Helper\TemplateContainer;

class LoginController extends LayoutController
{
    public function showLogin(): string
    {
        return $this->renderTemplate(
            "tpl.login",
            [
                "login" => ""
            ]
        );
    }
}

Extensions

The Extensions folder stores components that extend the functionality of Twig. Extensions can be operators, global variables, functions, etc. Twig also allows you to create your own filters. TwigIOExtension.php, for example, uses filters stored in the Filters sub-folder. NumberFormatFilter.php provides methods for formatting numbers and currencies that can be included in twig templates.

IO/src/Extensions/Filters/NumberFormatFilter.php
...

public function formatCurrency(float $value, string $currencyISO):string
{
    $locale            = 'de_DE';
    $useCurrencySymbol = true;

    $formatter = numfmt_create($locale, \NumberFormatter::CURRENCY);
    if(!$useCurrencySymbol)
    {
        $formatter->setTextAttribute(\NumberFormatter::CURRENCY_CODE, $currencyISO);
        $formatter->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $currencyISO);
    }

    if($this->config->get('IO.format.use_locale_currency_format') === "0")
    {
        $decimal_separator   = $this->config->get('IO.format.separator_decimal');
        $thousands_separator = $this->config->get('IO.format.separator_thousands');
        $formatter->setSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL, $decimal_separator);
        $formatter->setSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $thousands_separator);
    }
    return $formatter->format($value);
}

In your template, add this method using the pipe within the twig variable. In the example below, the item price will be formatted by adding the ISO code EUR to the price.

{{item.price | formatCurrency ("EUR")}}

Guards

The Guards folder contains guard classes. AuthGuard.php, for example, extends the AbstractGuard class and controls if the online store user is logged in. Guards are used for redirecting.

Helper

The Helper folder contains helper classes. TemplateContainer.php, for example, is a class that controls the data exchange between the Ceres plugin and the IO plugin.

Middlewares

Middlewares have multiple usages. On the one hand, they are used to obtain the current HTTP request via the before() method. This instance of the request is obtained before being processed by controllers.

On the other hand, the after() method allows you, for example, to replace the HTTP response with your own response. In this way, we can integrate the method showPageNotFound from the StaticPagesController to render the 404 page after all routes of all plugins are registered.

IO/src/Middlewares/Middleware.php
<?php // strict

namespace IO\Middlewares;

use IO\Controllers\StaticPagesController;
use Plenty\Plugin\Http\Request;
use Plenty\Plugin\Http\Response;

class Middleware extends \Plenty\Plugin\Middleware
{

    public function before(Request $request)
    {

    }

    public function after(Request $request, Response $response):Response
    {
        if ($response->content() == '') {
            /** @var StaticPagesController $controller */
            $controller = pluginApp(StaticPagesController::class);

            return $response->make(
                $controller->showPageNotFound()
            );
        }

        return $response;
    }
}

Providers

Two types of providers are used in plentymarkets plugins: service providers and route service providers.

Service Providers

Figuratively speaking, the service provider is the starting point of the plugin. Every plugin must have a service provider, which is needed to register the route service provider. All service providers extend the Plenty\Plugin\ServiceProvider class. In the case of Ceres, the service provider is also used to boot the design and add various extensions.

Route Service Providers

Routes are used to point URLs to controllers or anonymous functions that should be executed when a user accesses a given page. In line 20 of the example below, the function showLogin is executed when a user opens the /login page. This function is defined in LoginController.php.

IO/src/Providers/IORouteServiceProvider.php
<?php

namespace IO\Providers;


use Plenty\Plugin\RouteServiceProvider;
use Plenty\Plugin\Routing\Router;
use Plenty\Plugin\Routing\ApiRouter;
use Plenty\Plugin\Templates\Twig;

class IORouteServiceProvider extends RouteServiceProvider

...

{
    public function map(Router $router, ApiRouter $api)
    {
      ...

      $router->get('login', 'IO\Controllers\LoginController@showLogin');

      ...

    }
}

Services

Services contain the methods for processing data between the user and plentymarkets that can be used by controllers, REST and Twig templates. In the example below, BasketService.php contains the getBasket() method, which is used in BasketResource.php.

IO/src/Services/BasketService.php
...

/**
 * Return basket as array
 * @return Basket
 */
public function getBasket():Basket
{
    return $this->basketRepository->load();
}

...

The example below shows how the getBasket() method is used in a REST call. An array of basket items and a response code are returned.

IO/src/Api/Resources/BasketResource.php
<?php

namespace IO\Api\Resources;

use Symfony\Component\HttpFoundation\Response as BaseResponse;
use Plenty\Plugin\Http\Response;
use Plenty\Plugin\Http\Request;
use IO\Api\ApiResource;
use IO\Api\ApiResponse;
use IO\Api\ResponseCode;
use IO\Services\BasketService;

/**
 * Class BasketResource
 * @package IO\Api\Resources
 */
class BasketResource extends ApiResource
{
    /**
     * @var BasketService
     */
    private $basketService;

    /**
     * BasketResource constructor.
     * @param Request $request
     * @param ApiResponse $response
     * @param BasketService $basketService
     */
    public function __construct(Request $request, ApiResponse $response, BasketService $basketService)
    {
        parent::__construct($request, $response);
        $this->basketService = $basketService;
    }

    /**
     * Get the basket
     * @return BaseResponse
     */
    public function index():BaseResponse
    {
        $basket = $this->basketService->getBasket();
        return $this->response->create($basket, ResponseCode::OK);
    }
}

Design features

We will explain the design structure of a plugin based on the resources folder of the plentymarkets Ceres plugin and its sub-folders.

CSS

The css folder contains the CSS files based on Bootstrap 4.

Documents

The documents folder contains fonts, pdf-files and other document resources.

Images

Images, such as the company logo, are stored in the images folder.

JS

This is the folder for JavaScript files. The js folder contains the dist and src sub-folders. The source files are organised in the src folder. These source files are required for building plugin-ceres-app.js - the main JavaScript file, which is included in PageDesign.twig.

Ceres/resources/views/PageDesign.twig
<script src="{{ plugin_path('Ceres') }}/js/dist/plugin-ceres-app.js"></script>

The sub-folders app and libraries are located in src. All Vue.js components are saved in app/components. Related Twig templates can be found in the resources/views/templates folder. Custom Vue.js directives, e.g. the Logout.js, can be found in the app/directives folders.

Ceres/resources/js/src/app/directives/Logout.js
var ApiService          = require('services/ApiService');
var NotificationService = require('services/NotificationService');

    Vue.directive('logout', function ()
    {

      $(this.el).click(
        function (e)
        {
          ApiService.post("/rest/account/logout")
          .done(
            function(response)
            {
              NotificationService.success('Sie wurden erfolgreich ausgeloggt.').closeAfter(3000);
            }
          );

          e.preventDefault();

        }.bind(this));

    });

Services are saved in the app/services folder, e.g. the ApiService.js service for sending REST calls.

Ceres/resources/js/src/app/services/ApiService.js
var NotificationService = require('services/NotificationService');
var WaitScreenService = require('services/WaitScreenService');

module.exports = (function($) {

    var _token;

    return {
        get:    _get,
        put:    _put,
        post:   _post,
        delete: _delete,
        send:   _send,
        setToken: _setToken,
        getToken: _getToken
    };

    function _get( url, data, config )
    {
        config = config || {};
        config.method = 'GET';
        return _send( url, data, config );
    }

    ...

Lang

The lang folder contains sub-folders for translations in different languages. Translated strings for the Ceres design are saved in key-value pairs in the Template.properties file. Keys have prefixes that help you associating the keys with the respective templates:

Prefix Description
basket Template texts for templates in the resources/views/Basket folder and sub-folders
acc Template texts for templates in the resources/views/MyAccount and resources/views/Customer folders and sub-folders
itemCategory Template texts for templates in the resources/views/Category folder and sub-folders
item Template texts for templates in the resources/views/Item folder and sub-folders
variation Template texts for templates in the resources/views/Item folder and sub-folders
general Overall template texts used in multiple templates
address Template texts for templates in the resources/views/Customer folder and sub-folders
order Template texts for templates in the resources/views/MyAccount and resources/views/Checkout folders and sub-folders

In addition to the resources/lang folder, another lang folder can be found under resources/js/lang containing sub-folders with .js files in the respective languages. These files are built with the build:lang gulp task.

SCSS

In this folder, the Ceres.scss file imports all the other SCSS files stored in sub-folders. A grunt task generates the plugin-ceres.css file that can be found in the resources/css folder.

Views

The views folder contains the PageDesign.twig file - the basic framework for your online store. Static content pages, such as the login page, are organised in sub-folders with the related twig files. Vue.js related template files are organised into multiple sub-folders within the Templates folder. These files are necessary for rendering dynamic content of the Vue.js components stored in the folder resources/js/src/app/components.