Skip to content

Commit 1702aef

Browse files
authored
Add FetchMetadata service and a placeholder (#369)
So I can add live metadata to blog posts (more like to just one blog post) with placeholders like ``` /--- **FETCH_METADATA:all** **FETCH_METADATA:Sec-Fetch-Dest** \--- ``` or ``` ''**FETCH_METADATA:Sec-Fetch-Dest**'' ``` etc.
2 parents 2b7a268 + e7bc5d3 commit 1702aef

File tree

12 files changed

+181
-8
lines changed

12 files changed

+181
-8
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Formatter\Placeholders;
5+
6+
use MichalSpacekCz\Http\FetchMetadata\FetchMetadata;
7+
use MichalSpacekCz\Http\FetchMetadata\FetchMetadataHeader;
8+
use Override;
9+
10+
/**
11+
* Inserts live `Sec-Fetch-*` (fetch metadata) headers into blog posts for example.
12+
*
13+
* All headers:
14+
* /---
15+
* **FETCH_METADATA:all**
16+
* \---
17+
* Single header:
18+
* /---
19+
* **FETCH_METADATA:Sec-Fetch-Dest**
20+
* \---
21+
* Inline values:
22+
* ''**FETCH_METADATA:Sec-Fetch-Dest**''
23+
*/
24+
readonly class FetchMetadataTexyFormatterPlaceholder implements TexyFormatterPlaceholder
25+
{
26+
27+
public function __construct(
28+
private FetchMetadata $fetchMetadata,
29+
) {
30+
}
31+
32+
33+
#[Override]
34+
public static function getId(): string
35+
{
36+
return 'FETCH_METADATA';
37+
}
38+
39+
40+
#[Override]
41+
public function replace(string $value): string
42+
{
43+
if ($value === 'all') {
44+
$headers = $this->fetchMetadata->getAllHeaders();
45+
} else {
46+
$header = FetchMetadataHeader::from($value);
47+
$headers = [$header->value => $this->fetchMetadata->getHeader($header)];
48+
}
49+
$result = [];
50+
foreach ($headers as $header => $value) {
51+
$result[] = "{$header}: {$value}";
52+
}
53+
return implode("\n", $result);
54+
}
55+
56+
}

site/app/Formatter/TexyFormatterPlaceholder.php renamed to site/app/Formatter/Placeholders/TexyFormatterPlaceholder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
declare(strict_types = 1);
33

4-
namespace MichalSpacekCz\Formatter;
4+
namespace MichalSpacekCz\Formatter\Placeholders;
55

66
interface TexyFormatterPlaceholder
77
{

site/app/Formatter/TrainingDateTexyFormatterPlaceholder.php renamed to site/app/Formatter/Placeholders/TrainingDateTexyFormatterPlaceholder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
declare(strict_types = 1);
33

4-
namespace MichalSpacekCz\Formatter;
4+
namespace MichalSpacekCz\Formatter\Placeholders;
55

66
use Contributte\Translation\Translator;
77
use MichalSpacekCz\DateTime\DateTimeFormatter;

site/app/Formatter/TexyFormatter.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use Contributte\Translation\Exceptions\InvalidArgument;
77
use Contributte\Translation\Translator;
8+
use MichalSpacekCz\Formatter\Placeholders\TexyFormatterPlaceholder;
89
use MichalSpacekCz\Utils\Hash;
910
use Nette\Utils\Html;
1011
use Nette\Utils\Strings;

site/app/Formatter/TexyPhraseHandler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use MichalSpacekCz\Application\Locale\LocaleLinkGenerator;
88
use MichalSpacekCz\Articles\Blog\BlogPostLocaleUrls;
99
use MichalSpacekCz\Formatter\Exceptions\UnexpectedHandlerInvocationReturnType;
10+
use MichalSpacekCz\Formatter\Placeholders\TrainingDateTexyFormatterPlaceholder;
1011
use MichalSpacekCz\ShouldNotHappenException;
1112
use MichalSpacekCz\Training\TrainingLocales;
1213
use Nette\Application\Application;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Http\FetchMetadata;
5+
6+
use Nette\Http\IRequest;
7+
8+
readonly class FetchMetadata
9+
{
10+
11+
public function __construct(
12+
private IRequest $httpRequest,
13+
) {
14+
}
15+
16+
17+
public function getHeader(FetchMetadataHeader $header): ?string
18+
{
19+
return $this->httpRequest->getHeader($header->value);
20+
}
21+
22+
23+
/**
24+
* @return array<value-of<FetchMetadataHeader>, string|null>
25+
*/
26+
public function getAllHeaders(): array
27+
{
28+
$headers = [];
29+
foreach (FetchMetadataHeader::cases() as $header) {
30+
$headers[$header->value] = $this->getHeader($header);
31+
}
32+
return $headers;
33+
}
34+
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Http\FetchMetadata;
5+
6+
enum FetchMetadataHeader: string
7+
{
8+
9+
case Dest = 'Sec-Fetch-Dest';
10+
case Mode = 'Sec-Fetch-Mode';
11+
case Site = 'Sec-Fetch-Site';
12+
case User = 'Sec-Fetch-User';
13+
14+
}

site/app/Test/Http/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ public function setMethod(string $method): void
212212

213213
public function setHeader(string $name, string $value): void
214214
{
215-
$this->headers[$name] = $value;
215+
$this->headers[strtolower($name)] = $value;
216216
}
217217

218218

site/config/services.neon

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,17 @@ services:
7070
- MichalSpacekCz\Form\TrainingReviewFormFactory
7171
- MichalSpacekCz\Form\UnprotectedFormFactory
7272
- MichalSpacekCz\Form\UpcKeysSsidFormFactory
73-
- MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: typed(MichalSpacekCz\Formatter\TexyFormatterPlaceholder), allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
73+
- MichalSpacekCz\Formatter\Placeholders\TrainingDateTexyFormatterPlaceholder
74+
- MichalSpacekCz\Formatter\Placeholders\FetchMetadataTexyFormatterPlaceholder
75+
- MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: typed(MichalSpacekCz\Formatter\Placeholders\TexyFormatterPlaceholder), allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
7476
texyFormatterNoPlaceholders:
7577
create: MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: [], allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
7678
autowired: false
7779
- MichalSpacekCz\Formatter\TexyPhraseHandler
78-
- MichalSpacekCz\Formatter\TrainingDateTexyFormatterPlaceholder
7980
httpClient: MichalSpacekCz\Http\Client\HttpClient
8081
- MichalSpacekCz\Http\Cookies\CookieDescriptions
8182
- MichalSpacekCz\Http\Cookies\Cookies
83+
- MichalSpacekCz\Http\FetchMetadata\FetchMetadata
8284
- MichalSpacekCz\Http\HttpInput
8385
- MichalSpacekCz\Http\Redirections
8486
- MichalSpacekCz\Http\SecurityHeaders(permissionsPolicy: %permissionsPolicy%)

site/psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
<errorLevel type="suppress">
109109
<referencedClass name="*Presenter" />
110110
<referencedClass name="MichalSpacekCz\CompanyInfo\CompanyRegister*" /> <!-- An array of these is passed to MichalSpacekCz\CompanyInfo\CompanyInfo::__construct() by the DIC -->
111+
<referencedClass name="MichalSpacekCz\Formatter\Placeholders\*" /> <!-- An array of these is passed to MichalSpacekCz\Formatter\TexyFormatter::__construct() by the DIC -->
111112
<referencedClass name="MichalSpacekCz\Test\Http\NullSession" /> <!-- Used in tests.neon -->
112113
<referencedClass name="MichalSpacekCz\Tls\CertificateMonitor" /> <!-- Used in bin/certmonitor.php but can't analyze bin because https://github.com/vimeo/psalm/issues/10143 -->
113114
</errorLevel>

site/tests/Formatter/TexyFormatterTest.phpt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace MichalSpacekCz\Formatter;
99
use DateTime;
1010
use MichalSpacekCz\Test\Application\ApplicationPresenter;
1111
use MichalSpacekCz\Test\Database\Database;
12+
use MichalSpacekCz\Test\Http\Request;
1213
use MichalSpacekCz\Test\TestCaseRunner;
1314
use Nette\Application\Application;
1415
use Nette\Utils\Html;
@@ -32,6 +33,7 @@ class TexyFormatterTest extends TestCase
3233
public function __construct(
3334
private readonly TexyFormatter $texyFormatter,
3435
private readonly AdapterInterface $cacheInterface,
36+
Request $httpRequest,
3537
Database $database,
3638
Application $application,
3739
ApplicationPresenter $applicationPresenter,
@@ -105,21 +107,23 @@ class TexyFormatterTest extends TestCase
105107
'cs_CZ' => 'bezpecnost-php-aplikaci',
106108
'en_US' => 'php-application-security',
107109
]);
110+
$httpRequest->setHeader('Sec-Fetch-Dest', 'iframe');
108111
$this->expectedFormatted = "<strong>foo <a\n"
109112
. "href=\"https://example.com/?dest=%2F%2F%3AWww%3ATrainings%3Atraining&amp;args=bezpecnost-php-aplikaci\">bar</a>\n"
110-
. "<small>(messages.trainings.nextdates: <strong>5.–7. ledna 2020</strong> messages.label.remote, <strong>5.–7. února 2020</strong> Le city 2)</small></strong>";
113+
. "<small>(messages.trainings.nextdates: <strong>5.–7. ledna 2020</strong> messages.label.remote, <strong>5.–7. února 2020</strong> Le city 2)</small></strong>\n"
114+
. "Sec-Fetch-Dest: iframe";
111115
}
112116

113117

114118
public function testFormat(): void
115119
{
116-
Assert::same($this->expectedFormatted, $this->texyFormatter->format('**foo "bar":[training:' . self::TRAINING_ACTION . ']**')->toHtml());
120+
Assert::same($this->expectedFormatted, $this->texyFormatter->format('**foo "bar":[training:' . self::TRAINING_ACTION . "]**\n''**FETCH_METADATA:Sec-Fetch-Dest**''")->toHtml());
117121
}
118122

119123

120124
public function testFormatBlock(): void
121125
{
122-
Assert::same("<p>{$this->expectedFormatted}</p>\n", $this->texyFormatter->formatBlock('**foo "bar":[training:' . self::TRAINING_ACTION . ']**')->toHtml());
126+
Assert::same("<p>{$this->expectedFormatted}</p>\n", $this->texyFormatter->formatBlock('**foo "bar":[training:' . self::TRAINING_ACTION . "]**\n''**FETCH_METADATA:Sec-Fetch-Dest**''")->toHtml());
123127
}
124128

125129

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Http\FetchMetadata;
5+
6+
use MichalSpacekCz\Test\Http\Request;
7+
use MichalSpacekCz\Test\TestCaseRunner;
8+
use Tester\Assert;
9+
use Tester\TestCase;
10+
11+
require __DIR__ . '/../../bootstrap.php';
12+
13+
/** @testCase */
14+
class FetchMetadataTest extends TestCase
15+
{
16+
17+
public function __construct(
18+
private readonly Request $httpRequest,
19+
private readonly FetchMetadata $fetchMetadata,
20+
) {
21+
}
22+
23+
24+
public function testGetHeader(): void
25+
{
26+
$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
27+
Assert::same('document', $this->fetchMetadata->getHeader(FetchMetadataHeader::Dest));
28+
Assert::null($this->fetchMetadata->getHeader(FetchMetadataHeader::Site));
29+
}
30+
31+
32+
public function testGetAllHeaders(): void
33+
{
34+
$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
35+
$expectedHeaders = [
36+
'Sec-Fetch-Dest' => 'document',
37+
'Sec-Fetch-Mode' => null,
38+
'Sec-Fetch-Site' => null,
39+
'Sec-Fetch-User' => null,
40+
];
41+
Assert::same($expectedHeaders, $this->fetchMetadata->getAllHeaders());
42+
Assert::null($this->fetchMetadata->getHeader(FetchMetadataHeader::Site));
43+
44+
$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
45+
$this->httpRequest->setHeader(FetchMetadataHeader::Mode->value, 'navigate');
46+
$this->httpRequest->setHeader(FetchMetadataHeader::Site->value, 'cross-site');
47+
$this->httpRequest->setHeader(FetchMetadataHeader::User->value, '?1');
48+
$expectedHeaders = [
49+
'Sec-Fetch-Dest' => 'document',
50+
'Sec-Fetch-Mode' => 'navigate',
51+
'Sec-Fetch-Site' => 'cross-site',
52+
'Sec-Fetch-User' => '?1',
53+
];
54+
Assert::same($expectedHeaders, $this->fetchMetadata->getAllHeaders());
55+
}
56+
57+
}
58+
59+
TestCaseRunner::run(FetchMetadataTest::class);

0 commit comments

Comments
 (0)