<?php
/**
* Pimcore
*
* This source file is available under following license:
* - Pimcore Commercial License (PCL)
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license PCL
*/
namespace Pimcore\Bundle\DirectEditBundle\Service;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key;
use Pimcore\Bundle\DirectEditBundle\Exception\DirectEditException;
use Pimcore\Bundle\DirectEditBundle\Model\Mercure\Subscription;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Mercure\Jwt\StaticJwtProvider;
use Symfony\Component\Mercure\Publisher;
use Symfony\Component\Mercure\Update;
class PublishService
{
private $httpClient;
/**
* @var Publisher
*/
private $publisher;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var string
*/
private $jwt;
/**
* @var MercureUrlService
*/
private $mercureUrlService;
public function __construct(ContainerInterface $container, LoggerInterface $directEditLogger, MercureUrlService $mercureUrlService)
{
$this->httpClient = HttpClient::create();
$this->mercureUrlService = $mercureUrlService;
$mercureConfig = $container->getParameter('mercure');
$jwtKey = @$mercureConfig['hub']['jwt_key'];
if (empty($jwtKey)) {
throw new DirectEditException('You must configure "mercure.hub.jwt_key" in your parameters.');
}
$subscriptionPayload = [
'subscribe' => ['*']
];
$this->jwt = $this->buildJwt($jwtKey, $subscriptionPayload);
$publishPayload = [
'publish' => ['*']
];
$jwtPublishToken = $this->buildJwt($jwtKey, $publishPayload);
$publisher = new Publisher(
$this->mercureUrlService->getServerSideUrl(), //see documentation
new StaticJwtProvider($jwtPublishToken));
$this->publisher = $publisher;
$this->logger = $directEditLogger;
}
private function buildJwt(string $jwtKey, array $payLoad): string
{
if (class_exists('Lcobucci\JWT\Token\Builder')) {
$configuration = Configuration::forSymmetricSigner( // @phpstan-ignore-line
new Sha256(),
Key\InMemory::plainText($jwtKey) // @phpstan-ignore-line
);
$token = $configuration->builder()->withClaim('mercure', $payLoad)->getToken($configuration->signer(), $configuration->signingKey())->toString();
} else {
$token = (new Builder())->withClaim('mercure', $payLoad) // @phpstan-ignore-line
->getToken(new Sha256(), new Key($jwtKey)); // @phpstan-ignore-line
}
return $token;
}
public function sendUpdate(string $topic, array $data)
{
$this->logger->debug('Send update via mercure.',
[
'topic' => $topic,
'data' => $data
]);
$update = new Update($topic, json_encode($data), false);
$this->publisher->__invoke($update);
}
/**
* @throws \Exception
*/
public function discoverSubscriptions(?string $topic, bool $filterByActive = false): array
{
$urlTopicSelector = '';
/*
token selector doesn't work in current Mercure version (also tested in postman)
$urlTopicSelector = '/'.urlencode($topic);
*/
try {
$response = $this->httpClient->request('GET',
$this->mercureUrlService->getServerSideUrl() . '/subscriptions'.$urlTopicSelector, [
'headers' => [
'Authorization' => 'Bearer '.$this->jwt
]
])->getContent();
$subscriptionList = [];
$subscriptionData = json_decode($response, true);
if (isset($subscriptionData['subscriptions'])) {
foreach ($subscriptionData['subscriptions'] as $subscription) {
$subscriptionObject = $this->subscriptionAsObject($subscription);
if ($topic && strpos($subscriptionObject->getTopic(), $topic) !== 0) {
continue;
}
if ($filterByActive && !$subscriptionObject->isActive()) {
continue;
}
$subscriptionList[] = $subscriptionObject;
}
}
return $subscriptionList;
} catch (\Exception $e) {
$this->logger->alert('DiscoverSubscriptions: '.$e->getMessage());
return [];
}
}
private function subscriptionAsObject(array $subscription): Subscription
{
$subscriptionObject = new Subscription();
$subscriptionObject
->setType($subscription['type'])
->setId($subscription['id'])
->setTopic($subscription['topic'])
->setSubscriber($subscription['subscriber'])
->setActive($subscription['active'])
;
return $subscriptionObject;
}
}