Skip to content

Commit

Permalink
Add FetchMetadata service and a placeholder (#369)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
spaze authored Jul 24, 2024
2 parents 2b7a268 + e7bc5d3 commit 1702aef
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Formatter\Placeholders;

use MichalSpacekCz\Http\FetchMetadata\FetchMetadata;
use MichalSpacekCz\Http\FetchMetadata\FetchMetadataHeader;
use Override;

/**
* Inserts live `Sec-Fetch-*` (fetch metadata) headers into blog posts for example.
*
* All headers:
* /---
* **FETCH_METADATA:all**
* \---
* Single header:
* /---
* **FETCH_METADATA:Sec-Fetch-Dest**
* \---
* Inline values:
* ''**FETCH_METADATA:Sec-Fetch-Dest**''
*/
readonly class FetchMetadataTexyFormatterPlaceholder implements TexyFormatterPlaceholder
{

public function __construct(
private FetchMetadata $fetchMetadata,
) {
}


#[Override]
public static function getId(): string
{
return 'FETCH_METADATA';
}


#[Override]
public function replace(string $value): string
{
if ($value === 'all') {
$headers = $this->fetchMetadata->getAllHeaders();
} else {
$header = FetchMetadataHeader::from($value);
$headers = [$header->value => $this->fetchMetadata->getHeader($header)];
}
$result = [];
foreach ($headers as $header => $value) {
$result[] = "{$header}: {$value}";
}
return implode("\n", $result);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Formatter;
namespace MichalSpacekCz\Formatter\Placeholders;

interface TexyFormatterPlaceholder
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Formatter;
namespace MichalSpacekCz\Formatter\Placeholders;

use Contributte\Translation\Translator;
use MichalSpacekCz\DateTime\DateTimeFormatter;
Expand Down
1 change: 1 addition & 0 deletions site/app/Formatter/TexyFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Contributte\Translation\Exceptions\InvalidArgument;
use Contributte\Translation\Translator;
use MichalSpacekCz\Formatter\Placeholders\TexyFormatterPlaceholder;
use MichalSpacekCz\Utils\Hash;
use Nette\Utils\Html;
use Nette\Utils\Strings;
Expand Down
1 change: 1 addition & 0 deletions site/app/Formatter/TexyPhraseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use MichalSpacekCz\Application\Locale\LocaleLinkGenerator;
use MichalSpacekCz\Articles\Blog\BlogPostLocaleUrls;
use MichalSpacekCz\Formatter\Exceptions\UnexpectedHandlerInvocationReturnType;
use MichalSpacekCz\Formatter\Placeholders\TrainingDateTexyFormatterPlaceholder;
use MichalSpacekCz\ShouldNotHappenException;
use MichalSpacekCz\Training\TrainingLocales;
use Nette\Application\Application;
Expand Down
35 changes: 35 additions & 0 deletions site/app/Http/FetchMetadata/FetchMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Http\FetchMetadata;

use Nette\Http\IRequest;

readonly class FetchMetadata
{

public function __construct(
private IRequest $httpRequest,
) {
}


public function getHeader(FetchMetadataHeader $header): ?string
{
return $this->httpRequest->getHeader($header->value);
}


/**
* @return array<value-of<FetchMetadataHeader>, string|null>
*/
public function getAllHeaders(): array
{
$headers = [];
foreach (FetchMetadataHeader::cases() as $header) {
$headers[$header->value] = $this->getHeader($header);
}
return $headers;
}

}
14 changes: 14 additions & 0 deletions site/app/Http/FetchMetadata/FetchMetadataHeader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Http\FetchMetadata;

enum FetchMetadataHeader: string
{

case Dest = 'Sec-Fetch-Dest';
case Mode = 'Sec-Fetch-Mode';
case Site = 'Sec-Fetch-Site';
case User = 'Sec-Fetch-User';

}
2 changes: 1 addition & 1 deletion site/app/Test/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public function setMethod(string $method): void

public function setHeader(string $name, string $value): void
{
$this->headers[$name] = $value;
$this->headers[strtolower($name)] = $value;
}


Expand Down
6 changes: 4 additions & 2 deletions site/config/services.neon
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,17 @@ services:
- MichalSpacekCz\Form\TrainingReviewFormFactory
- MichalSpacekCz\Form\UnprotectedFormFactory
- MichalSpacekCz\Form\UpcKeysSsidFormFactory
- MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: typed(MichalSpacekCz\Formatter\TexyFormatterPlaceholder), allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
- MichalSpacekCz\Formatter\Placeholders\TrainingDateTexyFormatterPlaceholder
- MichalSpacekCz\Formatter\Placeholders\FetchMetadataTexyFormatterPlaceholder
- MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: typed(MichalSpacekCz\Formatter\Placeholders\TexyFormatterPlaceholder), allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
texyFormatterNoPlaceholders:
create: MichalSpacekCz\Formatter\TexyFormatter(cache: @texyFormatterPhpFilesAdapter, placeholders: [], allowedLongWords: %texyFormatter.allowed.longWords%, staticRoot: %domain.sharedStaticRoot%, imagesRoot: %domain.imagesRoot%, locationRoot: %domain.locationRoot%)
autowired: false
- MichalSpacekCz\Formatter\TexyPhraseHandler
- MichalSpacekCz\Formatter\TrainingDateTexyFormatterPlaceholder
httpClient: MichalSpacekCz\Http\Client\HttpClient
- MichalSpacekCz\Http\Cookies\CookieDescriptions
- MichalSpacekCz\Http\Cookies\Cookies
- MichalSpacekCz\Http\FetchMetadata\FetchMetadata
- MichalSpacekCz\Http\HttpInput
- MichalSpacekCz\Http\Redirections
- MichalSpacekCz\Http\SecurityHeaders(permissionsPolicy: %permissionsPolicy%)
Expand Down
1 change: 1 addition & 0 deletions site/psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<errorLevel type="suppress">
<referencedClass name="*Presenter" />
<referencedClass name="MichalSpacekCz\CompanyInfo\CompanyRegister*" /> <!-- An array of these is passed to MichalSpacekCz\CompanyInfo\CompanyInfo::__construct() by the DIC -->
<referencedClass name="MichalSpacekCz\Formatter\Placeholders\*" /> <!-- An array of these is passed to MichalSpacekCz\Formatter\TexyFormatter::__construct() by the DIC -->
<referencedClass name="MichalSpacekCz\Test\Http\NullSession" /> <!-- Used in tests.neon -->
<referencedClass name="MichalSpacekCz\Tls\CertificateMonitor" /> <!-- Used in bin/certmonitor.php but can't analyze bin because https://github.com/vimeo/psalm/issues/10143 -->
</errorLevel>
Expand Down
10 changes: 7 additions & 3 deletions site/tests/Formatter/TexyFormatterTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace MichalSpacekCz\Formatter;
use DateTime;
use MichalSpacekCz\Test\Application\ApplicationPresenter;
use MichalSpacekCz\Test\Database\Database;
use MichalSpacekCz\Test\Http\Request;
use MichalSpacekCz\Test\TestCaseRunner;
use Nette\Application\Application;
use Nette\Utils\Html;
Expand All @@ -32,6 +33,7 @@ class TexyFormatterTest extends TestCase
public function __construct(
private readonly TexyFormatter $texyFormatter,
private readonly AdapterInterface $cacheInterface,
Request $httpRequest,
Database $database,
Application $application,
ApplicationPresenter $applicationPresenter,
Expand Down Expand Up @@ -105,21 +107,23 @@ class TexyFormatterTest extends TestCase
'cs_CZ' => 'bezpecnost-php-aplikaci',
'en_US' => 'php-application-security',
]);
$httpRequest->setHeader('Sec-Fetch-Dest', 'iframe');
$this->expectedFormatted = "<strong>foo <a\n"
. "href=\"https://example.com/?dest=%2F%2F%3AWww%3ATrainings%3Atraining&amp;args=bezpecnost-php-aplikaci\">bar</a>\n"
. "<small>(messages.trainings.nextdates: <strong>5.–7. ledna 2020</strong> messages.label.remote, <strong>5.–7. února 2020</strong> Le city 2)</small></strong>";
. "<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"
. "Sec-Fetch-Dest: iframe";
}


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


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


Expand Down
59 changes: 59 additions & 0 deletions site/tests/Http/FetchMetadata/FetchMetadataTest.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
declare(strict_types = 1);

namespace MichalSpacekCz\Http\FetchMetadata;

use MichalSpacekCz\Test\Http\Request;
use MichalSpacekCz\Test\TestCaseRunner;
use Tester\Assert;
use Tester\TestCase;

require __DIR__ . '/../../bootstrap.php';

/** @testCase */
class FetchMetadataTest extends TestCase
{

public function __construct(
private readonly Request $httpRequest,
private readonly FetchMetadata $fetchMetadata,
) {
}


public function testGetHeader(): void
{
$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
Assert::same('document', $this->fetchMetadata->getHeader(FetchMetadataHeader::Dest));
Assert::null($this->fetchMetadata->getHeader(FetchMetadataHeader::Site));
}


public function testGetAllHeaders(): void
{
$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
$expectedHeaders = [
'Sec-Fetch-Dest' => 'document',
'Sec-Fetch-Mode' => null,
'Sec-Fetch-Site' => null,
'Sec-Fetch-User' => null,
];
Assert::same($expectedHeaders, $this->fetchMetadata->getAllHeaders());
Assert::null($this->fetchMetadata->getHeader(FetchMetadataHeader::Site));

$this->httpRequest->setHeader(FetchMetadataHeader::Dest->value, 'document');
$this->httpRequest->setHeader(FetchMetadataHeader::Mode->value, 'navigate');
$this->httpRequest->setHeader(FetchMetadataHeader::Site->value, 'cross-site');
$this->httpRequest->setHeader(FetchMetadataHeader::User->value, '?1');
$expectedHeaders = [
'Sec-Fetch-Dest' => 'document',
'Sec-Fetch-Mode' => 'navigate',
'Sec-Fetch-Site' => 'cross-site',
'Sec-Fetch-User' => '?1',
];
Assert::same($expectedHeaders, $this->fetchMetadata->getAllHeaders());
}

}

TestCaseRunner::run(FetchMetadataTest::class);

0 comments on commit 1702aef

Please sign in to comment.