Files
WP-LIT-MCP/src/Integrations/Media/MediaPresenter.php
T
janniclas.loosenandClaude Opus 5.5 b24eb6c3e9 fix(security): no direct HTTP access to plugin PHP files (audit L2, cheap part)
Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:32:12 +02:00

115 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
use LIT\MCP\Abilities\JsonMap;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Output format of an attachment. Not final so abilities can be tested with a mock.
*/
class MediaPresenter
{
/** @return array<string, mixed> */
public function summary(WP_Post $attachment): array
{
$id = (int) $attachment->ID;
$meta = $this->metadata($attachment);
return [
'id' => $id,
'title' => (string) $attachment->post_title,
'filename' => wp_basename((string) get_attached_file($id)),
'mime_type' => (string) $attachment->post_mime_type,
'url' => (string) wp_get_attachment_url($id),
'alt' => (string) get_post_meta($id, '_wp_attachment_image_alt', true),
'width' => isset($meta['width']) ? (int) $meta['width'] : null,
'height' => isset($meta['height']) ? (int) $meta['height'] : null,
'filesize' => isset($meta['filesize']) ? (int) $meta['filesize'] : null,
'date' => (string) $attachment->post_date,
// Optimistic lock for lit/media-update (post_modified_gmt, changes with every row write).
'modified' => (string) $attachment->post_modified_gmt,
];
}
/**
* @param list<array<string, mixed>> $usages From MediaUsageFinder::usages().
* @return array<string, mixed>
*/
public function full(WP_Post $attachment, array $usages): array
{
$id = (int) $attachment->ID;
$sizes = [];
foreach ((array) ($this->metadata($attachment)['sizes'] ?? []) as $name => $size) {
$source = wp_get_attachment_image_src($id, (string) $name);
if (is_array($source)) {
$sizes[(string) $name] = [
'url' => (string) $source[0],
'width' => (int) $source[1],
'height' => (int) $source[2],
];
}
}
return array_merge($this->summary($attachment), [
'caption' => (string) $attachment->post_excerpt,
'description' => (string) $attachment->post_content,
'parent' => (int) $attachment->post_parent,
'sizes' => JsonMap::of($sizes),
'usages' => $usages,
'edit_url' => (string) get_edit_post_link($id, 'raw'),
]);
}
/** @return array<string, mixed> */
public static function summarySchema(): array
{
return [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'title' => ['type' => 'string'],
'filename' => ['type' => 'string'],
'mime_type' => ['type' => 'string'],
'url' => ['type' => 'string'],
'alt' => ['type' => 'string'],
'width' => ['type' => ['integer', 'null']],
'height' => ['type' => ['integer', 'null']],
'filesize' => ['type' => ['integer', 'null']],
'date' => ['type' => 'string'],
'modified' => ['type' => 'string'],
],
'required' => ['id', 'title', 'mime_type', 'url', 'alt'],
];
}
/** @return array<string, mixed> */
public static function fullSchema(): array
{
$schema = self::summarySchema();
$schema['properties'] = array_merge($schema['properties'], [
'caption' => ['type' => 'string'],
'description' => ['type' => 'string'],
'parent' => ['type' => 'integer'],
'sizes' => ['type' => 'object'],
'usages' => ['type' => 'array', 'items' => MediaUsageFinder::usageSchema()],
'edit_url' => ['type' => 'string'],
]);
$schema['required'] = array_merge($schema['required'], ['sizes', 'usages']);
return $schema;
}
/** @return array<string, mixed> */
private function metadata(WP_Post $attachment): array
{
$meta = wp_get_attachment_metadata((int) $attachment->ID);
return is_array($meta) ? $meta : [];
}
}