Skip to content
28 changes: 28 additions & 0 deletions features/export.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,34 @@ Feature: Export content.
Error: Term is missing a parent
"""

Scenario: Export a site and skip the authors
Given a WP install

When I run `wp export --skip_authors`
Then save STDOUT 'Writing to file %s' as {EXPORT_FILE}
And the {EXPORT_FILE} file should not contain:
"""
<wp:author>
"""

Scenario: Export a site and skip the terms
Given a WP install

When I run `wp export --skip_terms`
Then save STDOUT 'Writing to file %s' as {EXPORT_FILE}
And the {EXPORT_FILE} file should not contain:
"""
<wp:term>
"""
And the {EXPORT_FILE} file should not contain:
"""
<wp:category>
"""
And the {EXPORT_FILE} file should not contain:
"""
<wp:tag>
"""

@require-wp-5.2 @require-mysql
Scenario: Export posts with future status
Given a WP install
Expand Down
57 changes: 57 additions & 0 deletions src/Export_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Export_Command extends WP_CLI_Command {
private $max_file_size;
private $include_once;
private $wxr_path;
private $exclude = [];

/**
* Exports WordPress content to a WXR file.
Expand All @@ -51,6 +52,12 @@ class Export_Command extends WP_CLI_Command {
* [--skip_comments]
* : Don't include comments in the WXR export file.
*
* [--skip_authors]
* : Don't include authors in the WXR export file.
*
* [--skip_terms]
* : Don't include terms (categories, tags, custom taxonomy terms and nav menu terms) in the WXR export file.
*
* [--max_file_size=<MB>]
* : A single export file should have this many megabytes. -1 for unlimited.
* ---
Expand Down Expand Up @@ -145,6 +152,8 @@ public function __invoke( $_, $assoc_args ) {
'with_attachments' => true, // or FALSE if user requested some post__in
'start_id' => null,
'skip_comments' => null,
'skip_authors' => null,
'skip_terms' => null,
'max_file_size' => 15,
'filename_format' => '{site}.wordpress.{date}.{n}.xml',
'include_once' => null,
Expand All @@ -169,6 +178,27 @@ public function __invoke( $_, $assoc_args ) {
$defaults['with_attachments']
);

$this->export_args['skip_authors'] = Utils\get_flag_value(
$assoc_args,
'skip_authors',
$defaults['skip_authors']
);

$this->export_args['skip_terms'] = Utils\get_flag_value(
$assoc_args,
'skip_terms',
$defaults['skip_terms']
);

// Re-calculate exclusions after validation to ensure consistency.
$this->exclude = [];
if ( $this->export_args['skip_authors'] ) {
$this->exclude[] = 'authors';
}
if ( $this->export_args['skip_terms'] ) {
$this->exclude = array_merge( $this->exclude, array( 'categories', 'tags', 'nav_menu_terms', 'custom_taxonomies_terms' ) );
}

if ( ! function_exists( 'wp_export' ) ) {
self::load_export_api();
}
Expand Down Expand Up @@ -204,6 +234,7 @@ static function ( $file_path ) {
'destination_directory' => $this->wxr_path,
'filename_template' => self::get_filename_template( $assoc_args['filename_format'] ),
'include_once' => $this->include_once,
'exclude' => $this->exclude,
],
]
);
Expand Down Expand Up @@ -468,6 +499,32 @@ private function check_skip_comments( $skip ) {
return true;
}

private function check_skip_authors( $skip ) {
if ( null === $skip ) {
return true;
}

if ( 0 !== (int) $skip && 1 !== (int) $skip ) {
WP_CLI::warning( 'skip_authors needs to be 0 (no) or 1 (yes).' );
return false;
}
$this->export_args['skip_authors'] = $skip;
return true;
}

private function check_skip_terms( $skip ) {
if ( null === $skip ) {
return true;
}

if ( 0 !== (int) $skip && 1 !== (int) $skip ) {
WP_CLI::warning( 'skip_terms needs to be 0 (no) or 1 (yes).' );
return false;
}
$this->export_args['skip_terms'] = $skip;
return true;
}

private function check_max_file_size( $size ) {
if ( ! is_numeric( $size ) ) {
WP_CLI::warning( 'max_file_size should be numeric.' );
Expand Down
11 changes: 10 additions & 1 deletion src/WP_Export_Split_Files_Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,18 @@ public function __construct( $formatter, $writer_args = [] ) {
$this->subsequent_sections = array_diff( $this->available_sections, $writer_args['include_once'] );
}

if ( ! empty( $writer_args['exclude'] ) ) {
foreach ( $writer_args['exclude'] as $exclude ) {
$key = array_search( $exclude, $this->available_sections, true );
if ( false !== $key ) {
unset( $this->available_sections[ $key ] );
}
}
}
Comment on lines +48 to +55
Copy link

Copilot AI Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exclusion logic removes sections from $this->available_sections after it has already been used to calculate $this->subsequent_sections (line 45). This means excluded sections will still be present in subsequent files when using --include_once. The exclusion logic should be moved before line 44 to ensure excluded sections are properly removed from both $this->available_sections and $this->subsequent_sections.

Copilot uses AI. Check for mistakes.

$this->destination_directory = $writer_args['destination_directory'];
$this->filename_template = $writer_args['filename_template'];
$this->before_posts_xml = $this->formatter->before_posts();
$this->before_posts_xml = $this->formatter->before_posts( $this->available_sections );
$this->after_posts_xml = $this->formatter->after_posts();
}

Expand Down