feat: media usage finder and media-get tool

Usage search distinguishes certain (ID referenced) and possible (file URL
only) usages, hides titles the user cannot read, and keeps queries bounded.
MediaGuard checks per-attachment capabilities and refuses trashed media
unless the caller opts in.

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:
2026-09-26 01:54:55 +02:00
co-authored by Claude Opus 5.5
parent 51e8846e1f
commit a2a2da1ae7
14 changed files with 1182 additions and 3 deletions
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\WordPress\PostGuard;
use WP_Error;
use WP_Post;
/**
* Loads an attachment for abilities and checks the capability on that attachment
* (spec §4.5). Every error is ready to be returned from permission()/execute().
*/
final class MediaGuard
{
/**
* Trashed attachments (only possible with MEDIA_TRASH) are refused unless $allowTrashed:
* readers pass it, writers must not (a change or a delete there would be surprising or permanent).
*
* @param string $capability Meta capability checked on the attachment: edit_post, delete_post or read_post.
*/
public static function attachment(
mixed $id,
string $capability = 'edit_post',
bool $allowTrashed = false
): WP_Post|WP_Error {
$attachmentId = PostGuard::id($id);
if ($attachmentId === null) {
return Errors::invalidInput('id fehlt oder ist ungültig.', ['id' => 'positive Ganzzahl erwartet']);
}
$attachment = get_post($attachmentId);
if (! $attachment instanceof WP_Post || $attachment->post_type !== 'attachment') {
return Errors::notFound(
sprintf('Medium %d nicht gefunden.', $attachmentId),
'ID prüfen; Medien über lit-media-search suchen.'
);
}
if (! current_user_can($capability, $attachmentId)) {
return Errors::forbidden(sprintf('Keine Berechtigung für Medium %d.', $attachmentId));
}
if (! $allowTrashed && $attachment->post_status === 'trash') {
return Errors::invalidInput(
sprintf('Medium %d liegt im Papierkorb.', $attachmentId),
['id' => 'Medium liegt im Papierkorb'],
'Medium zuerst in der WordPress-Mediathek wiederherstellen.'
);
}
return $attachment;
}
}