Skip to main content

Creating an Integrations Hub page

Jadu Central's Integrations hub can be further extended by registering custom integrations. Alongside secure storage of integration credentials, custom integrations can make use of the integration hub user interface, and a service manager to request integration details within homepage widgets and custom scripts.

Creating an Integrations Hub page

Custom Integrations Hub pages follow the MVC architecture. To add your own page to the Hub to capture integration configuration you will need:

  • An integration definition
  • A View (twig)
  • A controller
  • A service locator
  • A migration to update services configuration

Jadu Central provides storage for integration settings, so in most cases you do not need to add your own database tables or model classes.

Integration definition

This is used by the Integrations Hub user interface to present your integration to the user.

Create a class that extends Jadu\Integrations\AbstractIntegration and implement all of the required methods.

namespace Custom\MyIntegration;

use Jadu\Integrations\AbstractIntegration;

class MyIntegrationDefinition extends AbstractIntegration
{
public function getTitle()
{
return 'My Integration';
}

public function getDescription()
{
return 'Link Jadu Central with the MyIntegration Server';
}

public function getImage()
{
return '/jadu/custom/images/myintegration.png';
}

public function getUrl()
{
return SECURE_JADU_PATH . '/integrations/my-integration';
}

public function getMachineName()
{
return 'my-integration';
}

/**
* Indicate whether this integration is available for Galaxies sites
*/
public function galaxySiteEnabled()
{
return true;
}
}

View

Create a twig template to capture your integration settings. Some key things to include:

  • extend the integrations base template
  • set the active_integration to the machine key of your integration
  • place your form and any other content in the integration_content block

For more information and examples of implementing forms, please see the Pulsar Forms component documentation.

{% extends '@assets/utilities/integrations/base.html.twig' %}

{% set active_integration = 'my-integration' %}
{% block integration_content %}
{{
form.create({
'class': 'form piano__form',
'method': 'POST',
'action': SECURE_JADU_PATH ~ '/integrations/my-integration',
'nonce': nonce
})
}}

{% if errors is not empty %}
{% set _errors = [] %}
{% for key, value in errors %}
{% set _errors = _errors|merge([{
'label': value,
'href': key,
}]) %}
{% endfor %}
{{
form.error_summary({
'heading': 'There is a problem',
'errors': _errors,
})
}}
{% endif %}

{{
form.text({
[...]
})
}}

{% if pageAccess.updateContent %}
{{
form.hidden({
'name': '__token',
'value': csrfToken
})
}}

{{
form.end({
'class': 'form__actions--flush',
'actions': [
form.submit({
'label': 'Save',
'class': 'btn btn--primary'
})
]
})
}}
{% else %}
{{ form.end() }}
{% endif %}
{% endblock %}

Controller

Add a controller class that extends Jadu\Integrations\AbstractIntegrationController.

namespace Custom\MyIntegration;

use Jadu\Integrations\AbstractIntegrationController;
use Jadu\Response\RedirectResponse;

class IndexController extends AbstractIntegrationController
{
public function index()
{
//...
}

public function save()
{
//...
}
}

There are several methods provided by the abstract class and its parents that can help simplify building out your settings form. These provide easy ways to perform several common tasks such as:

  • confirm the user has the required access
  • fetch and save integration setting values
  • sending responses

Below is an example implementation of the index() method which will render the form and its current values:

public function index()
{
if (!$this->canAccessPage()) {
return $this->redirectToError();
}

$params = array_merge([
'values' => [
'my-integration-api-key' => $this->getIntegrationValue('my-integration-api-key'),
],
'tab_visibility' => 'is-selected',
'errors' => $this->flash->get('errors', []),
'csrfToken' => $this->jadu->getCSRFToken()->getToken(),
] + $this->defaultParams);

return $this->response('@custom.integrations.myintegration/index.html.twig', $params);
}

The class methods are also useful when processing the submitted form, assisting with validation of the CSRF token and displaying error and success messages as can be seen in the example below:

public function save()
{
if (!$this->canAccessPage() || !$this->pageAccess->updateContent) {
return $this->redirectToError();
}

if (!$this->jadu->getCSRFToken()->isValid($this->input->post('__token'))) {
$this->flash->set('error', 'There was an error processing the form.');

return new RedirectResponse($this->sitePrefix . '/jadu/integrations/my-integration');
}

$values = [
'my-integration-api-key' => trim($this->input->post('my-integration-api-key')),
];

$errors = [];

// TODO: validate $values here and populate $errors if required

if (!empty($errors)) {
$params = array_merge(
[
'values' => $values,
'tab_visibility' => 'is-selected',
'errors' => $errors,
'csrfToken' => $this->jadu->getCSRFToken()->getToken(),
],
$this->defaultParams
);

$this->flash->set('error', 'Please make sure all required fields have been filled in correctly.');

return $this->response('@custom.integrations.my-integration/index.html.twig', $params);
}

$this->setIntegrationValue('my-integration-api-key', $values['my-integration-api-key']);
$this->flash->set('success', 'Your changes have been saved successfully.');

return new RedirectResponse($this->sitePrefix . '/jadu/integrations/my-integration');
}

Service locator

This class is used by Jadu Central to link all of the above items together and register the route(s) for your page(s):

namespace Custom\MyIntegration;

use Jadu\Response\HtmlResponse;
use Jadu\Service\Container;
use JaduFramework\Service\AbstractLocator;

class ServiceLocator extends AbstractLocator
{
protected $serviceContainer;

public function __construct(Container $serviceContainer)
{
parent::__construct($serviceContainer);

// Add your route(s)
$router = $this->serviceContainer->getRouter();
$router->add(new \Jadu_Route('GET', '/jadu/integrations/my-integration', 'Custom\MyIntegration\IndexController::index'));
$router->add(new \Jadu_Route('POST', '/jadu/integrations/my-integration', 'Custom\MyIntegration\IndexController::save'));

// Register the definition
$definition = $this->serviceContainer
->getInjector()
->make('Custom\MyIntegration\MyIntegrationDefinition');

$this->serviceContainer
->getIntegrationsContainer()
->register($definition);

// Add the template path
HtmlResponse::addPath(__DIR__ . '/Views', 'custom.integrations.myintegration');
}

public function init()
{
}
}
note

The init() method must be defined but is unused at present. In a future version of Jadu Central this may be called a single time when an instance of the class is created. At present, it is recommended to place any logic that is required to be called when an instance is created into the constructor (or a method called within the constructor).

services.xml configuration

Once all of the above are completed, you must register your integration within the configuration file config/services.xml.

You should add a migration to your project to apply this consistently to each environment:

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

class Version20230102034545 extends AbstractMigration
{
public const CONFIG_FILE = 'config/services.xml';

public function up(Schema $schema): void
{
$config = $this->version->getConfiguration();
$configPath = $config->getJaduPath() . '/' . self::CONFIG_FILE;

if (!file_exists($configPath)) {
$xml = '<' . '?xml version="1.0" encoding="utf-8" ?>
<system xmlns:config="http://www.jadu.co.uk/schema/config">
<services config:type="array">
<item key="my-integration">Custom\MyIntegration\ServiceLocator</item>
</services>
</system>';

file_put_contents($configPath, $xml);
} else {
$xml = simplexml_load_file($configPath);

if (!isset($xml->services)) {
$servicesElement = $xml->addChild('services', null);
$servicesElement->addAttribute('config:type', 'array');
} else {
$servicesElement = $xml->services;
}

$found = false;
foreach ($servicesElement->item as $itemElement) {
if (isset($itemElement['key']) && (string) $itemElement['key'] === 'my-integration') {
$found = true;
break;
}
}

if (!$found) {
$itemElement = $servicesElement->addChild('item', 'Custom\MyIntegration\ServiceLocator');
$itemElement['key'] = 'my-integration';
}

file_put_contents($configPath, $xml->asXML());
}
}

public function down(Schema $schema): void
{
$config = $this->version->getConfiguration();
$configPath = $config->getJaduPath() . '/' . self::CONFIG_FILE;

if (!file_exists($configPath)) {
return;
}

$xml = simplexml_load_file($configPath);

if (!isset($xml->services)) {
return;
}

$index = 0;
foreach ($xml->services->item as $itemElement) {
if (isset($itemElement['key']) && (string) $itemElement['key'] === 'my-integration') {
unset($xml->services->item[$index]);
file_put_contents($configPath, $xml->asXML());

return;
}

++$index;
}
}
}