fix: slash media title/alt on import, keep sideload errors out of client responses
This commit is contained in:
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user