Skip to content

Commit

Permalink
Merge pull request #27 from terminal42/feature/qr-code
Browse files Browse the repository at this point in the history
Allow to generate QR code for rewrite entries
  • Loading branch information
qzminski authored Jul 22, 2021
2 parents 14ca746 + 0d9d3d3 commit c7a7b17
Show file tree
Hide file tree
Showing 20 changed files with 654 additions and 9 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"symfony/filesystem": "^3.3 || ^4.0 || ^5.0",
"symfony/http-foundation": "^3.3 || ^4.0 || ^5.0",
"symfony/http-kernel": "^3.3 || ^4.0 || ^5.0",
"symfony/routing": "^3.3 || ^4.0 || ^5.0"
"symfony/routing": "^3.3 || ^4.0 || ^5.0",
"bacon/bacon-qr-code": "^2.0"
},
"require-dev": {
"contao/manager-plugin": "^2.0",
Expand Down
12 changes: 10 additions & 2 deletions src/ConfigProvider/ChainConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function addProvider(ConfigProviderInterface $provider): void
*/
public function find(string $id): ?RewriteConfigInterface
{
list($class, $id) = explode(':', $id);
[$class, $id] = explode(':', $id);

/** @var ConfigProviderInterface $provider */
foreach ($this->providers as $provider) {
Expand All @@ -57,7 +57,7 @@ public function findAll(): array

/** @var RewriteConfigInterface $config */
foreach ($providerConfigs as $config) {
$config->setIdentifier($this->getProviderIdentifier($provider).':'.$config->getIdentifier());
$config->setIdentifier(static::getConfigIdentifier($this->getProviderIdentifier($provider), $config->getIdentifier()));
}

$configs = array_merge($configs, $providerConfigs);
Expand All @@ -66,6 +66,14 @@ public function findAll(): array
return $configs;
}

/**
* Get the config identifier.
*/
public static function getConfigIdentifier(string $providerIdentifier, string $configIdentifier): string
{
return $providerIdentifier.':'.$configIdentifier;
}

/**
* Get the provider identifier.
*/
Expand Down
235 changes: 235 additions & 0 deletions src/Controller/QrCodeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<?php

/*
* UrlRewrite Bundle for Contao Open Source CMS.
*
* @copyright Copyright (c) 2021, terminal42 gmbh
* @author terminal42 <https://terminal42.ch>
* @license MIT
*/

namespace Terminal42\UrlRewriteBundle\Controller;

use Contao\Backend;
use Contao\BackendTemplate;
use Contao\CoreBundle\Exception\PageNotFoundException;
use Contao\Input;
use Contao\StringUtil;
use Contao\Validator;
use Contao\Widget;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Exception\InvalidParameterException;
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
use Symfony\Component\Routing\RouterInterface;
use Terminal42\UrlRewriteBundle\QrCodeGenerator;

class QrCodeController
{
/**
* @var Connection
*/
private $connection;

/**
* @var QrCodeGenerator
*/
private $qrCodeGenerator;

/**
* @var RequestStack
*/
private $requestStack;

/**
* @var RouterInterface
*/
private $router;

/**
* @var UriSigner
*/
private $uriSigner;

/**
* QrCodeController constructor.
*/
public function __construct(Connection $connection, QrCodeGenerator $qrCodeGenerator, RequestStack $requestStack, RouterInterface $router, UriSigner $uriSigner)
{
$this->connection = $connection;
$this->qrCodeGenerator = $qrCodeGenerator;
$this->requestStack = $requestStack;
$this->router = $router;
$this->uriSigner = $uriSigner;
}

/**
* Index view.
*/
public function index(): Response
{
if (($request = $this->requestStack->getCurrentRequest()) === null
|| !($id = $request->query->getInt('id'))
|| ($rewriteData = $this->connection->fetchAssociative('SELECT * FROM tl_url_rewrite WHERE id=?', [$id])) === false
|| !$this->qrCodeGenerator->validate($rewriteData)
) {
throw new PageNotFoundException();
}

$routeParameters = [
'scheme' => $request->getScheme(),
'host' => null,
];

$template = new BackendTemplate('be_url_rewrite_qr_code');
$template->backUrl = Backend::getReferer();

// Add form to the template
$this->addFormToTemplate($template, $request, $rewriteData, $routeParameters);

// Generate the QR code only if ALL parameters are set
if (count($routeParameters) > 0 && !\in_array(null, $routeParameters, true)) {
$this->addQrCodeToTemplate($template, $rewriteData, $routeParameters);
}

return $template->getResponse();
}

/**
* @Route("/url_rewrite_qr_code/{url}", name="url_rewrite_qr_code", methods={"GET"})
*/
public function qrCode(Request $request, string $url): Response
{
if (!$this->uriSigner->check($request->getSchemeAndHttpHost() . $request->getBaseUrl() . $request->getPathInfo() . (null !== ($qs = $request->server->get('QUERY_STRING')) ? '?' . $qs : ''))) {
return new Response(Response::$statusTexts[Response::HTTP_BAD_REQUEST], Response::HTTP_BAD_REQUEST);
}

$url = base64_decode($url);

if (!Validator::isUrl($url) || !preg_match('/https?:\/\//', $url)) {
return new Response(Response::$statusTexts[Response::HTTP_BAD_REQUEST], Response::HTTP_BAD_REQUEST);
}

$response = new Response($this->qrCodeGenerator->generateImage($url));
$response->headers->set('content-type', 'image/svg+xml');

return $response;
}

/**
* Add QR code to the template.
*/
private function addQrCodeToTemplate(BackendTemplate $template, array $rewriteData, array $routeParameters): void
{
try {
$url = $this->qrCodeGenerator->generateUrl($rewriteData, $routeParameters);

if ($url !== '') {
$template->qrCode = $this->uriSigner->sign($this->router->generate('url_rewrite_qr_code', ['url' => base64_encode($url)], RouterInterface::ABSOLUTE_URL));
$template->url = $url;
} else {
$template->error = $GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['routeError'];
}
} catch (MissingMandatoryParametersException | InvalidParameterException $e) {
$template->error = $e->getMessage();
}
}

/**
* Add form to the template.
*/
private function addFormToTemplate(BackendTemplate $template, Request $request, array $rewriteData, array &$routeParameters): array
{
$formFields = [];

// Add the scheme form field
$formFields['scheme'] = new $GLOBALS['BE_FFL']['select'](Widget::getAttributesFromDca([
'label' => &$GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['scheme'],
'options' => ['http', 'https'],
], 'scheme', Input::post('scheme') ?: $request->getScheme()));

// Determine the host
if (\is_array($hosts = StringUtil::deserialize($rewriteData['requestHosts'])) && \count($hosts = array_filter($hosts)) > 0) {
// Set the host immediately if there's only one
if (1 === \count($hosts)) {
$routeParameters['host'] = $hosts[0];
} else {
// Generate a select menu field for host
$formFields['host'] = new $GLOBALS['BE_FFL']['select'](Widget::getAttributesFromDca([
'label' => &$GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['host'],
'options' => $hosts,
'eval' => ['mandatory' => true, 'includeBlankOption' => true],
], 'host', Input::post('host')));
}
} else {
// Generate a text field for host
$formFields['host'] = new $GLOBALS['BE_FFL']['text'](Widget::getAttributesFromDca([
'label' => &$GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['host'],
'eval' => ['mandatory' => true, 'decodeEntities' => true, 'rgxp' => 'url'],
], 'host', Input::post('host')));
}

$requirements = StringUtil::deserialize($rewriteData['requestRequirements']);

// Generate the requirement fields
if (\is_array($requirements) && \count($requirements) > 0) {
foreach ($requirements as $requirement) {
if ('' !== $requirement['key'] && '' !== $requirement['value']) {
$fieldName = 'requirement_'.$requirement['key'];

$formFields[$fieldName] = new $GLOBALS['BE_FFL']['text'](Widget::getAttributesFromDca([
'label' => sprintf($GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['requirement'], $requirement['key'], $requirement['value']),
'eval' => ['mandatory' => true, 'urlRewriteRequirement' => $requirement],
], $fieldName, Input::post($fieldName)));

// Set route parameter to null value to indicate it's mandatory
$routeParameters[$requirement['key']] = null;
}
}
}

// Add form to template
if (\count($formFields) > 0) {
$formSubmit = 'contao-url-rewrite-qr-code';

$template->formFields = $formFields;
$template->formSubmit = $formSubmit;

// Process the form
if ($request->request->get('FORM_SUBMIT') === $formSubmit) {
$routeParameters = $this->processForm($formFields, $routeParameters);
}
}

return $formFields;
}

/**
* Process the form.
*/
private function processForm(array $formFields, array $routeParameters): array
{
/** @var Widget $formField */
foreach ($formFields as $formField) {
$formField->validate();

// Validate the requirement regexp, if any
if ($formField->urlRewriteRequirement && !preg_match('/^'.$formField->urlRewriteRequirement['value'].'$/', $formField->value)) {
$formField->addError(sprintf($GLOBALS['TL_LANG']['tl_url_rewrite']['qrCodeRef']['requirementError'], $formField->urlRewriteRequirement['value']));
}

// Return an empty array if at least one field has an error
if ($formField->hasErrors()) {
return [];
}

$routeParameters[$formField->urlRewriteRequirement ? $formField->urlRewriteRequirement['key'] : $formField->name] = $formField->value;
}

return $routeParameters;
}
}
7 changes: 4 additions & 3 deletions src/Controller/RewriteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Terminal42\UrlRewriteBundle\ConfigProvider\ConfigProviderInterface;
use Terminal42\UrlRewriteBundle\Exception\TemporarilyUnavailableConfigProviderException;
use Terminal42\UrlRewriteBundle\RewriteConfigInterface;
use Terminal42\UrlRewriteBundle\Routing\UrlRewriteLoader;

class RewriteController
{
Expand Down Expand Up @@ -48,11 +49,11 @@ public function __construct(ConfigProviderInterface $configProvider, ContaoFrame
*/
public function indexAction(Request $request): Response
{
if (!$request->attributes->has('_url_rewrite')) {
throw new RouteNotFoundException('The _url_rewrite attribute is missing');
if (!$request->attributes->has(UrlRewriteLoader::ATTRIBUTE_NAME)) {
throw new RouteNotFoundException(sprintf('The "%s" attribute is missing', UrlRewriteLoader::ATTRIBUTE_NAME));
}

$rewriteId = $request->attributes->get('_url_rewrite');
$rewriteId = $request->attributes->get(UrlRewriteLoader::ATTRIBUTE_NAME);

try {
$config = $this->configProvider->find($rewriteId);
Expand Down
19 changes: 19 additions & 0 deletions src/EventListener/RewriteContainerListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@

namespace Terminal42\UrlRewriteBundle\EventListener;

use Contao\Backend;
use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\DataContainer;
use Contao\Image;
use Contao\Input;
use Contao\StringUtil;
use Symfony\Cmf\Component\Routing\ChainRouterInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RouterInterface;
use Terminal42\UrlRewriteBundle\QrCodeGenerator;
use Terminal42\UrlRewriteBundle\RewriteConfigInterface;

class RewriteContainerListener
{
/**
* @var QrCodeGenerator
*/
private $qrCodeGenerator;

/**
* @var RouterInterface
*/
Expand All @@ -44,6 +53,7 @@ class RewriteContainerListener
private $fs;

public function __construct(
QrCodeGenerator $qrCodeGenerator,
RouterInterface $router,
string $cacheDir,
ContaoFramework $framework,
Expand All @@ -53,6 +63,7 @@ public function __construct(
$fs = new Filesystem();
}

$this->qrCodeGenerator = $qrCodeGenerator;
$this->router = $router;
$this->cacheDir = $cacheDir;
$this->fs = $fs;
Expand Down Expand Up @@ -156,6 +167,14 @@ public function generateExamples(): string
return sprintf('<div class="widget long">%s</div>', $buffer);
}

/**
* On QR code button callback.
*/
public function onQrCodeButtonCallback(array $row, string $href, string $label, string $title, string $icon, string $attributes): string
{
return $this->qrCodeGenerator->validate($row) ? '<a href="'.Backend::addToUrl($href.'&amp;id='.$row['id']).'" title="'.StringUtil::specialchars($title).'"'.$attributes.'>'.Image::getHtml($icon, $label).'</a> ' : Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)).' ';
}

/**
* Clear the router cache.
*/
Expand Down
Loading

0 comments on commit c7a7b17

Please sign in to comment.