-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
- Loading branch information
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
use Symfony\Component\Routing\RouterInterface; | ||
|
||
final readonly class ActionsSectionProcessor implements SectionProcessor | ||
{ | ||
public function __construct( | ||
private ?RouterInterface $router = null, | ||
) { | ||
} | ||
|
||
public function process(SymfonyStyle $io, array $config, array $manifest): array|int | ||
Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
Check failure on line 18 in src/Command/SectionProcessor/ActionsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
|
||
{ | ||
if ($config['file_handlers'] === []) { | ||
return $manifest; | ||
} | ||
foreach ($manifest['file_handlers'] as $id => $handler) { | ||
if (str_starts_with((string) $handler['action'], '/')) { | ||
continue; | ||
} | ||
if ($this->router === null) { | ||
$io->error('The router is not available. Unable to generate the file handler action URL.'); | ||
return Command::FAILURE; | ||
} | ||
$manifest['file_handlers'][$id]['action'] = $this->router->generate( | ||
$handler['action'], | ||
[], | ||
RouterInterface::RELATIVE_PATH | ||
); | ||
} | ||
|
||
return $manifest; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||
use Symfony\Component\Filesystem\Filesystem; | ||
use function is_int; | ||
|
||
final class ApplicationIconsSectionProcessor implements SectionProcessor | ||
{ | ||
use IconsSectionProcessorTrait; | ||
|
||
public function __construct( | ||
Check failure on line 17 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
|
||
private readonly Filesystem $filesystem, | ||
#[Autowire('%spomky_labs_pwa.dest%')] | ||
private readonly array $dest, | ||
private readonly null|ImageProcessor $imageProcessor = null, | ||
) { | ||
} | ||
|
||
public function process(SymfonyStyle $io, array $config, array $manifest): array|int | ||
Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
Check failure on line 25 in src/Command/SectionProcessor/ApplicationIconsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
|
||
{ | ||
if ($config['icons'] === []) { | ||
return $manifest; | ||
} | ||
$result = $this->processIcons($io, $config['icons']); | ||
if (is_int($result)) { | ||
return $result; | ||
} | ||
$manifest['icons'] = $result; | ||
$io->info('Icons are built'); | ||
|
||
return $manifest; | ||
} | ||
|
||
protected function getFilesystem(): Filesystem | ||
{ | ||
return $this->filesystem; | ||
} | ||
|
||
protected function getIconPrefixUrl(): string | ||
{ | ||
return $this->dest['icon_prefix_url']; | ||
} | ||
|
||
protected function getIconFolder(): string | ||
{ | ||
return $this->dest['icon_folder']; | ||
} | ||
|
||
protected function getImageProcessor(): ?ImageProcessor | ||
{ | ||
return $this->imageProcessor; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use SpomkyLabs\PwaBundle\Command\Client; | ||
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||
use Symfony\Component\Filesystem\Filesystem; | ||
use function is_int; | ||
|
||
final class ApplicationScreenshotsSectionProcessor implements SectionProcessor | ||
{ | ||
use ScreenshotsProcessorTrait; | ||
|
||
private readonly null|Client $webClient; | ||
Check failure on line 18 in src/Command/SectionProcessor/ApplicationScreenshotsSectionProcessor.php GitHub Actions / PHP 8.2 Test on ubuntu-latest
|
||
|
||
public function __construct( | ||
private readonly Filesystem $filesystem, | ||
#[Autowire('%spomky_labs_pwa.dest%')] | ||
private readonly array $dest, | ||
#[Autowire('@pwa.web_client')] | ||
null|Client $webClient, | ||
private readonly null|ImageProcessor $imageProcessor = null, | ||
) { | ||
if ($webClient === null && class_exists(Client::class)) { | ||
$webClient = Client::createChromeClient(); | ||
} | ||
$this->webClient = $webClient; | ||
} | ||
|
||
public function process(SymfonyStyle $io, array $config, array $manifest): array|int | ||
{ | ||
if ($config['screenshots'] === []) { | ||
return $manifest; | ||
} | ||
$result = $this->processScreenshots($io, $config['screenshots']); | ||
if (is_int($result)) { | ||
return $result; | ||
} | ||
$manifest['screenshots'] = $result; | ||
|
||
return $manifest; | ||
} | ||
|
||
protected function getFilesystem(): Filesystem | ||
{ | ||
return $this->filesystem; | ||
} | ||
|
||
protected function getImageProcessor(): ?ImageProcessor | ||
{ | ||
return $this->imageProcessor; | ||
} | ||
|
||
protected function getWebClient(): ?Client | ||
{ | ||
return $this->webClient; | ||
} | ||
|
||
protected function getScreenshotPrefixUrl(): string | ||
{ | ||
return $this->dest['screenshot_prefix_url']; | ||
} | ||
|
||
protected function getScreenshotFolder(): string | ||
{ | ||
return $this->dest['screenshot_folder']; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use RuntimeException; | ||
use Symfony\Component\Filesystem\Exception\IOExceptionInterface; | ||
use Symfony\Component\Filesystem\Filesystem; | ||
use Symfony\Component\Mime\MimeTypes; | ||
|
||
trait FileProcessorTrait | ||
{ | ||
private null|MimeTypes $mime = null; | ||
|
||
abstract protected function getFilesystem(): Filesystem; | ||
|
||
protected function getMime(): MimeTypes | ||
{ | ||
if (! isset($this->mime)) { | ||
$this->mime = MimeTypes::getDefault(); | ||
} | ||
return $this->mime; | ||
} | ||
|
||
protected function createDirectoryIfNotExists(string $folder): bool | ||
{ | ||
try { | ||
if (! $this->getFilesystem()->exists($folder)) { | ||
$this->getFilesystem() | ||
->mkdir($folder); | ||
} | ||
} catch (IOExceptionInterface) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @param array<string|null> $components | ||
* @return array{src: string, type: string} | ||
*/ | ||
protected function storeFile(string $data, string $prefixUrl, string $storageFolder, array $components): array | ||
{ | ||
$tempFilename = $this->getFilesystem() | ||
->tempnam($storageFolder, 'pwa-'); | ||
$hash = mb_substr(hash('sha256', $data), 0, 8); | ||
file_put_contents($tempFilename, $data); | ||
$mime = $this->getMime() | ||
->guessMimeType($tempFilename); | ||
$extension = $this->getMime() | ||
->getExtensions($mime); | ||
|
||
if (empty($extension)) { | ||
throw new RuntimeException(sprintf('Unable to guess the extension for the mime type "%s"', $mime)); | ||
} | ||
|
||
$components[] = $hash; | ||
$filename = sprintf('%s.%s', implode('-', $components), $extension[0]); | ||
$localFilename = sprintf('%s/%s', rtrim($storageFolder, '/'), $filename); | ||
|
||
file_put_contents($localFilename, $data); | ||
$this->getFilesystem() | ||
->remove($tempFilename); | ||
|
||
return [ | ||
'src' => sprintf('%s/%s', $prefixUrl, $filename), | ||
'type' => $mime, | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
use function is_int; | ||
|
||
trait IconsSectionProcessorTrait | ||
{ | ||
use ImageSectionProcessorTrait; | ||
|
||
abstract protected function getIconPrefixUrl(): string; | ||
|
||
abstract protected function getIconFolder(): string; | ||
|
||
/** | ||
* @param array{src: string, sizes: array<int>, format: ?string, purpose: ?string} $icons | ||
*/ | ||
protected function processIcons(SymfonyStyle $io, array $icons): array|int | ||
{ | ||
if (! $this->createDirectoryIfNotExists($this->getIconFolder()) || ! $this->checkImageProcessor($io)) { | ||
return Command::FAILURE; | ||
} | ||
$result = []; | ||
foreach ($icons as $icon) { | ||
foreach ($icon['sizes'] as $size) { | ||
if (! is_int($size) || $size < 0) { | ||
$io->error('The icon size must be a positive integer'); | ||
return Command::FAILURE; | ||
} | ||
$data = $this->loadFileAndConvert($icon['src'], $size, $icon['format'] ?? null); | ||
if ($data === null) { | ||
$io->error(sprintf('Unable to read the icon "%s"', $icon['src'])); | ||
return Command::FAILURE; | ||
} | ||
|
||
$iconManifest = $this->storeIcon($data, $size, $icon['purpose'] ?? null); | ||
$result[] = $iconManifest; | ||
} | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* @return array{src: string, sizes: string, type: string, purpose: ?string} | ||
*/ | ||
private function storeIcon(string $data, int $size, ?string $purpose): array | ||
{ | ||
$fileData = $this->storeFile( | ||
$data, | ||
$this->getIconPrefixUrl(), | ||
$this->getIconFolder(), | ||
['icon', $purpose, $size === 0 ? 'any' : $size . 'x' . $size] | ||
); | ||
|
||
return $this->handleSizeAndPurpose($purpose, $size, $fileData); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SpomkyLabs\PwaBundle\Command\SectionProcessor; | ||
|
||
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
|
||
trait ImageSectionProcessorTrait | ||
{ | ||
use FileProcessorTrait; | ||
|
||
abstract protected function getImageProcessor(): ?ImageProcessor; | ||
|
||
protected function handleSizeAndPurpose(?string $purpose, int $size, array $fileData): array | ||
{ | ||
$sizes = $size === 0 ? 'any' : $size . 'x' . $size; | ||
$fileData += [ | ||
'sizes' => $sizes, | ||
]; | ||
|
||
if ($purpose !== null) { | ||
$fileData += [ | ||
'purpose' => $purpose, | ||
]; | ||
} | ||
|
||
return $fileData; | ||
} | ||
|
||
protected function loadFileAndConvert(string $src, ?int $size, ?string $format): ?string | ||
{ | ||
$data = file_get_contents($src); | ||
if ($data === false) { | ||
return null; | ||
} | ||
if ($size !== 0 && $size !== null) { | ||
$data = $this->getImageProcessor() | ||
->process($data, $size, $size, $format); | ||
} | ||
|
||
return $data; | ||
} | ||
|
||
protected function checkImageProcessor(SymfonyStyle $io): bool | ||
{ | ||
if ($this->getImageProcessor() === null) { | ||
$io->error('Image processor not found'); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} |