diff --git a/README.md b/README.md index 06368002..7b057360 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contr ## Using ~~~ -wp export [--dir=] [--stdout] [--skip_comments] [--max_file_size=] [--filename_format=] [--include_once=] [--start_date=] [--end_date=] [--post_type=] [--post_type__not_in=] [--post__in=] [--with_attachments] [--start_id=] [--max_num_posts=] [--author=] [--category=] [--post_status=] +wp export [--dir=] [--stdout] [--skip_comments] [--max_file_size=] [--filename_format=] [--include_once=] [--allow_orphan_terms] [--start_date=] [--end_date=] [--post_type=] [--post_type__not_in=] [--post__in=] [--with_attachments] [--start_id=] [--max_num_posts=] [--author=] [--category=] [--post_status=] ~~~ Generates one or more WXR files containing authors, terms, posts, @@ -43,6 +43,9 @@ comments, and attachments. WXR files do not include site configuration are categories, tags, nav_menu_items, custom_taxonomies_terms. Separate multiple sections with a comma. Defaults to none. + [--allow_orphan_terms] + Export orphaned terms with `parent=0`, instead of throwing an exception. + **FILTERS** [--start_date=] diff --git a/features/export.feature b/features/export.feature index 71555f89..87a169b7 100644 --- a/features/export.feature +++ b/features/export.feature @@ -996,6 +996,10 @@ Feature: Export content. Then STDOUT should be a number And save STDOUT as {EXPORT_CATEGORY_ID} + When I run `wp term create category National --parent={EXPORT_CATEGORY_ID} --porcelain` + Then STDOUT should be a number + And save STDOUT as {EXPORT_SUBCATEGORY_ID} + When I run `wp term create post_tag Tech --description="Technology-related" --porcelain` Then STDOUT should be a number And save STDOUT as {EXPORT_TAG_ID} @@ -1028,6 +1032,10 @@ Feature: Export content. """ {EXPORT_CATEGORY_ID} """ + And the {EXPORT_FILE} file should contain: + """ + news + """ And the {EXPORT_FILE} file should contain: """ @@ -1104,6 +1112,20 @@ Feature: Export content. """ News """ + And STDOUT should contain: + """ + National + """ + + When I run `wp term get category news --by=slug --field=id` + Then STDOUT should be a number + And save STDOUT as {IMPORT_CATEGORY_ID} + + When I run `wp term get category national --by=slug --field=parent` + Then STDOUT should be: + """ + {IMPORT_CATEGORY_ID} + """ When I run `wp term list post_tag` Then STDOUT should contain: @@ -1148,3 +1170,69 @@ Feature: Export content. """ Test User """ + + @require-wp-5.2 + Scenario: Allow export to proceed when orphaned terms are found + Given a WP install + And I run `wp term create category orphan --parent=1` + And I run `wp term create category parent` + And I run `wp term create category child --parent=3` + And I run `wp term create post_tag atag` + And I run `wp term create post_tag btag` + And I run `wp term create post_tag ctag` + And I run `wp db query "DELETE FROM wp_terms WHERE term_id = 1"` + + When I run `wp export --allow_orphan_terms` + Then save STDOUT 'Writing to file %s' as {EXPORT_FILE} + And the {EXPORT_FILE} file should contain: + """ + orphan + """ + And the {EXPORT_FILE} file should contain: + """ + atag + """ + + When I run `wp site empty --yes` + And I run `wp plugin install wordpress-importer --activate` + And I run `wp import {EXPORT_FILE} --authors=skip` + Then STDOUT should contain: + """ + Success: + """ + + When I run `wp term get post_tag atag --by=slug --field=id` + Then STDOUT should be a number + + When I run `wp term get post_tag btag --by=slug --field=id` + Then STDOUT should be a number + + When I run `wp term get post_tag ctag --by=slug --field=id` + Then STDOUT should be a number + + When I run `wp term get category parent --by=slug --field=id` + Then STDOUT should be a number + And save STDOUT as {EXPORT_CATEGORY_PARENT_ID} + + When I run `wp term get category child --by=slug --field=parent` + Then STDOUT should be: + """ + {EXPORT_CATEGORY_PARENT_ID} + """ + + When I run `wp term get category orphan --by=slug --field=parent` + Then STDOUT should be: + """ + 0 + """ + + Scenario: Throw exception when orphaned terms are found + Given a WP install + And I run `wp term create category orphan --parent=1` + And I run `wp db query "DELETE FROM wp_terms WHERE term_id = 1"` + + When I try `wp export` + Then STDERR should contain: + """ + Error: Term is missing a parent + """ \ No newline at end of file diff --git a/src/Export_Command.php b/src/Export_Command.php index 49610460..e0b063dd 100644 --- a/src/Export_Command.php +++ b/src/Export_Command.php @@ -65,6 +65,9 @@ class Export_Command extends WP_CLI_Command { * are categories, tags, nav_menu_items, custom_taxonomies_terms. Separate multiple * sections with a comma. Defaults to none. * + * [--allow_orphan_terms] + * : Export orphaned terms with `parent=0`, instead of throwing an exception. + * * ## FILTERS * * [--start_date=] @@ -128,23 +131,24 @@ class Export_Command extends WP_CLI_Command { */ public function __invoke( $_, $assoc_args ) { $defaults = [ - 'dir' => null, - 'stdout' => false, - 'start_date' => null, - 'end_date' => null, - 'post_type' => null, - 'post_type__not_in' => null, - 'max_num_posts' => null, - 'author' => null, - 'category' => null, - 'post_status' => null, - 'post__in' => null, - 'with_attachments' => true, // or FALSE if user requested some post__in - 'start_id' => null, - 'skip_comments' => null, - 'max_file_size' => 15, - 'filename_format' => '{site}.wordpress.{date}.{n}.xml', - 'include_once' => null, + 'dir' => null, + 'stdout' => false, + 'start_date' => null, + 'end_date' => null, + 'post_type' => null, + 'post_type__not_in' => null, + 'max_num_posts' => null, + 'author' => null, + 'category' => null, + 'post_status' => null, + 'post__in' => null, + 'with_attachments' => true, // or FALSE if user requested some post__in + 'start_id' => null, + 'skip_comments' => null, + 'max_file_size' => 15, + 'filename_format' => '{site}.wordpress.{date}.{n}.xml', + 'include_once' => null, + 'allow_orphan_terms' => null, ]; if ( ! empty( $assoc_args['stdout'] ) && ( ! empty( $assoc_args['dir'] ) || ! empty( $assoc_args['filename_format'] ) ) ) { @@ -492,4 +496,17 @@ private function check_include_once( $once ) { return true; } + + private function check_allow_orphan_terms( $allow_orphan_terms ) { + if ( null === $allow_orphan_terms ) { + return true; + } + + if ( 0 !== (int) $allow_orphan_terms && 1 !== (int) $allow_orphan_terms ) { + WP_CLI::warning( 'allow_orphan_terms needs to be 0 (no) or 1 (yes).' ); + return false; + } + $this->export_args['allow_orphan_terms'] = $allow_orphan_terms; + return true; + } } diff --git a/src/WP_Export_Query.php b/src/WP_Export_Query.php index 77972bb7..4ddc9656 100644 --- a/src/WP_Export_Query.php +++ b/src/WP_Export_Query.php @@ -11,15 +11,16 @@ class WP_Export_Query { const QUERY_CHUNK = 100; private static $defaults = [ - 'post_ids' => null, - 'post_type' => null, - 'status' => null, - 'author' => null, - 'start_date' => null, - 'end_date' => null, - 'start_id' => null, - 'max_num_posts' => null, - 'category' => null, + 'post_ids' => null, + 'post_type' => null, + 'status' => null, + 'author' => null, + 'start_date' => null, + 'end_date' => null, + 'start_id' => null, + 'max_num_posts' => null, + 'category' => null, + 'allow_orphan_terms' => null, ]; private $post_ids; @@ -97,7 +98,7 @@ public function categories() { } $categories = (array) get_categories( [ 'get' => 'all' ] ); - $this->check_for_orphaned_terms( $categories ); + $categories = $this->process_orphaned_terms( $categories ); $categories = self::topologically_sort_terms( $categories ); @@ -110,7 +111,7 @@ public function tags() { } $tags = (array) get_tags( [ 'get' => 'all' ] ); - $this->check_for_orphaned_terms( $tags ); + $tags = $this->process_orphaned_terms( $tags ); return $tags; } @@ -122,7 +123,7 @@ public function custom_taxonomies_terms() { $custom_taxonomies = get_taxonomies( [ '_builtin' => false ] ); // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found -- Deprecated, but we need to support older versions of WordPress. $custom_terms = (array) get_terms( $custom_taxonomies, [ 'get' => 'all' ] ); - $this->check_for_orphaned_terms( $custom_terms ); + $custom_terms = $this->process_orphaned_terms( $custom_terms ); $custom_terms = self::topologically_sort_terms( $custom_terms ); return $custom_terms; } @@ -356,9 +357,11 @@ private static function topologically_sort_terms( $terms ) { return $sorted; } - private function check_for_orphaned_terms( $terms ) { + private function process_orphaned_terms( $terms ) { + $term_ids = []; $have_parent = []; + $orphans = []; foreach ( $terms as $term ) { $term_ids[ $term->term_id ] = true; @@ -369,10 +372,30 @@ private function check_for_orphaned_terms( $terms ) { foreach ( $have_parent as $has_parent ) { if ( ! isset( $term_ids[ $has_parent->parent ] ) ) { - $this->missing_parents = $has_parent; - throw new WP_Export_Term_Exception( "Term is missing a parent: {$has_parent->slug} ({$has_parent->term_taxonomy_id})" ); + if ( $this->filters['allow_orphan_terms'] ) { + $orphans[ $has_parent->term_id ] = true; + } else { + $this->missing_parents = $has_parent; + throw new WP_Export_Term_Exception( "Term is missing a parent: {$has_parent->slug} ({$has_parent->term_taxonomy_id})" ); + } + } + } + + if ( ! $this->filters['allow_orphan_terms'] ) { + return $terms; + } + + if ( count( $orphans ) > 0 ) { + $terms_return = []; + foreach ( $terms as $term ) { + if ( isset( $orphans[ $term->term_id ] ) ) { + $term->parent = 0; + } + $terms_return[] = $term; } + $terms = $terms_return; } + return $terms; } private static function get_terms_for_post( $post ) {