refreshless-8.x-1.x-dev/src/Value/PageState.php
src/Value/PageState.php
<?php
declare(strict_types=1);
namespace Drupal\refreshless\Value;
use Drupal\Component\Utility\UrlHelper;
use Drupal\refreshless\Value\PageStateInterface;
use function array_intersect_key;
use function implode;
use function is_string;
use function json_decode;
use function sort;
use function sprintf;
use Symfony\Component\HttpFoundation\Request;
/**
* Default implementation of a RefreshLess page state value object.
*/
class PageState implements PageStateInterface {
/**
* Name of the cookie containing the RefreshLess page state.
*/
protected const COOKIE_NAME = 'refreshless-page-state';
/**
* Libraries already on the page, if found in the page state.
*
* @var string[]
*/
protected array $libraries = [];
/**
* The Symfony Request object this value object was constructed from, if any.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected readonly Request $request;
/**
* A CSRF theme token, if one is found in the page state.
*
* @var string
*/
protected string $themeToken = '';
/**
* The theme machine name.
*
* @var string
*/
protected string $theme = '';
/**
* Protected constructor; use static contructor methods instead.
*/
protected function __construct() {}
/**
* {@inheritdoc}
*/
public function getLibraries(): array {
return $this->libraries;
}
/**
* {@inheritdoc}
*/
public function setLibraries(array|string $libraries): self {
if (is_string($libraries)) {
$libraries = UrlHelper::uncompressQueryParameter($libraries);
$libraries = explode(',', $libraries);
}
$this->libraries = $libraries;
return $this;
}
/**
* {@inheritdoc}
*/
public function getThemeToken(): string {
return $this->themeToken;
}
/**
* {@inheritdoc}
*/
public function setThemeToken(string $themeToken): self {
$this->themeToken = $themeToken;
return $this;
}
/**
* {@inheritdoc}
*/
public function getTheme(): string {
return $this->theme;
}
/**
* {@inheritdoc}
*/
public function setTheme(string $theme): self {
$this->theme = $theme;
return $this;
}
/**
* Format the page state into an array suitable for drupalSettings.
*
* @return array
* An array of page state information containing 'libraries', 'theme', and
* 'themeToken' keys.
*/
public function toDrupalSettings(): array {
$libraries = sort($this->getLibraries());
return [
'theme' => $this->getTheme(),
'themeToken' => $this->getThemeToken(),
'libraries' => UrlHelper::compressQueryParameter(implode(
',', $libraries,
)),
];
}
/**
* Create from a provided drupalSettings array.
*
* @param array $settings
* A array containing 'libraries', 'theme', and 'themeToken' keys.
*
* @return self
* A new instance with values set.
*/
public static function fromDrupalSettings(array $settings): self {
$instance = (new self())
->setTheme($settings['theme'])
->setLibraries($settings['libraries']);
// Theme token is optional and only used when on a different theme from the
// default theme.
if (isset($settings['themeToken'])) {
$instance->setThemeToken($settings['themeToken']);
}
return $instance;
}
/**
* {@inheritdoc}
*/
public static function fromCookieValue(string $value): self {
$parsed = array_intersect_key(
json_decode($value, true),
// Allow list of keys from the cookie value.
['libraries' => true, 'theme' => true, 'themeToken' => true]
);
// This avoids duplicate code by delegating to this method.
return self::fromDrupalSettings($parsed);
}
/**
* {@inheritdoc}
*
* @throws \ValueError If the cookie is not present in the request.
*/
public static function fromRequest(Request $request): self {
if (!$request->cookies->has(self::COOKIE_NAME)) {
throw new \ValueError(sprintf(
'The request must contain a "%s" cookie!', self::COOKIE_NAME,
));
}
return self::fromCookieValue($request->cookies->get(self::COOKIE_NAME));
}
/**
* {@inheritdoc}
*/
public function getRequest(): Request {
return $this->request;
}
/**
* {@inheritdoc}
*/
public static function getCookieName(): string {
return self::COOKIE_NAME;
}
}
