From 9f5bb66ae9a61e0d30018b16bf65b924dd106984 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 12 Dec 2023 21:35:35 +0100 Subject: [PATCH] Allow referencing other snips in snip --- src/Controller/SnipController.php | 12 ++---- src/Service/SnipParser/Pipeline.php | 11 ++++- .../SnipParser/Stages/HtmlEscapeStage.php | 3 +- .../SnipParser/Stages/ReplaceStage.php | 19 ++++++++ .../SnipParser/Stages/UrlReferenceStage.php | 43 +++++++++++++++++++ templates/snip/single.html.twig | 2 +- 6 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 src/Service/SnipParser/Stages/ReplaceStage.php create mode 100644 src/Service/SnipParser/Stages/UrlReferenceStage.php diff --git a/src/Controller/SnipController.php b/src/Controller/SnipController.php index c74326c..7badff0 100644 --- a/src/Controller/SnipController.php +++ b/src/Controller/SnipController.php @@ -44,25 +44,25 @@ class SnipController extends AbstractController } #[Route('/single/{snip}', name: '_single')] - public function single(Snip $snip): Response + public function single(Snip $snip, Pipeline $pl): Response { $this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip); $snipService = $this->snipServiceFactory->create($snip); return $this->render('snip/single.html.twig', [ 'snip' => $snip, - 'content' => (new Pipeline())->parse($snipService->get()), + 'content' => $pl->parse($snipService->get()), 'branch' => $snipService->getRepo()->getCurrentBranchName(), ]); } #[Route('/raw/{snip}', name: '_raw')] - public function raw(Snip $snip, Request $request): Response + public function raw(Snip $snip, Pipeline $pl, Request $request): Response { $this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip); $response = new Response( - (new Pipeline())->clean($this->snipServiceFactory->create($snip)->get()), + $pl->clean($this->snipServiceFactory->create($snip)->get()), Response::HTTP_OK, ['Content-Type' => 'text/html'] ); @@ -77,10 +77,6 @@ class SnipController extends AbstractController } return $response; -// return $this->render('snip/single.html.twig', [ -// 'snip' => $snip, -// 'content' => $this->snipServiceFactory->create($snip)->get(), -// ]); } #[Route('/edit/{snip}', name: '_edit')] diff --git a/src/Service/SnipParser/Pipeline.php b/src/Service/SnipParser/Pipeline.php index 666ca57..cc0b37f 100644 --- a/src/Service/SnipParser/Pipeline.php +++ b/src/Service/SnipParser/Pipeline.php @@ -3,19 +3,28 @@ namespace App\Service\SnipParser; use App\Service\SnipParser\Stages\HtmlEscapeStage; +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, + ) {} + public function parse(string $payload): string { $builder = new PipelineBuilder(); $pipeline = $builder ->add(new HtmlEscapeStage()) + ->add(new ReplaceStage(PHP_EOL, '
')) ->add(new ReplaceBlocksStage('
', '
', '```')) ->add(new ReplaceBlocksStage('', '', '``')) - ->build(); + ->add($this->referenceStage) + ->build() + ; return $pipeline->process($payload); } diff --git a/src/Service/SnipParser/Stages/HtmlEscapeStage.php b/src/Service/SnipParser/Stages/HtmlEscapeStage.php index 2760c0e..521b784 100644 --- a/src/Service/SnipParser/Stages/HtmlEscapeStage.php +++ b/src/Service/SnipParser/Stages/HtmlEscapeStage.php @@ -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'); } } \ No newline at end of file diff --git a/src/Service/SnipParser/Stages/ReplaceStage.php b/src/Service/SnipParser/Stages/ReplaceStage.php new file mode 100644 index 0000000..f9e4f45 --- /dev/null +++ b/src/Service/SnipParser/Stages/ReplaceStage.php @@ -0,0 +1,19 @@ +search, $this->replace, $payload); + } +} \ No newline at end of file diff --git a/src/Service/SnipParser/Stages/UrlReferenceStage.php b/src/Service/SnipParser/Stages/UrlReferenceStage.php new file mode 100644 index 0000000..fcc18c2 --- /dev/null +++ b/src/Service/SnipParser/Stages/UrlReferenceStage.php @@ -0,0 +1,43 @@ +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) { + dump($matches); + $snip = $this->snipRepository->find($matches[1]); + if ($snip === null) { + return sprintf('%s', $matches[0]); + } + if (!$this->security->isGranted(SnipVoter::VIEW, $snip)) { + return sprintf('%s', $matches[0]); + } + + $url = $this->router->generate('snip_single', ['snip' => $snip->getId()]); + return sprintf('#%s', $url, $snip->getCreatedBy(), $snip->getId()); + }, $payload); + } +} \ No newline at end of file diff --git a/templates/snip/single.html.twig b/templates/snip/single.html.twig index ea93e3a..20d7780 100644 --- a/templates/snip/single.html.twig +++ b/templates/snip/single.html.twig @@ -24,7 +24,7 @@

{{ include('snip/badge.html.twig', {snip: snip}) }} - {{ snip }} + {{ snip }} #{{ snip.id }}

Current branch: {{ branch }}