refactor: Kern ohne Divi – Ports BuilderDetector, MediaUsageSource, EnvironmentComponent und ausgeschlossene Post-Typen im ContentSchema; Divi-Implementierungen und Verdrahtung über DiviIntegration/Plugin; CoreIndependenceTest

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 21:49:28 +02:00
co-authored by Claude Sonnet 5.5
parent abeb4b0eed
commit 06148fb8c1
33 changed files with 952 additions and 154 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
defined('ABSPATH') || exit;
/** The stored file of an attachment, as far as usage checks need it. */
final class AttachmentFile
{
/**
* Upload-relative path without extension and without WordPress' suffixes for big, rotated or edited images:
* 2026/09/foto-scaled.jpg → 2026/09/foto. '' when the attachment has no file.
*/
public static function stem(int $attachmentId): string
{
$file = ltrim((string) get_post_meta($attachmentId, '_wp_attached_file', true), '/');
$stem = (string) preg_replace('/\.[^.\/]+$/', '', $file);
return (string) preg_replace('/(?:-scaled|-rotated|-e\d{10,})+$/', '', $stem);
}
}
+12 -3
View File
@@ -17,8 +17,13 @@ defined('ABSPATH') || exit;
*/
final class MediaIntegration implements Integration
{
public function __construct(private ContentSchema $schema, private ChangeLog $log, private Limits $limits)
{
/** @param list<MediaUsageSource> $usageSources Further places an attachment can be used (e.g. the Divi logo). */
public function __construct(
private ContentSchema $schema,
private ChangeLog $log,
private Limits $limits,
private array $usageSources = []
) {
}
public function id(): string
@@ -39,7 +44,11 @@ final class MediaIntegration implements Integration
public function abilities(): array
{
$presenter = new MediaPresenter();
$finder = new MediaUsageFinder(fn (): array => $this->schema->mediaFieldKeys());
$finder = new MediaUsageFinder(
fn (): array => $this->schema->mediaFieldKeys(),
null,
$this->usageSources
);
$importer = new MediaImporter($this->limits);
$uploads = new MediaUploads($presenter, $this->log, $this->schema);
+18 -26
View File
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
use Closure;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\WordPress\PostStatuses;
use LIT\MCP\Log;
@@ -59,9 +58,14 @@ class MediaUsageFinder
/**
* @param Closure(): list<string> $mediaMetaKeys Keys of file/image fields (ContentSchema::mediaFieldKeys()).
* @param list<MediaUsageSource> $sources Further site-wide places (e.g. the Divi logo), asked after the
* core options and theme mods.
*/
public function __construct(private Closure $mediaMetaKeys, private ?\wpdb $db = null)
{
public function __construct(
private Closure $mediaMetaKeys,
private ?\wpdb $db = null,
private array $sources = []
) {
}
/**
@@ -102,7 +106,7 @@ class MediaUsageFinder
private function collect(int $attachmentId, int $max, int $limit): array
{
$wpdb = $this->db ?? $GLOBALS['wpdb'];
$stem = $this->fileStem($attachmentId);
$stem = AttachmentFile::stem($attachmentId);
$fileRegex = $stem === '' ? null : ContentMediaReferences::fileRegex($stem);
$found = [];
@@ -257,14 +261,10 @@ class MediaUsageFinder
);
}
$diviLogo = DesignSystemReader::diviLogo();
if ($diviLogo !== '' && ContentMediaReferences::matches($fileRegex, $diviLogo) !== false) {
$found['site:divi_logo'] = self::siteUsage(
'Divi-Logo',
'option',
'divi_logo',
self::POSSIBLE
);
foreach ($this->sources as $source) {
foreach ($source->usagesOf($attachmentId) as $usage) {
$found['site:' . $usage['where']] = $usage;
}
}
$this->addWidgetUsages($found, $attachmentId, $fileRegex);
@@ -420,18 +420,6 @@ class MediaUsageFinder
return $contents;
}
/**
* Upload-relative path without extension and without WordPress' suffixes for big,
* rotated or edited images: 2026/09/foto-scaled.jpg → 2026/09/foto.
*/
private function fileStem(int $attachmentId): string
{
$file = ltrim((string) get_post_meta($attachmentId, '_wp_attached_file', true), '/');
$stem = (string) preg_replace('/\.[^.\/]+$/', '', $file);
return (string) preg_replace('/(?:-scaled|-rotated|-e\d{10,})+$/', '', $stem);
}
/**
* @param array<string, array<string, mixed>> $found
* @param list<object> $rows
@@ -470,8 +458,12 @@ class MediaUsageFinder
];
}
/** @return array{post_id: int, title: string, post_type: string, where: string, certainty: string} */
private static function siteUsage(string $title, string $type, string $where, string $certainty): array
/**
* A site-wide usage (post_id 0): option, theme mod, widget or a MediaUsageSource's place.
*
* @return array{post_id: int, title: string, post_type: string, where: string, certainty: string}
*/
public static function siteUsage(string $title, string $type, string $where, string $certainty): array
{
return ['post_id' => 0, 'title' => $title, 'post_type' => $type, 'where' => $where, 'certainty' => $certainty];
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
defined('ABSPATH') || exit;
/**
* Port: a site-wide place outside the media library's own knowledge where an attachment can be used (e.g. the
* logo in Divi's theme options). MediaUsageFinder asks every source it is given after the core options and
* theme mods; the integration that owns the place implements it, Plugin wires it in.
*/
interface MediaUsageSource
{
/**
* @return list<array{post_id: int, title: string, post_type: string, where: string, certainty: string}>
* Usages of attachment $attachmentId in the format of MediaUsageFinder::usages(); use
* MediaUsageFinder::siteUsage() for site-wide ones (post_id 0). "where" names the place and is
* unique per source (it keys the usage, so a place is reported once).
*/
public function usagesOf(int $attachmentId): array;
}