Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/modules/image/app_image.deploy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* @file
* Deploy hooks for app_image module.
*/

use Drupal\app_image\Hook\Deploy\Deploy0001;
use Drupal\app_image\Hook\Deploy\Deploy0002;

/**
* Delete dynamic image style security key from state.
*/
function app_image_deploy_0001(): string {
return \Drupal::classResolver(Deploy0001::class)();
}

/**
* Delete generated dynamic image style derivatives.
*/
function app_image_deploy_0002(): string {
return \Drupal::classResolver(Deploy0002::class)();
}
24 changes: 0 additions & 24 deletions app/modules/image/app_image.install

This file was deleted.

78 changes: 35 additions & 43 deletions app/modules/image/src/DynamicImageStyle/DynamicImageStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\State\StateInterface;
use Drupal\Core\PrivateKey;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
Expand All @@ -34,7 +33,7 @@
public function __construct(
private EntityTypeManagerInterface $entityTypeManager,
private StreamWrapperManagerInterface $streamWrapperManager,
private StateInterface $state,
private PrivateKey $privateKey,
) {}

public function effect(string $id, array $data = []): DynamicImageStyleBuilder {
Expand All @@ -45,52 +44,19 @@ public function effect(string $id, array $data = []): DynamicImageStyleBuilder {
* @param list<array{0: string, 1: array<string, mixed>}> $effects
*/
public function buildUrl(string $uri, array $effects): string {
$scheme = StreamWrapperManager::getScheme($uri);
$target = StreamWrapperManager::getTarget($uri);
Assert::string($scheme);
Assert::string($target);
$compressed = $this->compressEffects($effects);
[$scheme, $target, $compressed, $hash] = $this->resolveDerivativePath($uri, $effects);
$itok = $this->generateToken($compressed, $uri);
$hash = Crypt::hashBase64($compressed);

$image_style = $this->createImageStyle($effects);
$original_extension = \pathinfo($target, \PATHINFO_EXTENSION);
$derivative_extension = $image_style->getDerivativeExtension($original_extension);

$path = $target;
if ($original_extension !== $derivative_extension) {
$path .= '.' . $derivative_extension;
}

$encoded_effects = \urlencode($compressed);
$base_path = $this->getBaseUrlPath($scheme);

return '/' . $base_path . '/styles/dynamic/' . $hash . '/' . $scheme . '/' . $path
. '?effects=' . \urlencode($compressed)
. '&itok=' . $itok;
return "/$base_path/styles/dynamic/$hash/$scheme/$target?effects=$encoded_effects&itok=$itok";
}

/**
* @param list<array{0: string, 1: array<string, mixed>}> $effects
*/
public function buildUri(string $uri, array $effects): string {
$scheme = StreamWrapperManager::getScheme($uri);
$target = StreamWrapperManager::getTarget($uri);
Assert::string($scheme);
Assert::string($target);

$compressed = $this->compressEffects($effects);
$hash = Crypt::hashBase64($compressed);

$image_style = $this->createImageStyle($effects);
$original_extension = \pathinfo($target, \PATHINFO_EXTENSION);
$derivative_extension = $image_style->getDerivativeExtension($original_extension);

$path = $target;
if ($original_extension !== $derivative_extension) {
$path .= '.' . $derivative_extension;
}

return "$scheme://styles/dynamic/$hash/$scheme/$path";
[$scheme, $target, , $hash] = $this->resolveDerivativePath($uri, $effects);
return "$scheme://styles/dynamic/$hash/$scheme/$target";
}

/**
Expand Down Expand Up @@ -121,8 +87,7 @@ public function decompressEffects(string $compressed): array {
}

public function generateToken(string $compressed, string $uri): string {
$key = $this->state->get('app_image.dynamic_image_style_key', Settings::getHashSalt());
return \substr(Crypt::hmacBase64($compressed . ':' . $uri, $key), 0, 8);
return \substr(Crypt::hmacBase64($compressed . ':' . $uri, $this->privateKey->get()), 0, 8);
}

/**
Expand All @@ -140,6 +105,33 @@ public function createImageStyle(array $effects): ImageStyleInterface {
return $image_style;
}

private function hashEffects(string $compressed): string {
return \substr(Crypt::hashBase64($compressed), 0, 8);
}

/**
* @param list<array{0: string, 1: array<string, mixed>}> $effects
* @return array{string, string, string, string}
*/
private function resolveDerivativePath(string $uri, array $effects): array {
$scheme = StreamWrapperManager::getScheme($uri);
$target = StreamWrapperManager::getTarget($uri);
Assert::string($scheme);
Assert::string($target);

$image_style = $this->createImageStyle($effects);
$original_extension = \pathinfo($target, \PATHINFO_EXTENSION);
$derivative_extension = $image_style->getDerivativeExtension($original_extension);
if ($original_extension !== $derivative_extension) {
$target .= '.' . $derivative_extension;
}

$compressed = $this->compressEffects($effects);
$hash = $this->hashEffects($compressed);

return [$scheme, $target, $compressed, $hash];
}

/**
* Returns the base URL path for the given stream wrapper scheme.
*
Expand Down
28 changes: 28 additions & 0 deletions app/modules/image/src/Hook/Deploy/Deploy0001.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Drupal\app_image\Hook\Deploy;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class Deploy0001 implements ContainerInjectionInterface {

public static function create(ContainerInterface $container): self {
return new self(
$container->get(StateInterface::class),
);
}

public function __construct(
private StateInterface $state,
) {}

public function __invoke(): string {
$this->state->delete('app_image.dynamic_image_style_key');
return 'Deleted app_image.dynamic_image_style_key from state.';
}

}
42 changes: 42 additions & 0 deletions app/modules/image/src/Hook/Deploy/Deploy0002.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Drupal\app_image\Hook\Deploy;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class Deploy0002 implements ContainerInjectionInterface {

public static function create(ContainerInterface $container): self {
return new self(
$container->get('file_system'),
);
}

public function __construct(
private FileSystemInterface $fileSystem,
) {}

public function __invoke(): string {
$deleted = [];
foreach (['public', 'private'] as $scheme) {
$dir = $scheme . '://styles/dynamic';
if (!\is_dir($dir)) {
continue;
}

$this->fileSystem->deleteRecursive($dir);
$deleted[] = $scheme;
}

if ($deleted === []) {
return 'No dynamic style directories found.';
}

return \sprintf('Deleted styles/dynamic in: %s.', \implode(', ', $deleted));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ public function processInbound($path, Request $request): string {
}

// Private files: /system/files/styles/dynamic/...
// Rewrite to internal path that won't be caught by PathProcessorFiles.
// Set 'file' query param to block PathProcessorFiles (it skips processing
// when 'file' is already present), then rewrite to the registered route.
$private_prefix = '/system/files/styles/dynamic/';
if (\str_starts_with($path, $private_prefix)) {
return '/app-image/dynamic-private';
$request->query->set('file', 'dynamic');
return '/system/files/styles/dynamic';
}

return $path;
Expand Down
2 changes: 1 addition & 1 deletion app/modules/image/src/Routing/RouteProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __invoke(): RouteCollection {
));

$routes->add('app_image.dynamic_image_style_private', new Route(
path: '/app-image/dynamic-private',
path: '/system/files/styles/dynamic',
defaults: [
'_controller' => DynamicImageStyleController::class,
'_disable_route_normalizer' => TRUE,
Expand Down
Loading