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()
{
}
}
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;
}
}
}