How to create your own plugin

If you need a login method which is not covered by the shipped plugins you can easily add your own. In the following how to we will cover all needed steps to create your own plugin.

Name conventions

Type Mandatory
Example
Plugin folder yes
Free
Capitalize the first letter, pluginname equals Foldername
Class and filename yes
FreePlugin
FreePlugin.php
Capitalize the first letter, expression “Plugin” has to be added
Configuration file yes
free.config
Name of the folder, file ending has to be .config
Language file yes
lang
Has to be this name
Language files yes
free.en.lang
Lowercase, short country code (e.g. en), file ending has to be .lang, everything separated through a “.”
View-file of a plugin no
free.php
Lowercase, name of the folder
Css and javascript no
css/
js/
Folder names have to be in lowercase
Css and javascript files no
free.css
free.js
Lowercase

Needed Steps

Please note that some name conventions are mandatory. You can see this conventions in the table above. The other file and folder names are examples we are using in the SDK code you are free to modify them.

  1. Create a folder with the desired plugin name in the folder Plugin

  2. Add the following files and folders:

    • [Pluginname]Plugin.php
      • Main code of the plugin
      • Class name must be identical with this name
    • [pluginname].php
      • View file of the plugin
    • [pluginname].config
      • Configuration file
    • Folder “lang”
    • [pluginname].[lang].lang
      • Has to be in the folder lang
      • For each used language a language file has to be added
    • Optional: CSS and JS files
  3. Implmentation details:
    • Your plugin class has to implement the interface Plugin
    • In a regular case your plugin will use the PHP traits VisualPluginDefaults and AuthPluginDefaults to include common code.

States

Using states correctely is important to have a working plugin as this is the main criteria what has to happen next what´s especially important for multi-step authentication methods like PMS or external method like social or payment login. During authentication a client is in different states. This state is always saved in the Session object.

  • UNDEF: Initial state or unresolved state
  • PRE_AUTH:We have a valid redirect and a session but no auth plugin was chosen (during first page rendering)
  • AUTH_NEEDED: Request parsing was ok - waiting for the user to choose a login-type
  • AUTH_PENDING: We are waiting for an IACBOX callback with an success/error code
  • AUTH_EXT_PENDING: If the backend needs external redirects (like social or payment logins)
  • TEMPLATE_BEFORE_NEEDED: Mainly for prepaid tickets (paypal, etc) - to know what to charge a selction has to be made
  • TEMPLATE_AFTER_NEEDED: Mainly for PMS - authentication was ok, but template selection is needed but template selection is needed
  • TICKET_CREATION_PENDING: If AUTH_EXT_PENDING was successful, the ticket creation on the IACBOX is pending
  • ONLINE: Authentication was successful - user is online
  • TEMPORARILY_ONLINE_PENDING:User is taken online and an action is required
  • TEMPORARILY_ONLINE: User is taken online for a short amount to make an action
  • ERRORS_START: Marker
  • ROUTING_ERROR: General error state for the login API
  • PLUGIN_ERROR: Something went wrong inside the plugin, or with a needed external backend (like no connection to the PMS, …)
  • AUTH_FAILED: Authentication failed
../_images/loginapi2_states.png

Useful functions for creating your own plugin

Before we start we will describe with some useful methods you can use when developing your own plugin.

Get config values

We provide a number of methods to get the config from your plugin config file.

  • $key
    • Represents the key (given as String) from the value you want to use
    • Example: example-icon
  • $section
    • This parameter is optional
    • You can hand over a specific section as String
    • Example: v17
    • We recommend to use a more generic approach as we provide the method $this->loginApi->getLocationId()
  • $default
    • This parameter is optional
    • Define a default value if the config could not be found
  • $keyValueDelimier
    • This parameter is optional
    • Only available for the method getMap
    • Use only a specific field delimiter instead of default “:” colon
  • $entryDelimiter
    • This parameter is optional
    • Only available for the methods getlist and getMap
    • Use only a specific field delimiter instead of default [s,;]+
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

// Returns a config value as string
$this->config->get($key, $section, $default);

// Returns a numeric value casted to an integer
$this->config->getInt($key, $this->loginApi->getLocationId(), $default);

// Returns a numeric value parsed as float
$this->config->getFloat($key, $this->loginApi->getLocationId(), $default);

// Returns a boolean value of a config kex in the given section
$this->config->getBoolean($key, $this->loginApi->getLocationId(), $default);

// Returns a list of splitted strings, - used delimiters are whitespace, "," comma or ";" semicolon
$this->config->getList($key, $this->loginApi->getLocationId(), $default, $entryDelimiter = null);

// Returns a map - used delimiters are whitespace, "," comma or ";" semicolon.
// To seperate the list in a key => value the delimiter ":" is used
$this->config->getMap($key, $this->loginApi->getLocationId(), $default);

Get location Id

The method getLocationId() enables you to get the id with which the client hits the Login-API. It is crucial to use this method if you want to have a generic approach in your plugin and use the location-based configuration from the conf/main.config.

1
2
<?php
$this->loginApi->getLocationId();

Get translations

With the method translate() you can call translation keys you defined before hand in your lang files.

  • $key
    • Represents the key (given as String) from the value you want to use
    • Example: example-icon
1
2
<?php
$this->translate($key);

Check if the Login-API is used locally

The method isLocal() is an easy check if you use the local Login-API. Returns true if used locally or false.

1
2
<?php
$this->loginApi->isLocal()

Code Example Main Implementation

Attention

  • In the whole example we use Example as plugin name
  • We will start with the Plugin implementation itself

First of all you need to add the namespace to the plugin and add the import of the namespaces which we are listet in the code example below.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php

namespace Iacbox\LoginApi\Plugin\Example;

use Iacbox\LoginApi\Core\AbstractPlugin;
use Iacbox\LoginApi\Core\AuthPlugin;
use Iacbox\LoginApi\Core\VisualPlugin;
use Iacbox\LoginApi\Core\AuthPluginDefaults;
use Iacbox\LoginApi\Core\VisualPluginDefaults;
use Iacbox\LoginApi\Core\Session;

Next you need to define the class. Extend your Plugin from the class AbstractPlugin and implement the Interfaces AuthPlugin, VisualPlugin. To use some default implementation we are providing use the traits AuthPluginDefaults, VisualPluginDefaults.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
1
2
3
<?php
class ExamplePlugin extends AbstractPlugin implements AuthPlugin, VisualPlugin {
use AuthPluginDefaults, VisualPluginDefaults;

In the next step you need to define a name for your plugin how it should be able be called in the conf/main.config and a name how the Plugin should be displayed. The name has to be the same as the plugin.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
        /**
         * @inherit
         */
        public function getName() {
                // This is the internal name of the plugin used in the config - this has to be exactly the name used for the class name (lowercase)
                return 'example';
        }

        /**
         * @inherit
         */
        public function getDisplayName() {
                return 'Example of a plugin';
        }

For the next part we are going to define a icon and the name which should be displayed on the landing page. We will use the method to get the config which was described before. We recommend to copy the code provided below and change the keys according to your needs.

  • renderIcon($addCss = null)
    • The param $addCss is optional
    • The method used outputGlyphicon() adds the glyphicon with the default class “icon” and the class you defined if you use the param $addCss
  • renderName()
    • Add the translation of the plugin name you want to be displayed on the landing page
  • render($slot)
    • Renders the plugin and defines were it should be positioned on the landing page with the param $slot
    • Possible Slots:
      • SLOT_AUTH Positions the plugin in the main section of the landing page (e.g. Ticket plugin)
      • SLOT_BEFORE_FOOTER Positions the plugin before the footer (e.g. Status plugin)
      • SLOT_BOTTOM Positions the plugin below the footer
Iacbox/LoginApi/Plugin/Example/ExamplePlugin.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
32
33
<?php

/**
* @inherit
*/
public function renderIcon($addCss = null) {
        // This is called when the icon gets rendered for this plugin.
        // In the mobile version only the icons will be shown
        $this->outputGlyphicon($this->config->get('example-icon', $this->loginApi->getLocationId(), 'fa fa-code').($addCss ? " $addCss" : ''));
}

/**
* @inherit
*/
public function renderName() {
        // Renders the name of this plugin shown to the user in desktop mode.
        // The name is translated, so a language key is used.
        // Find further informations for translate() in the PHPdoc
        echo $this->translate('example');
}

/**
 * @inherit
 */
public function render($slot) {
        // The template index_view.php calls this method at different places with different slot names.
        // The usual slot for authentication plugins is SLOT_AUTH
        // Possible slots are SLOT_AUTH, SLOT_BEFORE_FOOTER, SLOT_BOTTOM
        if ($slot == VisualPlugin::SLOT_AUTH) {
                // Include the representation of the plugin for the LoginApi login page
                include(__DIR__.'/example.php');
        }
}

In the next part you can include css and javascript files. We positioned the possibility to add css and js files to the plugin to load them only if they really are needed. You can add as many css and js files as you need for your Plugin. In the method getJsIncludes() you can also see how to differentiate between local and external mode.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.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
<?php

/**
 * @inherit
 */
public function getCssIncludes() {
        // If a own CSS file is needed for this plugin it can be defined here and will be automatically included.
        // Please note that it's necessary to add the folder path ([Plugin]/[name]/[name].css).
        return array('Example/css/example.css');
}

/**
 * @inherit
 */
public function getJsIncludes() {
        // If a own JavaScript file is needed for this plugin it can be defined here and will be automatically included.
        // Please note it is necessary to add the folder path ([Plugin]/[name]/[name].js).
        // It's also possible to differentiate between local and external mode
        $js = array();
        if ($this->loginApi->isLocal()) {
                array_push($js, 'Example/js/example_local.js');
        } else {
                array_push($js, 'Example/js/example_external.js');
        }
        return $js;
}

In the last part of the main implementation of a plugin we are going to the core part. In the method processRequest() we are handling the different behaviour of the plugin. To navigate through the different phases of the plugin we are using states. Find all the possible states above under the point States. In our example we are going to create a simple ticket login. We will also provide an implementation for the local and the external mode.

We are starting with the backbone of the plugin. First you need to think of which states are needed to route through your plugin correctly. The authentication always starts with the State AUTH_NEEDED. Because the local Login-API is on the IACBOX it is not needed to use any further states but in the external mode (Login-API lies on an external webserver) we will need to use the AUTH_PENDING state. We also check if the the plugin was entered with the wrong state (every state except AUTH_NEEDED, AUTH_PENDING, ONLINE).

  • $getParams
    • In production LoginApiImpl passes $_GET.
  • postParams
    • In production LoginApiImpl passes $_POST.
  • &$session
    • State with the information about the session. Is called by reference.
Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

/**
 * @inherit
 */
public function processRequest($getParams, $postParams, &$session) {
        if ($session->getState() == Session::AUTH_NEEDED) {
                //Implement your code here
        } else if ($session->getState() == Session::AUTH_PENDING) {
                //Implement your code here
        } else if ($session->getState() != Session::ONLINE) {
                //Implement your code here
        }
}

We will now start with the concrete implementation of the Code which is needed for the state AUTH_NEEDED. At first we get the username and password from the post parameters. If you want to use the configuration value force-terms from conf/main.config it is important to check this value in the implementation. This check should to be independent from the difference between local and external mode. If the configuration is active (true) but the value could not be found in the post parameters we cancel the authentication and return the state AUTH_FAILED. A generic error message will be generated if the plugin returns with this state so you don’t need to create your own.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php

if ($session->getState() == Session::AUTH_NEEDED) {
        $usr = $postParams['username'];
        $pwd = $postParams['password'];
        // True if config force-terms is true/yes and have been accepted
        if ($this->loginApi->getConfig()->getBoolean('force-terms', $this->loginApi->getLocationId())
                        && !(array_key_exists('termsofuse', $postParams) && $postParams['termsofuse'] == '1')) {
                return $session->setState(Session::AUTH_FAILED);
        }

        // Implementation follows below
} else if ()

In the next step we are going to implement the logic for the local login. First we check if we are in local mode. You can find a description of the metho isLocal() above. We set the state to AUTH_PENDING. In local mode it should not be needed to get in this state but we can catch a possible error there. To take the client online call the method loginByCredentials(). If the client could be taken online the method returns a boolean. Now we just check the return value and add a error message. If the client could not be taken online you can get the error message of the IACBOX throught the following array in the session object $session->lastRequest[‘err’]. With the method addErrorMessage() you can add your own error message and the one from IACBOX.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php

if ($this->loginApi->isLocal()) {
        // Local LoginApi part
        $session->setState(Session::AUTH_PENDING);
        $success = $this->localInterface->loginByCredentials($usr, $pwd, $session);
        if (! $success) {
                $this->loginApi->addErrorMessage('logon-failed');
                if (array_key_exists('err', $session->lastRequest) && $session->lastRequest['err'] != null) {
                        $this->loginApi->addErrorMessage('', false, $session->lastRequest['err']);
                }
                $this->log->error('ticket', "Auth Failed with the following RC [".$session->lastRequest['rc']."]");
        }
        if ($this->log->debugging()) {
                $this->log->debug('ticket', 'Login by ticket '.($success ? 'success' : 'failed'));
        }
        return $session->setState($success ? Session::ONLINE : Session::AUTH_FAILED);
} else {
        // Implementation of the external mode

In the implementation of the external mode we prepared a method redirectToIacBox() which sends a post request to the IACBOX from a array you defined before. In our example we create the array $paramMap with the following params:

  • id
    • The ID which identifies the client. This is a random token
  • ac
    • Which action should be taken
    • In our example and most of the time it is logon
  • type
    • Logon type which needs to be used
    • The following types are available
      • cred = credentials (e.g. Ticket login)
      • to = takeonline (e.g. Social login)
      • free = free logon (e.g. Free logon)
      • create = create ticket per id (e.g. Payment login)
      • pms = take onliner per roomnumber or other pms fields (e.g. PMS login)
  • user
    • Username which was entered in the input field
  • pwd
    • Password which was entered in the input field

After we created the array to login we set the state to AUTH_PENDING. Afterwards we call the method redirectToIacBox() to redirect our params to the IACBOX to take the client online. To ensure the scripts stops its execution we use the php method exit().

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

if ($session->getState() == Session::AUTH_NEEDED) {

        if ($this->loginApi->isLocal()) {
                // Implementation of local Login-API part
        } else {
                // External Login-API part
                $paramMap = array(
                        'id'            => $session->id,
                        'ac'            => 'logon',
                        'type'          => 'cred',
                        'user'          => $usr,
                        'pwd'           => $pwd
                );
                $session->setState(Session::AUTH_PENDING);
                $this->log->info('ticket', 'Ticket login pending');
                $this->loginApi->redirectToIacBox($paramMap);
                exit();
        }
} else if ($session->getState() == Session::AUTH_PENDING) {
        //Implementation of behaviour in state AUTH_PENDING
}

In the next step we cover the behaviour when entering the plugin with the state AUTH_PENDING. Like in the state before we need to differentiate between local mode and external mode.

If we enter the ExamplePlugin in this state locally no further steps are needed because the login was finished in AUTH_NEEDED. If the plugin still gets entered in the state AUTH_PENDING it equals an error and we add a error message and return the error state PLUGIN_ERROR.

In external mode we cover the return values of the IACBOX which were saved in the session. We check if the key rc is 0. This means no error happend during the login. Any other return code represents a specific error. If the rc was 0 we return with the stete ONLINE. If we an error occured we return a error message with the error of the IACBOX and the state AUTH_FAILED.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.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
<?php

if ($session->getState() == Session::AUTH_NEEDED) {
        //Implementation of behaviour in state AUTH_NEEDED
} else if ($session->getState() == Session::AUTH_PENDING) {
        if ($this->loginApi->isLocal()) {
                // Should not happen, as the login is made blocking and locally - see above
                $this->loginApi->addErrorMessage('error-plugin');
                $this->log->error('ticket', 'Unknown Error! Local call in State AUTH_PENDING');
                return $session->setState(Session::PLUGIN_ERROR);
        } else {
                // Awaited callback with an success or error code
                if ($session->lastRequest['rc'] == 0) {
                        $this->log->info('ticket', 'Ticket login was successful');
                        return $session->setState(Session::ONLINE);
                } else {
                        $this->loginApi->addErrorMessage('logon-failed');
                        if (array_key_exists('err', $session->lastRequest) && $session->lastRequest['err'] != null) {
                                $this->loginApi->addErrorMessage('', false, $session->lastRequest['err']);
                                $this->log->error('ticket', "Auth Failed with the following RC [".$session->lastRequest['rc']."]");
                        }
                        return $session->setState(Session::AUTH_FAILED);
                }
        }
}

In the last part we check if the plugin was entered with the state ONLINE. This should not happen because no further steps are needed. We return a error message and the state PLUGIN_ERROR to show something did not work correctly. At last we return the present state to catch the error in case we forgot to return the state in one of our cases.

Iacbox/LoginApi/Plugin/Example/ExamplePlugin.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

if ($session->getState() == Session::AUTH_NEEDED) {
        //Implementation of behaviour in state AUTH_NEEDED
} else if ($session->getState() == Session::AUTH_PENDING) {
        //Implementation of behaviour in state AUTH_PENDING
} else if ($session->getState() != Session::ONLINE) {
        $this->loginApi->addErrorMessage('error-plugin');
        $this->log->error('ticket', 'Plugin was entered with the wrong State '.$session->getStateAsString());
        return $session->setState(Session::PLUGIN_ERROR);
}
return $session->getState();