91 lines
3.2 KiB
PHP
91 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Model\ContentFeed;
|
|
|
|
use App\Entity\ContentDocument;
|
|
|
|
/**
|
|
* One entry of the TYPO3 content manifest. Identity is `(type, uid, language)`.
|
|
*
|
|
* `contentUrl`, `breadcrumb` and `detailOf` are set on pages only. `contentUrl` is fetched as
|
|
* given and never constructed: the trailing-slash variant silently serves the whole page.
|
|
*/
|
|
final readonly class ManifestItem
|
|
{
|
|
/**
|
|
* @param list<string> $breadcrumb
|
|
* @param list<array{type: string, uid: int}> $detailOf
|
|
*/
|
|
public function __construct(
|
|
public string $type,
|
|
public int $uid,
|
|
public string $language,
|
|
public ?string $site,
|
|
public ?string $url,
|
|
public \DateTimeImmutable $modified,
|
|
public ?string $contentUrl = null,
|
|
public ?string $title = null,
|
|
public ?string $description = null,
|
|
public array $breadcrumb = [],
|
|
public array $detailOf = [],
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $item
|
|
*/
|
|
public static function fromArray(array $item): self
|
|
{
|
|
if (!is_string($item['type'] ?? null) || !is_int($item['uid'] ?? null) || !is_string($item['language'] ?? null) || !is_string($item['modified'] ?? null)) {
|
|
throw new \InvalidArgumentException('Manifest item without type, uid, language or modified');
|
|
}
|
|
|
|
$modified = self::localTime($item['modified']);
|
|
if (null === $modified) {
|
|
throw new \InvalidArgumentException(sprintf('Manifest item %s:%d has an unparseable modified', $item['type'], $item['uid']));
|
|
}
|
|
|
|
$detailOf = [];
|
|
foreach (is_array($item['detailOf'] ?? null) ? $item['detailOf'] : [] as $ref) {
|
|
if (is_array($ref) && is_string($ref['type'] ?? null) && is_int($ref['uid'] ?? null)) {
|
|
$detailOf[] = ['type' => $ref['type'], 'uid' => $ref['uid']];
|
|
}
|
|
}
|
|
|
|
$string = static fn (mixed $value): ?string => is_string($value) && '' !== $value ? $value : null;
|
|
|
|
return new self(
|
|
type: $item['type'],
|
|
uid: $item['uid'],
|
|
language: $item['language'],
|
|
site: $string($item['site'] ?? null),
|
|
url: $string($item['url'] ?? null),
|
|
modified: $modified,
|
|
contentUrl: $string($item['contentUrl'] ?? null),
|
|
title: $string($item['title'] ?? null),
|
|
description: $string($item['description'] ?? null),
|
|
breadcrumb: array_values(array_filter(is_array($item['breadcrumb'] ?? null) ? $item['breadcrumb'] : [], 'is_string')),
|
|
detailOf: $detailOf,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A feed timestamp in the application's timezone. Doctrine stores datetimes without
|
|
* their offset, so a value kept in TYPO3's +02:00 would never compare equal to itself
|
|
* once reloaded, and every item would look changed on every run.
|
|
*/
|
|
public static function localTime(string $value): ?\DateTimeImmutable
|
|
{
|
|
$date = date_create_immutable($value);
|
|
|
|
return false === $date ? null : $date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
|
}
|
|
|
|
public function key(): string
|
|
{
|
|
return ContentDocument::key($this->type, $this->uid, $this->language);
|
|
}
|
|
}
|