refactor: ContentMediaReferences holds the pure media matcher (M8)
The block/ID/file-URL matching moves out of MediaUsageFinder into a static class; the finder keeps queries, limits and result building. Behaviour unchanged; direct tests for the matcher added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pure part of MediaUsageFinder: whether a piece of stored content (post content or a
|
||||||
|
* block widget) references an attachment – by ID (wp-image-<id>, [gallery ids], Divi 4
|
||||||
|
* gallery_ids, core media blocks, Divi 5 "id"/"galleryIds") or only by its file URL. A regex
|
||||||
|
* that fails (PCRE limit) is undecided and counts as a possible usage. No database access.
|
||||||
|
*/
|
||||||
|
final class ContentMediaReferences
|
||||||
|
{
|
||||||
|
private const CORE_MEDIA_BLOCKS = ['image', 'gallery', 'cover', 'media-text', 'file', 'video', 'audio'];
|
||||||
|
|
||||||
|
/** Block delimiter pattern of WordPress' block parser (WP_Block_Parser::next_token()). */
|
||||||
|
private const BLOCK_PATTERN = '/<!--\s+wp:(?P<namespace>[a-z][a-z0-9_-]*\/)?(?P<name>[a-z][a-z0-9_-]*)\s+'
|
||||||
|
. '(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*+)?}\s+)?\/?-->/s';
|
||||||
|
|
||||||
|
/** [gallery ids="…"] and Divi 4 gallery_ids="…" – quotes also JSON-escaped inside Divi 5 blocks. */
|
||||||
|
private const ID_LIST_PATTERN = '/(?:\[gallery\b[^\]]*?\bids|\bgallery_ids)=(?:"|\'|\\\\u0022)?([\d,\s]+)/i';
|
||||||
|
|
||||||
|
/** Content a regex could not check (PCRE limit) counts as a possible usage. */
|
||||||
|
public static function certainty(string $content, int $attachmentId, ?string $fileRegex): ?string
|
||||||
|
{
|
||||||
|
$references = self::referencesId($content, $attachmentId);
|
||||||
|
if ($references !== false) {
|
||||||
|
return $references === true ? MediaUsageFinder::CERTAIN : MediaUsageFinder::POSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::matches($fileRegex, $content) === false ? null : MediaUsageFinder::POSSIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return bool|null Null when a regex failed (e.g. backtrack limit) – undecided. */
|
||||||
|
private static function referencesId(string $content, int $attachmentId): ?bool
|
||||||
|
{
|
||||||
|
$wpImage = self::matches('/\bwp-image-' . $attachmentId . '\b/', $content);
|
||||||
|
if ($wpImage !== false) {
|
||||||
|
return $wpImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
$listCount = preg_match_all(self::ID_LIST_PATTERN, $content, $lists);
|
||||||
|
if ($listCount === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ($listCount > 0 && self::inList($lists[1], $attachmentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match_all(self::BLOCK_PATTERN, $content, $blocks, PREG_SET_ORDER) === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
foreach ($blocks as $block) {
|
||||||
|
$attrs = json_decode(trim((string) ($block['attrs'] ?? '')), true);
|
||||||
|
if (! is_array($attrs)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespace = rtrim((string) $block['namespace'], '/');
|
||||||
|
$isCoreMedia = in_array($namespace, ['', 'core'], true)
|
||||||
|
&& in_array($block['name'], self::CORE_MEDIA_BLOCKS, true);
|
||||||
|
if (
|
||||||
|
($isCoreMedia && self::coreBlockReferences($attrs, $attachmentId))
|
||||||
|
|| ($namespace === 'divi' && self::diviAttrsReference($attrs, $attachmentId))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* preg_match() as ?bool: null when the regex failed (backtrack/recursion limit), false
|
||||||
|
* without a pattern.
|
||||||
|
*/
|
||||||
|
public static function matches(?string $pattern, string $subject): ?bool
|
||||||
|
{
|
||||||
|
if ($pattern === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = preg_match($pattern, $subject);
|
||||||
|
|
||||||
|
return $result === false ? null : $result === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<mixed> $attrs */
|
||||||
|
private static function coreBlockReferences(array $attrs, int $attachmentId): bool
|
||||||
|
{
|
||||||
|
foreach (['id', 'mediaId'] as $key) {
|
||||||
|
if (isset($attrs[$key]) && is_numeric($attrs[$key]) && (int) $attrs[$key] === $attachmentId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = is_array($attrs['ids'] ?? null) ? array_filter($attrs['ids'], 'is_numeric') : [];
|
||||||
|
|
||||||
|
return in_array($attachmentId, array_map('intval', $ids), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divi 5 image modules store {"src": url, "id": "<ID>"}, galleries "galleryIds" as "1,2,3"
|
||||||
|
* or as an array of IDs.
|
||||||
|
*
|
||||||
|
* @param array<mixed> $attrs
|
||||||
|
*/
|
||||||
|
private static function diviAttrsReference(array $attrs, int $attachmentId): bool
|
||||||
|
{
|
||||||
|
foreach ($attrs as $key => $value) {
|
||||||
|
if ($key === 'id' && (is_int($value) || is_string($value)) && (string) $value === (string) $attachmentId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($key === 'galleryIds' && self::inList(self::stringLeaves($value), $attachmentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value) && self::diviAttrsReference($value, $attachmentId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<string> String and integer leaves, integers as strings. */
|
||||||
|
public static function stringLeaves(mixed $value): array
|
||||||
|
{
|
||||||
|
if (is_string($value) || is_int($value)) {
|
||||||
|
return [(string) $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
$leaves = [];
|
||||||
|
if (is_array($value)) {
|
||||||
|
array_walk_recursive($value, static function ($leaf) use (&$leaves): void {
|
||||||
|
if (is_string($leaf) || is_int($leaf)) {
|
||||||
|
$leaves[] = (string) $leaf;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $leaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param list<string> $lists Comma-separated ID lists. */
|
||||||
|
public static function inList(array $lists, int $attachmentId): bool
|
||||||
|
{
|
||||||
|
foreach ($lists as $list) {
|
||||||
|
foreach (explode(',', $list) as $item) {
|
||||||
|
$item = trim($item);
|
||||||
|
if ($item !== '' && ctype_digit($item) && (int) $item === $attachmentId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Original or a generated size of the file, with plain or JSON-escaped slashes. */
|
||||||
|
public static function fileRegex(string $stem): string
|
||||||
|
{
|
||||||
|
$separator = '(?:/|\\\\/)';
|
||||||
|
$parts = array_map(static fn (string $part): string => preg_quote($part, '~'), explode('/', $stem));
|
||||||
|
|
||||||
|
return '~' . $separator . implode($separator, $parts)
|
||||||
|
. '(?:-\d+x\d+|-scaled|-rotated|-e\d{10,})*\.[a-z0-9]{2,5}(?![a-z0-9])~i';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,18 +48,9 @@ class MediaUsageFinder
|
|||||||
/** Entries whose content is loaded per query (keeps memory low with large Divi pages). */
|
/** Entries whose content is loaded per query (keeps memory low with large Divi pages). */
|
||||||
private const CONTENT_BATCH = 10;
|
private const CONTENT_BATCH = 10;
|
||||||
|
|
||||||
private const CORE_MEDIA_BLOCKS = ['image', 'gallery', 'cover', 'media-text', 'file', 'video', 'audio'];
|
|
||||||
|
|
||||||
/** Media widgets that store one attachment ID. */
|
/** Media widgets that store one attachment ID. */
|
||||||
private const SINGLE_MEDIA_WIDGETS = ['media_image', 'media_video', 'media_audio'];
|
private const SINGLE_MEDIA_WIDGETS = ['media_image', 'media_video', 'media_audio'];
|
||||||
|
|
||||||
/** Block delimiter pattern of WordPress' block parser (WP_Block_Parser::next_token()). */
|
|
||||||
private const BLOCK_PATTERN = '/<!--\s+wp:(?P<namespace>[a-z][a-z0-9_-]*\/)?(?P<name>[a-z][a-z0-9_-]*)\s+'
|
|
||||||
. '(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*+)?}\s+)?\/?-->/s';
|
|
||||||
|
|
||||||
/** [gallery ids="…"] and Divi 4 gallery_ids="…" – quotes also JSON-escaped inside Divi 5 blocks. */
|
|
||||||
private const ID_LIST_PATTERN = '/(?:\[gallery\b[^\]]*?\bids|\bgallery_ids)=(?:"|\'|\\\\u0022)?([\d,\s]+)/i';
|
|
||||||
|
|
||||||
/** Set by results() when a query of the current collect() run failed. */
|
/** Set by results() when a query of the current collect() run failed. */
|
||||||
private bool $queryFailed = false;
|
private bool $queryFailed = false;
|
||||||
|
|
||||||
@@ -109,7 +100,7 @@ class MediaUsageFinder
|
|||||||
{
|
{
|
||||||
$wpdb = $this->db ?? $GLOBALS['wpdb'];
|
$wpdb = $this->db ?? $GLOBALS['wpdb'];
|
||||||
$stem = $this->fileStem($attachmentId);
|
$stem = $this->fileStem($attachmentId);
|
||||||
$fileRegex = $stem === '' ? null : self::fileRegex($stem);
|
$fileRegex = $stem === '' ? null : ContentMediaReferences::fileRegex($stem);
|
||||||
|
|
||||||
$found = [];
|
$found = [];
|
||||||
$complete = true;
|
$complete = true;
|
||||||
@@ -252,7 +243,9 @@ class MediaUsageFinder
|
|||||||
}
|
}
|
||||||
|
|
||||||
$background = get_theme_mod('background_image');
|
$background = get_theme_mod('background_image');
|
||||||
if (is_string($background) && $background !== '' && self::matches($fileRegex, $background) !== false) {
|
$backgroundMatches = is_string($background) && $background !== ''
|
||||||
|
&& ContentMediaReferences::matches($fileRegex, $background) !== false;
|
||||||
|
if ($backgroundMatches) {
|
||||||
$found['site:background_image'] = self::siteUsage(
|
$found['site:background_image'] = self::siteUsage(
|
||||||
'Hintergrundbild',
|
'Hintergrundbild',
|
||||||
'theme_mod',
|
'theme_mod',
|
||||||
@@ -263,7 +256,7 @@ class MediaUsageFinder
|
|||||||
|
|
||||||
$divi = get_option('et_divi');
|
$divi = get_option('et_divi');
|
||||||
$diviLogo = is_array($divi) && is_string($divi['divi_logo'] ?? null) ? $divi['divi_logo'] : '';
|
$diviLogo = is_array($divi) && is_string($divi['divi_logo'] ?? null) ? $divi['divi_logo'] : '';
|
||||||
if ($diviLogo !== '' && self::matches($fileRegex, $diviLogo) !== false) {
|
if ($diviLogo !== '' && ContentMediaReferences::matches($fileRegex, $diviLogo) !== false) {
|
||||||
$found['site:divi_logo'] = self::siteUsage('Divi-Logo', 'option', 'divi_logo', self::POSSIBLE);
|
$found['site:divi_logo'] = self::siteUsage('Divi-Logo', 'option', 'divi_logo', self::POSSIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,14 +278,17 @@ class MediaUsageFinder
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (self::widgetInstances('media_gallery') as $number => $instance) {
|
foreach (self::widgetInstances('media_gallery') as $number => $instance) {
|
||||||
if (self::inList(self::stringLeaves($instance['ids'] ?? []), $attachmentId)) {
|
$ids = ContentMediaReferences::stringLeaves($instance['ids'] ?? []);
|
||||||
|
if (ContentMediaReferences::inList($ids, $attachmentId)) {
|
||||||
$widgets['media_gallery-' . $number] = self::CERTAIN;
|
$widgets['media_gallery-' . $number] = self::CERTAIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (self::widgetInstances('block') as $number => $instance) {
|
foreach (self::widgetInstances('block') as $number => $instance) {
|
||||||
$content = is_string($instance['content'] ?? null) ? $instance['content'] : '';
|
$content = is_string($instance['content'] ?? null) ? $instance['content'] : '';
|
||||||
$certainty = $content === '' ? null : self::contentCertainty($content, $attachmentId, $fileRegex);
|
$certainty = $content === ''
|
||||||
|
? null
|
||||||
|
: ContentMediaReferences::certainty($content, $attachmentId, $fileRegex);
|
||||||
if ($certainty !== null) {
|
if ($certainty !== null) {
|
||||||
$widgets['block-' . $number] = $certainty;
|
$widgets['block-' . $number] = $certainty;
|
||||||
}
|
}
|
||||||
@@ -332,7 +328,8 @@ class MediaUsageFinder
|
|||||||
foreach (array_chunk($candidates, self::CONTENT_BATCH) as $chunk) {
|
foreach (array_chunk($candidates, self::CONTENT_BATCH) as $chunk) {
|
||||||
$contents = $this->contents($wpdb, array_map(static fn (object $row): int => (int) $row->ID, $chunk));
|
$contents = $this->contents($wpdb, array_map(static fn (object $row): int => (int) $row->ID, $chunk));
|
||||||
foreach ($chunk as $row) {
|
foreach ($chunk as $row) {
|
||||||
$certainty = self::contentCertainty($contents[(int) $row->ID] ?? '', $attachmentId, $fileRegex);
|
$content = $contents[(int) $row->ID] ?? '';
|
||||||
|
$certainty = ContentMediaReferences::certainty($content, $attachmentId, $fileRegex);
|
||||||
if ($certainty !== null) {
|
if ($certainty !== null) {
|
||||||
$this->add($found, $row, 'content', $certainty);
|
$this->add($found, $row, 'content', $certainty);
|
||||||
}
|
}
|
||||||
@@ -416,144 +413,6 @@ class MediaUsageFinder
|
|||||||
return $contents;
|
return $contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Content a regex could not check (PCRE limit) counts as a possible usage. */
|
|
||||||
private static function contentCertainty(string $content, int $attachmentId, ?string $fileRegex): ?string
|
|
||||||
{
|
|
||||||
$references = self::referencesId($content, $attachmentId);
|
|
||||||
if ($references !== false) {
|
|
||||||
return $references === true ? self::CERTAIN : self::POSSIBLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::matches($fileRegex, $content) === false ? null : self::POSSIBLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return bool|null Null when a regex failed (e.g. backtrack limit) – undecided. */
|
|
||||||
private static function referencesId(string $content, int $attachmentId): ?bool
|
|
||||||
{
|
|
||||||
$wpImage = self::matches('/\bwp-image-' . $attachmentId . '\b/', $content);
|
|
||||||
if ($wpImage !== false) {
|
|
||||||
return $wpImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
$listCount = preg_match_all(self::ID_LIST_PATTERN, $content, $lists);
|
|
||||||
if ($listCount === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if ($listCount > 0 && self::inList($lists[1], $attachmentId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match_all(self::BLOCK_PATTERN, $content, $blocks, PREG_SET_ORDER) === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
foreach ($blocks as $block) {
|
|
||||||
$attrs = json_decode(trim((string) ($block['attrs'] ?? '')), true);
|
|
||||||
if (! is_array($attrs)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$namespace = rtrim((string) $block['namespace'], '/');
|
|
||||||
$isCoreMedia = in_array($namespace, ['', 'core'], true)
|
|
||||||
&& in_array($block['name'], self::CORE_MEDIA_BLOCKS, true);
|
|
||||||
if (
|
|
||||||
($isCoreMedia && self::coreBlockReferences($attrs, $attachmentId))
|
|
||||||
|| ($namespace === 'divi' && self::diviAttrsReference($attrs, $attachmentId))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* preg_match() as ?bool: null when the regex failed (backtrack/recursion limit), false
|
|
||||||
* without a pattern.
|
|
||||||
*/
|
|
||||||
private static function matches(?string $pattern, string $subject): ?bool
|
|
||||||
{
|
|
||||||
if ($pattern === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = preg_match($pattern, $subject);
|
|
||||||
|
|
||||||
return $result === false ? null : $result === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param array<mixed> $attrs */
|
|
||||||
private static function coreBlockReferences(array $attrs, int $attachmentId): bool
|
|
||||||
{
|
|
||||||
foreach (['id', 'mediaId'] as $key) {
|
|
||||||
if (isset($attrs[$key]) && is_numeric($attrs[$key]) && (int) $attrs[$key] === $attachmentId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$ids = is_array($attrs['ids'] ?? null) ? array_filter($attrs['ids'], 'is_numeric') : [];
|
|
||||||
|
|
||||||
return in_array($attachmentId, array_map('intval', $ids), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Divi 5 image modules store {"src": url, "id": "<ID>"}, galleries "galleryIds" as "1,2,3"
|
|
||||||
* or as an array of IDs.
|
|
||||||
*
|
|
||||||
* @param array<mixed> $attrs
|
|
||||||
*/
|
|
||||||
private static function diviAttrsReference(array $attrs, int $attachmentId): bool
|
|
||||||
{
|
|
||||||
foreach ($attrs as $key => $value) {
|
|
||||||
if ($key === 'id' && (is_int($value) || is_string($value)) && (string) $value === (string) $attachmentId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($key === 'galleryIds' && self::inList(self::stringLeaves($value), $attachmentId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($value) && self::diviAttrsReference($value, $attachmentId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return list<string> String and integer leaves, integers as strings. */
|
|
||||||
private static function stringLeaves(mixed $value): array
|
|
||||||
{
|
|
||||||
if (is_string($value) || is_int($value)) {
|
|
||||||
return [(string) $value];
|
|
||||||
}
|
|
||||||
|
|
||||||
$leaves = [];
|
|
||||||
if (is_array($value)) {
|
|
||||||
array_walk_recursive($value, static function ($leaf) use (&$leaves): void {
|
|
||||||
if (is_string($leaf) || is_int($leaf)) {
|
|
||||||
$leaves[] = (string) $leaf;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return $leaves;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param list<string> $lists Comma-separated ID lists. */
|
|
||||||
private static function inList(array $lists, int $attachmentId): bool
|
|
||||||
{
|
|
||||||
foreach ($lists as $list) {
|
|
||||||
foreach (explode(',', $list) as $item) {
|
|
||||||
$item = trim($item);
|
|
||||||
if ($item !== '' && ctype_digit($item) && (int) $item === $attachmentId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload-relative path without extension and without WordPress' suffixes for big,
|
* Upload-relative path without extension and without WordPress' suffixes for big,
|
||||||
* rotated or edited images: 2026/09/foto-scaled.jpg → 2026/09/foto.
|
* rotated or edited images: 2026/09/foto-scaled.jpg → 2026/09/foto.
|
||||||
@@ -566,16 +425,6 @@ class MediaUsageFinder
|
|||||||
return (string) preg_replace('/(?:-scaled|-rotated|-e\d{10,})+$/', '', $stem);
|
return (string) preg_replace('/(?:-scaled|-rotated|-e\d{10,})+$/', '', $stem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Original or a generated size of the file, with plain or JSON-escaped slashes. */
|
|
||||||
private static function fileRegex(string $stem): string
|
|
||||||
{
|
|
||||||
$separator = '(?:/|\\\\/)';
|
|
||||||
$parts = array_map(static fn (string $part): string => preg_quote($part, '~'), explode('/', $stem));
|
|
||||||
|
|
||||||
return '~' . $separator . implode($separator, $parts)
|
|
||||||
. '(?:-\d+x\d+|-scaled|-rotated|-e\d{10,})*\.[a-z0-9]{2,5}(?![a-z0-9])~i';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<string, mixed>> $found
|
* @param array<string, array<string, mixed>> $found
|
||||||
* @param list<object> $rows
|
* @param list<object> $rows
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Media;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Media\ContentMediaReferences;
|
||||||
|
use LIT\MCP\Integrations\Media\MediaUsageFinder;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class ContentMediaReferencesTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @return array<string, array{string, ?string}> */
|
||||||
|
public static function contents(): array
|
||||||
|
{
|
||||||
|
$certain = MediaUsageFinder::CERTAIN;
|
||||||
|
$possible = MediaUsageFinder::POSSIBLE;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'wp-image class' => ['<img class="wp-image-33" src="x">', $certain],
|
||||||
|
'other wp-image id' => ['<img class="wp-image-334">', null],
|
||||||
|
'gallery shortcode' => ['[gallery ids="1, 33,5"]', $certain],
|
||||||
|
'divi 4 gallery_ids' => ['[et_pb_gallery gallery_ids="12,33"]', $certain],
|
||||||
|
'core image block' => ['<!-- wp:image {"id":33} /-->', $certain],
|
||||||
|
'core gallery ids' => ['<!-- wp:gallery {"ids":[4,33]} /-->', $certain],
|
||||||
|
'divi 5 image id' => ['<!-- wp:divi/image {"image":{"innerContent":{"desktop":{"value":{"id":"33"}}}}} /-->', $certain],
|
||||||
|
'divi 5 galleryIds' => ['<!-- wp:divi/gallery {"image":{"advanced":{"galleryIds":{"desktop":{"value":"7,33"}}}}} /-->', $certain],
|
||||||
|
'id outside a media block' => ['<!-- wp:paragraph {"id":33} /-->', null],
|
||||||
|
'file url only' => ['<img src="/wp-content/uploads/2026/09/foto-300x200.jpg">', $possible],
|
||||||
|
'json-escaped file url' => ['{"src":"\/uploads\/2026\/09\/foto.jpg"}', $possible],
|
||||||
|
'other file' => ['<img src="/uploads/2026/09/fotos.jpg">', null],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider contents */
|
||||||
|
public function testCertaintyOfStoredContent(string $content, ?string $expected): void
|
||||||
|
{
|
||||||
|
$fileRegex = ContentMediaReferences::fileRegex('2026/09/foto');
|
||||||
|
|
||||||
|
self::assertSame($expected, ContentMediaReferences::certainty($content, 33, $fileRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testListsAndLeaves(): void
|
||||||
|
{
|
||||||
|
self::assertTrue(ContentMediaReferences::inList(['1, 2', ' 33 '], 33));
|
||||||
|
self::assertFalse(ContentMediaReferences::inList(['1,333', 'x33'], 33));
|
||||||
|
self::assertSame(['1', '33', '7'], ContentMediaReferences::stringLeaves(['1', [33, ['7', null, 2.5]]]));
|
||||||
|
self::assertFalse(ContentMediaReferences::matches(null, 'anything'));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user