" (SignatureFingerprint) – plus the * reference sources of the mirrors (tests/Fixtures/divi/reference/.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 Mirror ID or "sig:" => 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 $hashes Mirror ID or "sig:" => 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|null $calls null = DiviAdapters::calls(). * @return array "sig:" => 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 * @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; } }