Skip to content

Commit a115aad

Browse files
authored
Fix Mastodon import for archives with nested root folder (#2581)
1 parent c918bfd commit a115aad

File tree

3 files changed

+73
-4
lines changed

3 files changed

+73
-4
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: fixed
3+
4+
Mastodon importer now unpacks nested archives instead of getting confused by the extra folder.

includes/wp-admin/import/class-mastodon.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,15 @@ public static function import() {
225225

226226
// Unzip package to working directory.
227227
\unzip_file( $file, self::$archive );
228-
$files = $wp_filesystem->dirlist( self::$archive );
228+
self::maybe_unwrap_archive();
229229

230-
if ( ! isset( $files['outbox.json'] ) ) {
230+
if ( ! $wp_filesystem->exists( self::$archive . '/outbox.json' ) ) {
231231
echo '<p><strong>' . \esc_html( $error_message ) . '</strong><br />';
232232
echo \esc_html__( 'The archive does not contain an Outbox file, please try again.', 'activitypub' ) . '</p>';
233+
return;
233234
}
234235

235-
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
236-
self::$outbox = \json_decode( \file_get_contents( self::$archive . '/outbox.json' ), true );
236+
self::$outbox = \json_decode( $wp_filesystem->get_contents( self::$archive . '/outbox.json' ), true );
237237

238238
\wp_suspend_cache_invalidation();
239239
\wp_defer_term_counting( true );
@@ -434,4 +434,32 @@ private static function prepend_archive_path( $attachment ) {
434434

435435
return $attachment;
436436
}
437+
438+
/**
439+
* Detect and unwrap single nested directory in archive.
440+
*
441+
* Some Mastodon exports wrap all files in a root folder. This method
442+
* detects this pattern and updates the archive path to point inside it.
443+
*/
444+
private static function maybe_unwrap_archive() {
445+
global $wp_filesystem;
446+
447+
$files = $wp_filesystem->dirlist( self::$archive );
448+
449+
// Check if there's exactly one directory at root level.
450+
if ( count( $files ) !== 1 ) {
451+
return;
452+
}
453+
454+
$first = reset( $files );
455+
if ( 'd' !== $first['type'] ) {
456+
return;
457+
}
458+
459+
// Check if outbox.json exists inside the nested directory.
460+
$nested_path = self::$archive . '/' . $first['name'];
461+
if ( $wp_filesystem->exists( $nested_path . '/outbox.json' ) ) {
462+
self::$archive = $nested_path;
463+
}
464+
}
437465
}

tests/phpunit/tests/includes/wp-admin/import/class-test-mastodon.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,41 @@ public function test_import_posts_calls_filter_hook() {
856856

857857
remove_filter( 'activitypub_import_mastodon_post_data', $filter_callback );
858858
}
859+
860+
/**
861+
* Test maybe_unwrap_archive() unwraps nested folder structure.
862+
*/
863+
public function test_maybe_unwrap_archive_with_nested_folder() {
864+
\WP_Filesystem();
865+
global $wp_filesystem;
866+
867+
// Create temp directory structure: archive/nested-folder/outbox.json.
868+
$temp_dir = \get_temp_dir() . 'activitypub-test-' . \uniqid();
869+
$nested_dir = $temp_dir . '/nested-folder';
870+
871+
\wp_mkdir_p( $nested_dir );
872+
$wp_filesystem->put_contents( $nested_dir . '/outbox.json', '{}' );
873+
874+
// Set self::$archive via Reflection.
875+
$reflection = new ReflectionClass( Mastodon::class );
876+
877+
$archive_property = $reflection->getProperty( 'archive' );
878+
if ( \PHP_VERSION_ID < 80100 ) {
879+
$archive_property->setAccessible( true );
880+
}
881+
$archive_property->setValue( null, $temp_dir );
882+
883+
// Call private method via Reflection.
884+
$method = $reflection->getMethod( 'maybe_unwrap_archive' );
885+
if ( \PHP_VERSION_ID < 80100 ) {
886+
$method->setAccessible( true );
887+
}
888+
$method->invoke( null );
889+
890+
// Assert archive path was updated to nested folder.
891+
$this->assertSame( $nested_dir, $archive_property->getValue() );
892+
893+
// Cleanup.
894+
$wp_filesystem->delete( $temp_dir, true );
895+
}
859896
}

0 commit comments

Comments
 (0)