Files
WP-LIT-MCP/src/Integrations/Divi/Release/DiviReference.php
T

135 lines
4.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use RuntimeException;
defined('ABSPATH') || exit;
/**
* The expected fingerprints (fingerprints.json, shipped with the plugin): the source fingerprint of every mirror under
* its ID, and the signature fingerprint of every Divi call under "sig:<symbol>" (SignatureFingerprint) – plus the
* reference sources of the mirrors (tests/Fixtures/divi/reference/<id>.txt, development only – not in the plugin
* package). Written by wp lit-mcp divi-check --update-reference.
*/
final class DiviReference
{
public const FILE = __DIR__ . '/fingerprints.json';
/** Key prefix of the signature fingerprints; no mirror ID starts with it. */
public const SIGNATURE_PREFIX = 'sig:';
/**
* @return array<string, string> Mirror ID or "sig:<symbol>" => sha256, sorted by key; [] when the file is
* missing or broken.
*/
public static function expected(?string $file = null): array
{
$file ??= self::FILE;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin file.
$raw = is_file($file) && is_readable($file) ? file_get_contents($file) : false;
$data = is_string($raw) ? json_decode($raw, true) : null;
if (! is_array($data)) {
return [];
}
$expected = [];
foreach ($data as $id => $hash) {
if (is_string($id) && is_string($hash)) {
$expected[$id] = $hash;
}
}
ksort($expected);
return $expected;
}
/**
* @param array<string, string> $hashes Mirror ID or "sig:<symbol>" => sha256; written sorted by key.
* @throws RuntimeException
*/
public static function writeExpected(array $hashes, ?string $file = null): void
{
ksort($hashes);
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP, no WordPress.
$json = json_encode($hashes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- dev fixture.
if (! is_string($json) || file_put_contents($file ?? self::FILE, $json . "\n") === false) {
throw new RuntimeException('fingerprints.json not written');
}
}
/** The key of $call's signature fingerprint: "sig:" and the symbol without a leading backslash. */
public static function signatureKey(Call $call): string
{
return self::SIGNATURE_PREFIX . ltrim($call->symbol, '\\');
}
/**
* Signature fingerprints of the calls in the running PHP process (Divi loaded).
*
* @param list<Call>|null $calls null = DiviAdapters::calls().
* @return array<string, string> "sig:<symbol>" => sha256, sorted by key.
* @throws RuntimeException When a symbol is not loaded.
*/
public static function currentSignatures(?array $calls = null): array
{
$signatures = [];
foreach ($calls ?? DiviAdapters::calls() as $call) {
$hash = SignatureFingerprint::of($call);
if ($hash === null) {
throw new RuntimeException(sprintf('signature of %s: symbol is not loaded', $call->label()));
}
$signatures[self::signatureKey($call)] = $hash;
}
ksort($signatures);
return $signatures;
}
/** tests/Fixtures/divi/reference of a development checkout; null in an installed plugin. */
public static function sourceDir(): ?string
{
$dir = dirname(__DIR__, 4) . '/tests/Fixtures/divi/reference';
return is_dir($dir) ? $dir : null;
}
public static function readSource(string $id, string $dir): ?string
{
$file = $dir . '/' . $id . '.txt';
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local dev fixture.
$source = is_file($file) ? file_get_contents($file) : false;
return is_string($source) ? $source : null;
}
/** @throws RuntimeException */
public static function writeSource(string $id, string $source, string $dir): void
{
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- dev fixture.
if (file_put_contents($dir . '/' . $id . '.txt', $source . "\n") === false) {
throw new RuntimeException(sprintf('reference source %s not written', $id));
}
}
/**
* Fingerprints of every mirror in the installed Divi.
*
* @return array<string, SourceFingerprint>
* @throws RuntimeException When a mirrored function cannot be read.
*/
public static function current(string $themeDir): array
{
$prints = [];
foreach (DiviAdapters::mirrors() as $mirror) {
$file = $themeDir . '/' . $mirror->file;
$prints[$mirror->id] = SourceFingerprint::of($file, $mirror->function, $mirror->class);
}
return $prints;
}
}