Merge branch 'main' into feature/3-git-content-database

This commit is contained in:
Tim
2023-12-16 00:09:56 +01:00
12 changed files with 1461 additions and 1185 deletions

View File

@ -3,19 +3,31 @@
namespace App\Service\SnipParser;
use App\Service\SnipParser\Stages\HtmlEscapeStage;
use App\Service\SnipParser\Stages\IncludeReferenceStage;
use App\Service\SnipParser\Stages\UrlReferenceStage;
use App\Service\SnipParser\Stages\ReplaceBlocksStage;
use App\Service\SnipParser\Stages\ReplaceStage;
use League\Pipeline\PipelineBuilder;
class Pipeline
{
public function __construct(
private readonly UrlReferenceStage $referenceStage,
private readonly IncludeReferenceStage $includeStage,
) {}
public function parse(string $payload): string
{
$builder = new PipelineBuilder();
$pipeline = $builder
->add(new HtmlEscapeStage())
->add(new ReplaceStage(PHP_EOL, '<br>'))
->add(new ReplaceBlocksStage('<pre><code class="hljs">', '</code></pre>', '```'))
->add(new ReplaceBlocksStage('<code class="hljs">', '</code>', '``'))
->build();
->add($this->referenceStage)
->add($this->includeStage)
->build()
;
return $pipeline->process($payload);
}

View File

@ -8,6 +8,7 @@ class HtmlEscapeStage implements StageInterface
{
public function __invoke(mixed $payload): string
{
return htmlspecialchars($payload, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// https://www.php.net/manual/en/function.htmlspecialchars.php
return htmlspecialchars($payload, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Service\SnipParser\Stages;
use App\Repository\SnipRepository;
use App\Security\Voter\SnipVoter;
use App\Service\SnipParser\Pipeline;
use App\Service\SnipServiceFactory;
use League\Pipeline\StageInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class IncludeReferenceStage implements StageInterface
{
public function __construct(
#[Autowire(lazy: true)] private readonly Security $security,
#[Autowire(lazy: true)] private readonly SnipRepository $snipRepository,
#[Autowire(lazy: true)] private readonly SnipServiceFactory $snipServiceFactory,
#[Autowire(lazy: true)] private readonly Pipeline $pipeline,
) {}
public function __invoke(mixed $payload): string
{
return $this->replaceReferences($payload);
}
private function replaceReferences(mixed $payload): string
{
// replaces all references (#n) to other snips with links
$pattern = '/\{\{(\d+)\}\}/';
return preg_replace_callback($pattern, function ($matches) {
$snip = $this->snipRepository->find($matches[1]);
if ($snip === null) {
return sprintf('<span title="not found">%s</span>', $matches[0]);
}
if (!$this->security->isGranted(SnipVoter::VIEW, $snip)) {
return sprintf('<span title="access denied">%s</span>', $matches[0]);
}
return $this->pipeline->parse($this->snipServiceFactory->create($snip)->get());
}, $payload);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Service\SnipParser\Stages;
use League\Pipeline\StageInterface;
class ReplaceStage implements StageInterface
{
// replaces a string with another string
public function __construct(
public readonly string $search,
public readonly string $replace,
) {}
public function __invoke(mixed $payload): string
{
return str_replace($this->search, $this->replace, $payload);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Service\SnipParser\Stages;
use App\Repository\SnipRepository;
use App\Security\Voter\SnipVoter;
use League\Pipeline\StageInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class UrlReferenceStage implements StageInterface
{
public function __construct(
#[Autowire(lazy: true)] private readonly UrlGeneratorInterface $router,
#[Autowire(lazy: true)] private readonly Security $security,
#[Autowire(lazy: true)] private readonly SnipRepository $snipRepository,
) {}
public function __invoke(mixed $payload): string
{
return $this->replaceReferences($payload);
}
private function replaceReferences(mixed $payload): string
{
// replaces all references (#n) to other snips with links
$pattern = '/#(\d+)/';
return preg_replace_callback($pattern, function ($matches) {
$snip = $this->snipRepository->find($matches[1]);
if ($snip === null) {
return sprintf('<span title="not found">%s</span>', $matches[0]);
}
if (!$this->security->isGranted(SnipVoter::VIEW, $snip)) {
return sprintf('<span title="access denied">%s</span>', $matches[0]);
}
$url = $this->router->generate('snip_single', ['snip' => $snip->getId()]);
return sprintf('<a href="%s" title="Owner: %s">#%s</a>', $url, $snip->getCreatedBy(), $snip->getId());
}, $payload);
}
}

View File

@ -11,7 +11,6 @@ use Symfony\Bundle\SecurityBundle\Security;
class SnipServiceFactory
{
public function __construct(
private readonly string $snipBasePath,
private readonly Security $security,