Skip to content

Commit

Permalink
Version 2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Toflar authored Nov 25, 2024
2 parents ed12897 + 3159901 commit 3019080
Show file tree
Hide file tree
Showing 16 changed files with 412 additions and 22 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
"symfony/mailer": "^5.4 || ^6.0 || ^7.0",
"symfony/mime": "^5.4 || ^6.0 || ^7.0",
"symfony/routing": "^5.4 || ^6.0 || ^7.0",
"symfony/security-core": "^5.4 || ^6.0 || ^7.0",
"symfony/service-contracts": "^1.1 || ^2.0 || ^3.0",
"symfony/translation-contracts": "^2.0 || ^3.0",
Expand Down
10 changes: 10 additions & 0 deletions config/listeners.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Codefog\HasteBundle\Formatter;
use Symfony\Contracts\Translation\TranslatorInterface;
use Terminal42\NotificationCenterBundle\Backend\AutoSuggester;
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemStorage;
use Terminal42\NotificationCenterBundle\Config\ConfigLoader;
use Terminal42\NotificationCenterBundle\EventListener\AdminEmailTokenListener;
use Terminal42\NotificationCenterBundle\EventListener\Backend\BackendMenuListener;
Expand All @@ -17,6 +18,7 @@
use Terminal42\NotificationCenterBundle\EventListener\Backend\DataContainer\MessageListener;
use Terminal42\NotificationCenterBundle\EventListener\Backend\DataContainer\ModuleListener;
use Terminal42\NotificationCenterBundle\EventListener\Backend\DataContainer\NotificationListener;
use Terminal42\NotificationCenterBundle\EventListener\BulkyItemsTokenListener;
use Terminal42\NotificationCenterBundle\EventListener\DbafsMetadataListener;
use Terminal42\NotificationCenterBundle\EventListener\DisableDeliveryListener;
use Terminal42\NotificationCenterBundle\EventListener\DoctrineSchemaListener;
Expand Down Expand Up @@ -100,6 +102,14 @@
])
;

$services->set(BulkyItemsTokenListener::class)
->args([
service(BulkyItemStorage::class),
service(TokenDefinitionFactoryInterface::class),
service('twig'),
])
;

$services->set(DisableDeliveryListener::class);

$services->set(NotificationTypeForModuleListener::class);
Expand Down
20 changes: 20 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemStorage;
use Terminal42\NotificationCenterBundle\BulkyItem\FileItemFactory;
use Terminal42\NotificationCenterBundle\Config\ConfigLoader;
use Terminal42\NotificationCenterBundle\Controller\DownloadBulkyItemController;
use Terminal42\NotificationCenterBundle\Cron\PruneBulkyItemStorageCron;
use Terminal42\NotificationCenterBundle\DependencyInjection\Terminal42NotificationCenterExtension;
use Terminal42\NotificationCenterBundle\Gateway\GatewayRegistry;
Expand All @@ -18,6 +19,8 @@
use Terminal42\NotificationCenterBundle\Token\Definition\Factory\ChainTokenDefinitionFactory;
use Terminal42\NotificationCenterBundle\Token\Definition\Factory\CoreTokenDefinitionFactory;
use Terminal42\NotificationCenterBundle\Token\Definition\Factory\TokenDefinitionFactoryInterface;
use Terminal42\NotificationCenterBundle\Twig\NotificationCenterExtension;
use Terminal42\NotificationCenterBundle\Twig\NotificationCenterRuntime;

return static function (ContainerConfigurator $container): void {
$services = $container->services();
Expand All @@ -31,6 +34,14 @@
])
;

$services->set(DownloadBulkyItemController::class)
->args([
service('uri_signer'),
service(BulkyItemStorage::class),
])
->public()
;

$services->set(GatewayRegistry::class)
->args([
tagged_iterator(Terminal42NotificationCenterExtension::GATEWAY_TAG),
Expand All @@ -57,6 +68,8 @@
$services->set(BulkyItemStorage::class)
->args([
service('contao.filesystem.virtual.'.Terminal42NotificationCenterExtension::BULKY_ITEMS_VFS_NAME),
service('router'),
service('uri_signer'),
])
;

Expand All @@ -72,6 +85,13 @@
])
;

$services->set(NotificationCenterExtension::class);
$services->set(NotificationCenterRuntime::class)
->args([
service(BulkyItemStorage::class),
])
;

$services->set(NotificationCenter::class)
->args([
service('database_connection'),
Expand Down
Empty file added contao/templates/.twig-root
Empty file.
13 changes: 13 additions & 0 deletions contao/templates/notification_center/file_token.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% if format is same as 'html' %}
<ul>
{% for voucher, file in files %}
<li><a href="{{ notification_center_file_url(voucher) }}">{{ file.name }} ({{ file.size|format_bytes }})</a></li>
{% endfor %}
</ul>
{% endif %}

{% if format is same as 'text' %}
{% for voucher, file in files %}
- [{{ file.name }} ({{ file.size|format_bytes }})]({{ notification_center_file_url(voucher) }})
{% endfor %}
{% endif %}
21 changes: 16 additions & 5 deletions src/BulkyItem/BulkyItemStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
use Contao\CoreBundle\Filesystem\ExtraMetadata;
use Contao\CoreBundle\Filesystem\VirtualFilesystemException;
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Uid\Uuid;

class BulkyItemStorage
{
public const VOUCHER_REGEX = '^\d{8}/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$';

public function __construct(
private readonly VirtualFilesystemInterface $filesystem,
private readonly RouterInterface $router,
private readonly UriSigner $uriSigner,
private readonly int $retentionPeriodInDays = 7,
) {
}
Expand Down Expand Up @@ -95,12 +102,16 @@ public function prune(): void
}
}

public static function validateVoucherFormat(string $voucher): bool
public function generatePublicUri(string $voucher, int|null $ttl = null): string
{
if (!preg_match('@^\d{8}/@', $voucher)) {
return false;
}
return $this->uriSigner->sign(
$this->router->generate('nc_bulky_item_download', ['voucher' => $voucher], UrlGeneratorInterface::ABSOLUTE_URL),
time() + $ttl,
);
}

return Uuid::isValid(substr($voucher, 9));
public static function validateVoucherFormat(string $voucher): bool
{
return 1 === preg_match('@'.self::VOUCHER_REGEX.'@', $voucher);
}
}
13 changes: 12 additions & 1 deletion src/ContaoManager/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
use Contao\ManagerPlugin\Routing\RoutingPluginInterface;
use Symfony\Component\Config\Loader\LoaderResolverInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Terminal42\NotificationCenterBundle\Terminal42NotificationCenterBundle;

class Plugin implements BundlePluginInterface
class Plugin implements BundlePluginInterface, RoutingPluginInterface
{
public function getBundles(ParserInterface $parser): array
{
Expand All @@ -20,4 +23,12 @@ public function getBundles(ParserInterface $parser): array
->setLoadAfter([ContaoCoreBundle::class]),
];
}

public function getRouteCollection(LoaderResolverInterface $resolver, KernelInterface $kernel)
{
return $resolver
->resolve(__DIR__.'/../Controller/DownloadBulkyItemController.php', 'attribute')
->load(__DIR__.'/../Controller/DownloadBulkyItemController.php')
;
}
}
51 changes: 51 additions & 0 deletions src/Controller/DownloadBulkyItemController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemStorage;

#[Route('/notifications/download/{voucher}', 'nc_bulky_item_download', requirements: ['voucher' => BulkyItemStorage::VOUCHER_REGEX])]
class DownloadBulkyItemController
{
public function __construct(
private readonly UriSigner $uriSigner,
private readonly BulkyItemStorage $bulkyItemStorage,
) {
}

public function __invoke(Request $request, string $voucher): Response
{
if (!$this->uriSigner->checkRequest($request)) {
throw new NotFoundHttpException();
}

if (!$bulkyItem = $this->bulkyItemStorage->retrieve($voucher)) {
throw new NotFoundHttpException();
}

$stream = $bulkyItem->getContents();

$response = new StreamedResponse(
static function () use ($stream): void {
while (!feof($stream)) {
echo fread($stream, 8192); // Read in chunks of 8 KB
flush();
}
fclose($stream);
},
);

$response->headers->set('Content-Type', 'application/octet-stream');
$response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');

return $response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function load(array $configs, ContainerBuilder $container): void
}

$container->findDefinition(BulkyItemStorage::class)
->setArgument(1, $config['bulky_items_storage']['retention_period'])
->setArgument(3, $config['bulky_items_storage']['retention_period'])
;
}

Expand Down
105 changes: 105 additions & 0 deletions src/EventListener/BulkyItemsTokenListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\EventListener;

use Contao\StringUtil;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemInterface;
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemStorage;
use Terminal42\NotificationCenterBundle\Event\CreateParcelEvent;
use Terminal42\NotificationCenterBundle\Event\GetTokenDefinitionsForNotificationTypeEvent;
use Terminal42\NotificationCenterBundle\Parcel\Parcel;
use Terminal42\NotificationCenterBundle\Parcel\Stamp\BulkyItemsStamp;
use Terminal42\NotificationCenterBundle\Parcel\Stamp\TokenCollectionStamp;
use Terminal42\NotificationCenterBundle\Token\Definition\AnythingTokenDefinition;
use Terminal42\NotificationCenterBundle\Token\Definition\Factory\TokenDefinitionFactoryInterface;
use Terminal42\NotificationCenterBundle\Token\Definition\HtmlTokenDefinition;
use Terminal42\NotificationCenterBundle\Token\Definition\TextTokenDefinition;
use Terminal42\NotificationCenterBundle\Token\Token;
use Twig\Environment;

class BulkyItemsTokenListener
{
public function __construct(
private readonly BulkyItemStorage $bulkyItemStorage,
private readonly TokenDefinitionFactoryInterface $tokenDefinitionFactory,
private readonly Environment $twig,
) {
}

#[AsEventListener]
public function onGetTokenDefinitions(GetTokenDefinitionsForNotificationTypeEvent $event): void
{
$event
->addTokenDefinition($this->tokenDefinitionFactory->create(AnythingTokenDefinition::class, 'file_item_html_*', 'file_item_html_*'))
->addTokenDefinition($this->tokenDefinitionFactory->create(AnythingTokenDefinition::class, 'file_item_text_*', 'file_item_text_*'))
;
}

#[AsEventListener]
public function onCreateParcel(CreateParcelEvent $event): void
{
if (!$event->getParcel()->hasStamp(TokenCollectionStamp::class) || !$event->getParcel()->getStamp(BulkyItemsStamp::class)) {
return;
}

$tokenCollection = $event->getParcel()->getStamp(TokenCollectionStamp::class)->tokenCollection;

foreach ($tokenCollection as $token) {
$items = $this->extractFileItems($token, $event->getParcel()->getStamp(BulkyItemsStamp::class));

if ([] === $items) {
continue;
}

$tokenCollection->addToken($this->createFileToken($event->getParcel(), $token, $items, 'html', HtmlTokenDefinition::class));
$tokenCollection->addToken($this->createFileToken($event->getParcel(), $token, $items, 'text', TextTokenDefinition::class));
}
}

/**
* @param array<string, BulkyItemInterface> $items
*/
private function createFileToken(Parcel $parcel, Token $token, array $items, string $format, string $tokenDefinitionClass): Token
{
$content = $this->twig->render('@Contao/notification_center/file_token.html.twig', [
'files' => $items,
'parcel' => $parcel,
'format' => $format,
]);

$tokenName = 'file_item_'.$format.'_'.$token->getName();

return $this->tokenDefinitionFactory->create($tokenDefinitionClass, $tokenName, $tokenName)
->createToken($tokenName, $content)
;
}

/**
* @return array<string, BulkyItemInterface>
*/
private function extractFileItems(Token $token, BulkyItemsStamp $bulkyItemsStamp): array
{
$possibleVouchers = StringUtil::trimsplit(',', $token->getParserValue());
$items = [];

foreach ($possibleVouchers as $possibleVoucher) {
// Shortcut: Not a possibly bulky item voucher anyway - continue
if (!BulkyItemStorage::validateVoucherFormat($possibleVoucher)) {
continue;
}

if (!$bulkyItemsStamp->has($possibleVoucher)) {
continue;
}

if ($item = $this->bulkyItemStorage->retrieve($possibleVoucher)) {
$items[$possibleVoucher] = $item;
}
}

return $items;
}
}
Loading

0 comments on commit 3019080

Please sign in to comment.