diff --git a/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.module b/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.module index e15b65ac4..cf08ec2a5 100644 --- a/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.module +++ b/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.module @@ -50,11 +50,9 @@ function silverback_preview_link_theme(array $existing, string $type, string $th 'variables' => [ 'title' => NULL, 'preview_url' => NULL, - 'preview_qr_code' => NULL, - 'preview_qr_code_alt' => NULL, - 'link_description' => NULL, - 'actions_description' => NULL, - 'remaining_lifetime' => NULL, + 'preview_qr_code_url' => NULL, + 'expiry_description' => NULL, + 'actions_description' => NULL ], ], ]; diff --git a/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.routing.yml b/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.routing.yml index f88c4d34f..85f3e4da5 100644 --- a/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.routing.yml +++ b/packages/composer/amazeelabs/silverback_preview_link/silverback_preview_link.routing.yml @@ -20,6 +20,14 @@ silverback_preview_link.preview.access: _auth: ['oauth2'] no_cache: TRUE +silverback_preview_link.qr_code: + path: '/preview/qr-code/{base64_url}' + defaults: + _controller: '\Drupal\silverback_preview_link\Controller\PreviewController::getQRCode' + requirements: + # Keep it very generic, it's just to prevent anonymous access / bots. + _permission: 'access administration pages' + silverback_preview_link.preview_link.access: path: '/preview/link-access' defaults: diff --git a/packages/composer/amazeelabs/silverback_preview_link/src/Controller/PreviewController.php b/packages/composer/amazeelabs/silverback_preview_link/src/Controller/PreviewController.php index 426c68e55..6d5af216d 100644 --- a/packages/composer/amazeelabs/silverback_preview_link/src/Controller/PreviewController.php +++ b/packages/composer/amazeelabs/silverback_preview_link/src/Controller/PreviewController.php @@ -2,8 +2,10 @@ namespace Drupal\silverback_preview_link\Controller; +use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\silverback_preview_link\QRCodeWithLogo; use Drupal\user\Entity\User; use Symfony\Component\HttpFoundation\JsonResponse; @@ -61,4 +63,14 @@ public function hasLinkAccess() { ], 403); } + /** + * Returns the QR SVG file. + */ + public function getQRCode(string $base64_url): CacheableResponse { + $decodedUrl = base64_decode($base64_url); + $qrCode = new QRCodeWithLogo(); + $result = $qrCode->getQRCode($decodedUrl); + return new CacheableResponse($result, 200, ['Content-Type' => 'image/svg+xml']); + } + } diff --git a/packages/composer/amazeelabs/silverback_preview_link/src/Form/PreviewLinkForm.php b/packages/composer/amazeelabs/silverback_preview_link/src/Form/PreviewLinkForm.php index f087ae4b1..70ee6ff2e 100644 --- a/packages/composer/amazeelabs/silverback_preview_link/src/Form/PreviewLinkForm.php +++ b/packages/composer/amazeelabs/silverback_preview_link/src/Form/PreviewLinkForm.php @@ -5,6 +5,7 @@ namespace Drupal\silverback_preview_link\Form; use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\QROptions; use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; @@ -19,12 +20,15 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Url; use Drupal\node\NodeInterface; use Drupal\silverback_preview_link\Entity\SilverbackPreviewLink; use Drupal\silverback_preview_link\PreviewLinkExpiry; use Drupal\silverback_preview_link\PreviewLinkHostInterface; use Drupal\silverback_preview_link\PreviewLinkStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\silverback_preview_link\QRCodeLogo; +use Drupal\silverback_preview_link\QRCodeWithLogo; /** * Preview link form. @@ -145,7 +149,8 @@ public function buildForm(array $form, FormStateInterface $form_state, RouteMatc $remainingSeconds = max(0, ($this->entity->getExpiry()?->getTimestamp() ?? 0) - $this->time->getRequestTime()); $remainingAgeFormatted = $this->dateFormatter->formatInterval($remainingSeconds); $isNewToken = $this->linkExpiry->getLifetime() === $remainingSeconds; - $qrCode = NULL; + $displayQRCode = TRUE; + $qrCodeUrlString = NULL; $actionsDescription = NULL; if ($isNewToken) { @@ -160,6 +165,7 @@ public function buildForm(array $form, FormStateInterface $form_state, RouteMatc ':url' => $externalPreviewUrlString, '@entity_label' => $host->label(), ]); + $displayQRCode = FALSE; } else { $expiryDescription = $this->t('Live preview link for @entity_label expires in @lifetime.

', [ @@ -167,21 +173,23 @@ public function buildForm(array $form, FormStateInterface $form_state, RouteMatc '@entity_label' => $host->label(), '@lifetime' => $remainingAgeFormatted, ]); - $qrCode = (new QRCode)->render($externalPreviewUrlString); } - $actionsDescription = $this->t('If a new link is generated, active preview link will get invalidated.'); + $actionsDescription = $this->t('If a new link is generated, the active link becomes invalid.'); + } + + if ($displayQRCode) { + $qrCodeEncodedUrl = base64_encode($externalPreviewUrlString); + $qrCodeUrlString = Url::fromRoute('silverback_preview_link.qr_code', ['base64_url' => $qrCodeEncodedUrl])->toString(); } $form['preview_link'] = [ '#theme' => 'preview_link', '#title' => $this->t('Preview link'), '#weight' => -9999, - '#preview_qr_code' => $qrCode, - '#preview_qr_alt' => $externalPreviewUrlString, + '#preview_url' => $externalPreviewUrlString, + '#preview_qr_code_url' => $qrCodeUrlString, '#expiry_description' => $expiryDescription, '#actions_description' => $actionsDescription, - '#remaining_lifetime' => $remainingAgeFormatted, - '#preview_url' => $externalPreviewUrlString, ]; if (!$isNewToken) { diff --git a/packages/composer/amazeelabs/silverback_preview_link/src/QRCodeWithLogo.php b/packages/composer/amazeelabs/silverback_preview_link/src/QRCodeWithLogo.php new file mode 100644 index 000000000..7783855e9 --- /dev/null +++ b/packages/composer/amazeelabs/silverback_preview_link/src/QRCodeWithLogo.php @@ -0,0 +1,84 @@ + __DIR__ . '/images/amazee-labs_logo-square-green.svg', + 'svgLogoScale' => 1, + 'svgLogoCssClass' => 'dark', + 'version' => QRCode::VERSION_AUTO, + 'outputType' => QRCode::OUTPUT_CUSTOM, + 'outputInterface' => QRMarkupSVGWithLogo::class, + 'imageBase64' => FALSE, + // ECC level H is necessary when using logos. + 'eccLevel' => EccLevel::H, + 'addQuietzone' => TRUE, + // If set to TRUE, the light modules won't be rendered. + 'imageTransparent' => FALSE, + // Empty the default value to remove the fill* attributes from the elements + 'markupDark' => '', + 'markupLight' => '', + 'drawCircularModules' => TRUE, + 'circleRadius' => 0.45, + 'svgConnectPaths' => TRUE, + 'keepAsSquare' => [ + QRMatrix::M_FINDER | QRMatrix::IS_DARK, + QRMatrix::M_FINDER_DOT, + QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK, + ], + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient + 'svgDefs' => ' + + + + + ', + ]; + + private function getOptions(): QROptions { + // Augment the QROptions class. + return new class ($this->config) extends QROptions { + + protected string $svgLogo; + + // Logo scale in % of QR Code size, clamped to 10%-30%. + protected float $svgLogoScale = 0.20; + + // CSS class for the logo (defined in $svgDefs). + protected string $svgLogoCssClass = ''; + + protected function set_svgLogo(string $svgLogo): void { + if (!file_exists($svgLogo) || !is_readable($svgLogo)) { + throw new QRCodeException('invalid svg logo'); + } + $this->svgLogo = $svgLogo; + } + + // Clamp logo scale. + protected function set_svgLogoScale(float $svgLogoScale): void { + $this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale)); + } + + }; + } + + public function getQRCode(string $data) { + return (new QRCode($this->getOptions()))->render($data); + } + +} diff --git a/packages/composer/amazeelabs/silverback_preview_link/src/QRMarkupSVGWithLogo.php b/packages/composer/amazeelabs/silverback_preview_link/src/QRMarkupSVGWithLogo.php new file mode 100644 index 000000000..e3f4b5558 --- /dev/null +++ b/packages/composer/amazeelabs/silverback_preview_link/src/QRMarkupSVGWithLogo.php @@ -0,0 +1,51 @@ +moduleCount * $this->options->svgLogoScale); + // Calling QRMatrix::setLogoSpace() manually, + // so QROptions::$addLogoSpace has no effect. + $this->matrix->setLogoSpace($size, $size); + $svg = parent::paths(); + $svg .= $this->getLogo(); + return $svg; + } + + /** + * {@inheritdoc} + */ + protected function path(string $path, int $M_TYPE): string { + // Omit the "fill" and "opacity" attributes on the path element. + return sprintf('', $this->getCssClass($M_TYPE), $path); + } + + /** + * Returns a element that contains the SVG logo and positions + * it properly within the QR Code. + * + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform + */ + protected function getLogo(): string { + return sprintf( + '%5$s%5$s%4$s%5$s', + (($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2), + $this->options->svgLogoScale, + $this->options->svgLogoCssClass, + file_get_contents($this->options->svgLogo), + $this->options->eol + ); + } + +} diff --git a/packages/composer/amazeelabs/silverback_preview_link/src/images/amazee-labs_logo-square-green.svg b/packages/composer/amazeelabs/silverback_preview_link/src/images/amazee-labs_logo-square-green.svg new file mode 100644 index 000000000..44b8238f4 --- /dev/null +++ b/packages/composer/amazeelabs/silverback_preview_link/src/images/amazee-labs_logo-square-green.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/composer/amazeelabs/silverback_preview_link/templates/preview-link.html.twig b/packages/composer/amazeelabs/silverback_preview_link/templates/preview-link.html.twig index 434ee4ce9..bed873229 100644 --- a/packages/composer/amazeelabs/silverback_preview_link/templates/preview-link.html.twig +++ b/packages/composer/amazeelabs/silverback_preview_link/templates/preview-link.html.twig @@ -2,24 +2,26 @@ {% if preview_url is not empty %}