PHP client for the ePoetry service.
Before proceeding, it is recommended to read the "Introduction and terminology" section of the official ePoetry documentation.
A bird's-eye overview of a typical translation request workflow can be outlined as follows:
This project provides the necessary code (SOAP objects, middleware, etc.) to request a translation and handle incoming notifications from the ePoetry service.
./bin/epoetry
: CLI executable to interact with the ePoetry service./resources
: request and notification services WSLD/XLD files, as provided by the ePoetry service.
Original resources can be obtained by accessing the following links, withing the European Commission network:
./config/soap-client-*.php
: configuration files for the code generation./config/validator/*.yaml
: configuration files for object validation, built using Symfony Validator./src/CodeGenerator
: set of assembler classes, used to generated client's code./src/Console
: Symfony Console command classes./src/ExtSoapEngine
: custom SOAP engine classes, such as a WSDL provider to process locally stored WSDL files./src/Console
: automatically generated classes for the "Notification" service./src/Request
: automatically generated classes for the "Request" service./src/Authentication
: authentication services./src/TicketValidation
: ticket validation servicesThe ePoetry service uses EU Login as a trusted third-party authentication system. Application that wants to use the ePoetry client will need to request an EU Login Job Account.
You can request an EU Login job account from DIGIT by visiting this page and communicate it to DGT for setting up access. When requesting your job account, please keep in mind that:
Note: when requesting your job account make sure to ask DIGIT to insert your DG in the job account's "department" field: this is required by the ePoetry service.
Once you get your job account, you have two ways of authenticating against the service:
PLEASE NOTE: both methods above requires extra dependencies to be added to your project, please check the respective sections below for more information.
In EU Login, the application running the ePoetry library (e.g. a Drupal site) can be represented by a special kind of user accounts called a "job account".
In order to set up this authentication method you need to request a job account to EU Login, linked to the site running the ePoetry library. You can ask your direct manager or scrum master to initiate the request job account request. Check this page for more information.
After getting an EU Login job account you need to communicate this to DGT so that they can proceed with the setup on the ePoetry service. For more information about this procedure check this page.
Once you get the job account you need to request a client certificate: you can ask this too to your scum master. On acceptance environments you can request one yourself at https://webgate.acceptance.ec.europa.eu/cas/selfCertWeb.
Once you receive the actual client certificate file, in .p12
format, and its password, you can configure the
authentication plugin service. Below an example of a possible setup:
client_cert_authentication:
class: \OpenEuropa\EPoetry\Authentication\ClientCertificate\ClientCertificateAuthentication
arguments:
$serviceUrl: "https://www.cc.cec/epoetry/webservices/dgtService"
$certFilepath: "/path/to/certs/j123abc.p12"
$certPassword: "password"
$euLoginBasePath: "https://ecas.cc.cec.eu.int:7003"
$logger: "..." # A PSR compatible logger implementation.
For ePoetry acceptance, use the following:
$serviceUrl: "https://www.acceptance.cc.cec/epoetry/webservices/dgtService"
PLEASE NOTE: this authentication method requires the following dependencies, as specified in composer.json
"suggest" section:
"suggest": {
"symfony/http-client": "Require this in your project if you use the client certificate authentication plugin."
},
You can authenticate using OpenID Connect by using the OpenIDAuthentication plugin.
This authentication plugin needs the following parameters to be set:
In order to obtain this you need to register your application as an OpenID Connect client by following these instructions.
Below you can find a working example of a client metadata:
{
"application_type" : "web",
"client_id" : "...",
"client_id_issued_at" : 1656329142,
"client_name" : "[Name of your application]",
"client_secret" : "...",
"client_secret_expires_at" : 0,
"client_type" : "confidential",
"contacts" : [ "[Email of the EC official issuing the request]" ],
"grant_types" : [ "client_credentials" ],
"id_token_signed_response_alg" : "PS512",
"job_account" : "[Your EU Login Job Account ID]",
"oauth_application_type" : "web_application",
"redirect_uris" : [ "[Your application's URL]" ],
"registration_access_token" : "...",
"registration_client_uri" : "...",
"response_types" : [ ],
"scope" : "openid",
"subject_type" : "public",
"token_endpoint_auth_method" : "client_secret_jwt"
}
Use the above values as a reference to configure your own client metadata. Make sure you set these as follows:
...
"application_type" : "web",
"grant_types" : [ "client_credentials" ],
"id_token_signed_response_alg" : "PS512",
"oauth_application_type" : "web_application",
"token_endpoint_auth_method" : "client_secret_jwt"
...
Once you get such information, store it in a JSON file that is reachable by your application, as this will be needed to configure the authentication service. Below an example of a possible setup:
openid_authentication:
class: \OpenEuropa\EPoetry\Authentication\OpenID\OpenIDAuthentication
arguments:
$wellKnownUrl: "https://ecas.ec.europa.eu/cas/oauth2/.well-known/openid-configuration"
$clientMetadataFilepath: "/path/to/client-metadata.json"
$serviceUrl: "https://www.cc.cec/epoetry/webservices/dgtService"
$tokenEndpoint: "https://ecas.ec.europa.eu/cas/oauth2/token"
$logger: "..." # A PSR compatible logger implementation.
For ePoetry acceptance, use the following:
$serviceUrl: "https://www.acceptance.cc.cec/epoetry/webservices/dgtService"
PLEASE NOTE: this authentication method requires the following dependencies, as specified in composer.json
"suggest" section:
"suggest": {
"facile-it/php-openid-client": "Require this in your project if you use the OpenID Connect authentication plugin.",
"web-token/jwt-signature-algorithm-hmac": "Require this in your project if you use the OpenID Connect authentication plugin.",
},
The ePoetry service sends messages to the site, containing information about a translation request status change, the translated content, etc.
In order for your application to be able to receive and handle ePoetry notifications, you need to expose two endpoints:
The NotificationServerFactory
can be used in implementing both endpoints.
The NotificationServerFactory
service requires the following dependencies:
$callback
: the site's endpoint handing inbound POST requests.$eventDispatcher
: a Symfony event dispatcher instance.$logger
: a PSR compatible logger implementation.$serializer
: an instance of the library's Serializer class$ticketValidation
: optionally, an instance of the ticket validation service.Below an example of a Symfony controller that implements both endpoints:
<?php
namespace App\Controller;
use Http\Discovery\Psr17Factory;
use OpenEuropa\EPoetry\NotificationServerFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
class TranslationController extends AbstractController
{
private NotificationServerFactory $factory;
/**
* @param \OpenEuropa\EPoetry\NotificationServerFactory $factory
*/
public function __construct(NotificationServerFactory $factory) {
$this->factory = $factory;
}
/**
* @Route("/translation", name="translation_get", methods={"GET"})
*/
public function getWsdl(Request $request): Response
{
$response = new Response($this->factory->getWsdl());
$response->headers->set('Content-Type', 'application/xml');
return $response;
}
/**
* @Route("/translation", name="translation_post", methods={"POST"})
*/
public function handleNotification(Request $request): Response
{
$psr17Factory = new Psr17Factory();
$psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
$response = $this->factory->handle($psrHttpFactory->createRequest($request));
return (new HttpFoundationFactory())->createResponse($response);
}
}
In the example above, the NotificationServerFactory
's $callback
would be: https://my-site.com/translation
.
When handling inbound notifications, the NotificationServerFactory
fires the following Symfony events:
Product\StatusChangeAcceptedEvent
: fired when the status of the product changes to "accepted".Product\StatusChangeCancelledEvent
: fired when the status of the product changes to "cancelled".Product\StatusChangeClosedEvent
: fired when the status of the product changes to "closed".Product\StatusChangeOngoingEvent
: fired when the status of the product changes to "ongoing".Product\StatusChangeReadyToBeSentEvent
: fired when the status of the product changes to "ready to be sent".Product\StatusChangeRequestedEvent
: fired when the status of the product changes to "requested".Product\StatusChangeSentEvent
: fired when the status of the product changes to "sent".Product\StatusChangeSuspendedEvent
: fired when the status of the product changes to "suspended".Product\DeliveryEvent
: fired when a product translation is finalized. This contains the actual translation(s).Request\StatusChangeAcceptedEvent
: fired when the status of the linguistic request changes to "accepted".Request\StatusChangeCancelledEvent
: fired when the status of the linguistic request changes to "cancelled".Request\StatusChangeExecutedEvent
: fired when the status of the linguistic request changes to "executed".Request\StatusChangeRejectedEvent
: fired when the status of the linguistic request changes to "rejected".Request\StatusChangeSuspendedEvent
: fired when the status of the linguistic request changes to "suspended".For more information about ePoetry notifications check the official documentation.
Inbound notifications will need to be authenticated using the EU Login ticket validation service. In order to do so, this library
provides the EuLoginTicketValidation
service, which will need
to be injected when building the NotificationServerFactory
.
The EuLoginTicketValidation
service requires the following dependencies:
$callbackUrl
: the site's URL where the NotificationServerFactory
is handling requests.$euLoginBasePath
: the EU Login public base URL. For both ePoetry production and acceptance, this should be set to https://ecas.ec.europa.eu
.$jobAccount
: The ePoetry job account. This should be set to j97brfy
. Please not this might change in the future,
make sure you consult the following documentation page.$requestFactory
: a PSR compatible HTTP request factory, check this list for possible candidates.$httpClient
: a PSR compatible HTTP client, check this list for possible candidates.$logger
: a PSR compatible logger implementation.Below an example of a possible setup:
eu_login_ticket_validation:
class: \OpenEuropa\EPoetry\TicketValidation\EuLogin\EuLoginTicketValidation
arguments:
$callbackUrl: "https://my-site.europa.eu/my/epoetry/endpoint"
$euLoginBasePath: "https://ecas.ec.europa.eu"
$jobAccount: "j97brfy"
$requestFactory: "..." # A PSR compatible HTTP request factory.
$httpClient: "..." # PSR compatible HTTP client.
$logger: "..." # A PSR compatible logger implementation
The library is built using the PHP SOAP client project which, among other things, allows for SOAP-related PHP classes to be automatically generated, given a WSDL/XSD files pair.
To (re-)generate code, run:
./vendor/bin/run generate:request
./vendor/bin/run generate:notification
./vendor/bin/run generate:authentication
Or, simply, the command below, which runs all three commands above:
./vendor/bin/run generate
Note that the method \OpenEuropa\EPoetry\Notification\Type\RequestReference::getReference()
has been added manually,
and it won't be automatically generated: make sure you restore it using your local Git history.
This library provides the following convenience CLI commands to interact with the ePoetry service. You can set command verbosity
by setting the usual -v
, -vv
and -vvv
flags. If you want to set the maximum level of verbosity, set EPOETRY_CONSOLE_DEBUG=1
in .env
. You can also copy .env
into .env.local
and override the value there: .env.local
is git-ignored by default.
Note for developers: Symfony stores a compiled version of the command container under ./var
: make sure you delete this
directory if you:
Run:
$ ./bin/epoetry authentication:get-ticket
This will use the authentication method set in the Symfony Console container to retrieve an authentication ticket. If successful the ticket will be printed out:
$ ./bin/epoetry authentication:get-ticket
ST-1785405-EQb6PwLuh9PKnpLk6hiGHAD...
The default authentication method is the Client Certificate login, you can change that to the OpenID Connect plugin by setting an alternative value here in ./config/console/services.yml:
OpenEuropa\EPoetry\Authentication\AuthenticationInterface: "@openid_authentication"
The Client Certificate method requires a path to the client certificate file, in .p12
format, and it's password.
Both parameters can be set via the following environment variables:
EPOETRY_CONSOLE_CLIENT_CERT_SERVICE_URL=https://www.test.cc.cec/epoetry/webservices/dgtService
EPOETRY_CONSOLE_CLIENT_CERT_PATH=/path/to/certs/j905dyi.p12
EPOETRY_CONSOLE_CLIENT_CERT_PASSWORD=password
The OpenID Connect method requires a valid client metadata JSON file, available locally. You can control the value of that, along with other authentication setting, by changing the following environment variables:
EPOETRY_CONSOLE_OPENID_AUTH_CLIENT_METADATA=/path/to/client-metadata.json
EPOETRY_CONSOLE_OPENID_WELL_KNOWN_URL=https://ecas.acceptance.ec.europa.eu/cas/oauth2/.well-known/openid-configuration
EPOETRY_CONSOLE_OPENID_SERVICE_URL=https://www.test.cc.cec/epoetry/webservices/dgtService
EPOETRY_CONSOLE_OPENID_TOKEN_ENDPOINT=https://ecas.acceptance.ec.europa.eu/cas/oauth2/token
Run:
$ ./bin/epoetry request:evaluate /path/to/request.php
The PHP file /path/to/request.php
should return a function with the following signature:
<?php
use OpenEuropa\EPoetry\RequestClientFactory;
use OpenEuropa\EPoetry\Console\Command\EvaluateRequestReturn;
return function(): EvaluateRequestReturn {
// Build $object here...
$request = (new CreateLinguisticRequest())
->setRequestDetails($requestDetails)
->setApplicationName('FOO')
->setTemplateName('WEBTRA');
return new EvaluateRequestReturn($request, 'createLinguisticRequest');
};
The command will require the file above and use the returned valued to perform an actual request to the ePoetry service. You can set the desired service URL via the following environment variable:
EPOETRY_CONSOLE_SERVICE_URL=https://webgate.acceptance.ec.europa.eu/epoetrytst/epoetry/webservices/dgtService
If successful the command will return the ePoetry response in JSON format:
$ ./bin/epoetry request:evaluate /path/to/request.php
Request:
{
"requestDetails": {
"title": "Content title",
"internalReference": "Translation request 1",
"requestedDeadline": "2121-07-06T11:51:00+01:00",
"sensitive": false,
...
}
Response:
{
"return": {
"requestReference": {
"dossier": {
"requesterCode": "COMM",
"number": 25,
"year": 2023
},
"productType": "TRA",
"part": 0,
"version": 0
},
"requestDetails": {
"title": "Content title",
"workflowCode": "WEB",
...
}
Running the command with -vvv
will display the raw HTTP request and response.
Run:
$ ./bin/epoetry notification:start-listener
This will start a service listening for incoming messages at port 8088
.
All POST
incoming requests will be saved in .sink/notifications
, regardless if they are actual ePoetry notifications,
or not. Additionally, ePoetry notifications will be handled and the service response will be print out on the console.
Any GET
request to your service will print out the service WSDL, which contains the callback URL. When running this
on a publicly accessible server, you might want to change the callback URL by setting the following ENV variable:
EPOETRY_CONSOLE_CALLBACK_URL=https://my-host.com:8088
Remember to delete the ./var directory to force a Symfony command container rebuild.
You can override the command default parameters as follows:
$ ./bin/epoetry notification:start-listener --port=80 --save-to=/path/to/folder
If you wish to return an error, start the service with the -e
flag.
$ ./bin/epoetry notification:start-listener -e
It is recommended to always use -vvv
for a fully verbose output.
When using the console commands on a Cloud9 environment, add a docker-compose.override.yml
with the following content:
version: "2"
services:
php:
image: registry.fpfis.eu/fpfis/httpd-php:8.1-dev
working_dir: /var/www/html
Then login into the php
container and run:
apt update
apt install php-bcmath -y
This is necessary until the official Docker image will support the required PHP extension.
The ePoetry client library requires the ext-bcmath
PHP extension, which is not necessarily enabled on all images used
on the European Commission infrastructure.
When using this library on a site, make sure you install the ext-bcmath
extension by specifying it in the site's
.opts.yml
file, as follows:
extra_pkgs:
- ext-bcmath
For more information please refer to the pipeline configuration documentation.
The updateCallbackUrl
method is incorrectly specified in the ePoetry schema, for this reason we have patched locally
the schema in this PR. If necessary, remember to reapply
such change after updating the WSDL/XSD files.