fix: slash media title/alt on import, keep sideload errors out of client responses

This commit is contained in:
2026-09-26 05:06:34 +02:00
parent 648d53cb96
commit 66663daa99
2 changed files with 52 additions and 5 deletions
+14 -4
View File
@@ -188,11 +188,21 @@ class MediaImporter
}
$title = isset($meta['title']) ? sanitize_text_field((string) $meta['title']) : null;
$attachmentId = $this->sideload($name, $tmp, (int) ($meta['post_id'] ?? 0), $title);
// media_handle_sideload() and update_post_meta() expect slashed data (they unslash).
$attachmentId = $this->sideload(
$name,
$tmp,
(int) ($meta['post_id'] ?? 0),
$title === null ? null : wp_slash($title)
);
if ($attachmentId instanceof WP_Error) {
$reason = 'WordPress hat die Datei nicht übernommen: ' . $attachmentId->get_error_message();
// WordPress' message can contain server paths: only for the log, not for the client.
$message = sprintf('[wp-lit-mcp] media sideload failed: %s', $attachmentId->get_error_message());
error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
return Errors::writeFailed($reason);
return Errors::writeFailed(
'WordPress hat die Datei nicht übernommen (z. B. Upload-Verzeichnis nicht beschreibbar).'
);
}
if (isset($meta['alt']) && $meta['alt'] !== '') {
@@ -235,7 +245,7 @@ class MediaImporter
private function saveAlt(int $attachmentId, string $alt): void
{
// update_post_meta() is also false when the value is already stored (alt from IPTC data).
$stored = update_post_meta($attachmentId, self::ALT_KEY, $alt) !== false
$stored = update_post_meta($attachmentId, self::ALT_KEY, wp_slash($alt)) !== false
|| (string) get_post_meta($attachmentId, self::ALT_KEY, true) === $alt;
if ($stored) {
return;
@@ -32,9 +32,15 @@ final class MediaImporterTest extends TestCase
/** @var list<string> */
private array $tempFiles = [];
/** error_log() is a PHP internal and cannot be stubbed; it is redirected to this file. */
private string $errorLog = '';
private string|false $previousErrorLog = false;
protected function setUp(): void
{
parent::setUp();
$this->errorLog = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$this->previousErrorLog = ini_set('error_log', $this->errorLog);
Functions\when('get_option')->justReturn(['upload_mb' => 1]);
Functions\when('wp_tempnam')->alias(function (): string {
$file = (string) tempnam(sys_get_temp_dir(), 'lit-up');
@@ -63,6 +69,10 @@ final class MediaImporterTest extends TestCase
protected function tearDown(): void
{
ini_set('error_log', (string) $this->previousErrorLog);
if (is_file($this->errorLog)) {
unlink($this->errorLog);
}
foreach ($this->tempFiles as $file) {
if (is_file($file)) {
unlink($file);
@@ -181,13 +191,40 @@ final class MediaImporterTest extends TestCase
Functions\when('wp_check_filetype_and_ext')->justReturn(['ext' => 'png', 'type' => 'image/png', 'proper_filename' => false]);
Functions\when('media_handle_sideload')->justReturn(new WP_Error('upload_error', 'Verzeichnis nicht beschreibbar'));
Functions\when('media_handle_sideload')->justReturn(
new WP_Error('upload_error', 'Verzeichnis /var/www/html/wp-content/uploads/2026/09 nicht beschreibbar')
);
$result = $this->importer()->fromBase64(base64_encode('PNGDATA'), 'logo.png', []);
$logged = (string) file_get_contents($this->errorLog);
self::assertSame('lit_write_failed', $result->get_error_code());
self::assertStringContainsString('Verzeichnis nicht beschreibbar', $result->get_error_message());
self::assertStringNotContainsString('/var/www', $result->get_error_message());
self::assertStringNotContainsString('/var/www', (string) json_encode($result->get_error_data()));
self::assertStringContainsString('WordPress hat die Datei nicht übernommen', $result->get_error_message());
self::assertStringContainsString('/var/www/html/wp-content/uploads/2026/09 nicht beschreibbar', $logged);
$this->assertTempFilesRemoved();
}
public function testTitleAndAltTextAreSlashedForWordPress(): void
{
Functions\when('wp_slash')->alias('addslashes');
Functions\when('wp_check_filetype_and_ext')->justReturn(['ext' => 'png', 'type' => 'image/png', 'proper_filename' => false]);
Functions\expect('media_handle_sideload')->once()
->with(Mockery::type('array'), 0, 'C:\\\\Pfad')
->andReturn(55);
Functions\expect('update_post_meta')->once()
->with(55, '_wp_attachment_image_alt', 'Back\\\\slash')
->andReturn(true);
$id = $this->importer()->fromBase64(base64_encode('PNGDATA'), 'logo.png', [
'title' => 'C:\\Pfad',
'alt' => 'Back\\slash',
]);
self::assertSame(55, $id);
}
public function testFailedAltTextDeletesTheNewAttachmentAgain(): void
{
Functions\when('wp_check_filetype_and_ext')->justReturn(['ext' => 'png', 'type' => 'image/png', 'proper_filename' => false]);