From 630666503b92e7126182b3c47d4eab28639d17f7 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sat, 10 Nov 2012 10:50:55 +0200 Subject: [PATCH 01/56] First pass as an export API --- wp-includes/default-constants.php | 12 ++ wp-includes/export/class-wp-wxr-export.php | 118 ++++++++++++++++++ .../export/class-wp-wxr-xml-generator.php | 89 +++++++++++++ wp-includes/export/writers.php | 114 +++++++++++++++++ wp-settings.php | 3 + 5 files changed, 336 insertions(+) create mode 100644 wp-includes/export/class-wp-wxr-export.php create mode 100644 wp-includes/export/class-wp-wxr-xml-generator.php create mode 100644 wp-includes/export/writers.php diff --git a/wp-includes/default-constants.php b/wp-includes/default-constants.php index bc6c79a80b38..9bdc8bfb2fef 100644 --- a/wp-includes/default-constants.php +++ b/wp-includes/default-constants.php @@ -87,6 +87,18 @@ function wp_initial_constants() { define( 'DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS ); define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS ); define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); + define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); + + // Constants for expressing human-readable data sizes + // in their respective number of bytes. + define( 'KB_IN_BYTES', 1024 ); + define( 'MB_IN_BYTES', 1024 * KB_IN_BYTES ); + define( 'GB_IN_BYTES', 1024 * MB_IN_BYTES ); + define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES ); + define( 'PB_IN_BYTES', 1024 * TB_IN_BYTES ); + define( 'EB_IN_BYTES', 1024 * PB_IN_BYTES ); + define( 'ZB_IN_BYTES', 1024 * EB_IN_BYTES ); + define( 'YB_IN_BYTES', 1024 * ZB_IN_BYTES ); } /** diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php new file mode 100644 index 000000000000..24dc99732887 --- /dev/null +++ b/wp-includes/export/class-wp-wxr-export.php @@ -0,0 +1,118 @@ + null, 'post_type' => null, 'post_status' => null, ); + + private $post_ids; + private $filters; + private $xml_gen; + + public function __construct( $filters ) { + $this->filters = wp_parse_args( $filters, self::$defaults ); + $this->post_ids = $this->calculate_post_ids(); + } + + public function post_ids() { + return $this->post_ids; + } + + public function get_xml() { + return $this->export( 'WP_WXR_XML_Returner' ); + } + + public function export_to_xml_file( $file_name ) { + return $this->export( 'WP_WXR_XML_File_Writer', $file_name ); + } + + public function export() { + $args = func_get_args(); + $writer_class_name = array_shift( $args ); + $writer_args = $args; + $xml_generator = new WP_WXR_XML_Generator( $this ); + array_unshift( $writer_args, $xml_generator ); + $writer_class = new ReflectionClass( $writer_class_name ); + $writer = $writer_class->newInstanceArgs( $writer_args ); + try { + return $writer->export(); + } catch ( WP_WXR_Exception $e ) { + return new WP_Error( 'wxr-error', $e->getMessage() ); + } + } + + public function charset() { + return get_bloginfo( 'charset' ); + } + + public function site_metadata() { + $metadata = array( + 'name' => $this->bloginfo_rss( 'name' ), + 'url' => $this->bloginfo_rss( 'url' ), + 'language' => $this->bloginfo_rss( 'language' ), + 'description' => $this->bloginfo_rss( 'description' ), + 'pubDate' => date( 'D, d M Y H:i:s +0000' ), + 'site_url' => is_multisite()? network_home_url() : $this->bloginfo_rss( 'url' ), + 'blog_url' => $this->bloginfo_rss( 'url' ), + ); + return $metadata; + } + + public function wp_generator_tag() { + return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' ); + } + + private function bloginfo_rss( $section ) { + return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); + } + + private function calculate_post_ids() { + global $wpdb; + if ( is_array( $this->filters['post_ids'] ) ) { + return $this->filters['post_ids']; + } + $join = ''; + $wheres = array(); + + $wheres[] = $this->post_type_where(); + $wheres[] = $this->post_status_where(); + + $where = implode( ' AND ', $wheres ); + if ( $where ) $where = "WHERE $where"; + return $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); + } + + private function post_type_where() { + global $wpdb; + $post_types_filters = array( 'can_export' => true ); + if ( $this->filters['post_type'] ) { + $post_types_filters += array( 'name' => $this->filters['post_type'] ); + } + $post_types = get_post_types( $post_types_filters ); + if ( !$post_types ) { + return 'p.post_type IS NULL'; + } + return $this->build_IN_condition( 'p.post_type', $post_types ); + } + + private function post_status_where() { + global $wpdb; + if ( !$this->filters['post_status'] ) + return "p.post_status != 'auto-draft'"; + return $wpdb->prepare( 'p.post_status = %s', $this->filters['post_status'] ); + } + + private function build_IN_condition( $column_name, $values ) { + global $wpdb; + if ( !is_array( $values ) || empty( $values ) ) { + return false; + } + $esses = implode( ', ', array_fill( 0, count( $values ), '%s' ) ); + return $wpdb->prepare( "$column_name IN ($esses)", $values ); + } +} + +class WP_WXR_Exception extends RuntimeException { +} diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php new file mode 100644 index 000000000000..0ee4f3299136 --- /dev/null +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -0,0 +1,89 @@ +export = $export; + } + + static function get_parts() { + return array( 'header', 'site_metadata', 'authors', 'categories', 'tags', 'nav_menu_terms', 'other_terms', 'posts', 'footer', ); + } + + function header() { + $wxr_version = WXR_VERSION; + $charset = $this->export->charset(); + $wp_generator_tag = $this->export->wp_generator_tag(); + return << + + + + + + + + + + + + + + + + +$wp_generator_tag + + + +XML; + } + + function site_metadata() { + $metadata = $this->export->site_metadata(); + return <<{$metadata['name']} + + + + + + + + +XML; + } + + function authors() { + } + + function categories() { + } + + function tags() { + } + + function nav_menu_terms() { + } + + function other_terms() { + } + + function posts() { + return array( 'a', 'b' ); + } + + function footer() { + return << + +XML; + } +} diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php new file mode 100644 index 000000000000..7707a3e04f4d --- /dev/null +++ b/wp-includes/export/writers.php @@ -0,0 +1,114 @@ +xml_generator = $xml_generator; + } + + public function export() { + $this->export_before_posts(); + $this->export_posts(); + $this->export_after_posts(); + } + + protected function export_before_posts() { + $this->write( $this->xml_generator->header() ); + $this->write( $this->xml_generator->site_metadata() ); + $this->write( $this->xml_generator->authors() ); + $this->write( $this->xml_generator->categories() ); + $this->write( $this->xml_generator->tags() ); + $this->write( $this->xml_generator->nav_menu_terms() ); + $this->write( $this->xml_generator->other_terms() ); + } + + protected function export_posts() { + foreach( $this->xml_generator->posts() as $post_in_wxr ) { + $this->write( $post_in_wxr ); + } + } + + protected function export_after_posts() { + $this->write( $this->xml_generator->footer() ); + } + + abstract protected function write( $xml ); +} + +class WP_WXR_XML_Returner extends WP_WXR_XML_Base_Writer { + private $result = ''; + + public function export() { + $this->private = ''; + parent::export(); + return $this->result; + } + protected function write( $xml ) { + $this->result .= $xml; + } +} + +class WP_WXR_XML_File_Writer extends WP_WXR_XML_Base_Writer { + private $f; + private $file_name; + + public function __construct( $xml_generator, $file_name ) { + $this->file_name = $file_name; + parent::__construct( $xml_generator ); + } + + public function export() { + $this->f = fopen( $this->file_name, 'w' ); + if ( !$this->f ) { + throw new WP_WXR_Exception( sprintf( __( 'WXR Export: error opening %s for writing.' ), $this->ile_name ) ); + } + parent::export(); + fclose( $this->f ); + } + + protected function write( $xml ) { + $res = fwrite( $this->f, $xml); + if ( false === $res ) { + throw new WP_WXR_Exception( __( 'WXR Export: error writing to export file.' ) ); + } + } +} + +class WP_WXR_XML_Split_XML_Files_Writer extends WP_WXR_XML_Base_Writer { + private $result = ''; + + function __construct( $export, $destination_directory, $filename_template, $max_file_size_in_bytes = null ) { + $this->max_file_size_in_bytes = is_null( $max_file_size_in_bytes ) ? 15 * MB_IN_BYTES : $max_file_size_in_bytes; + } + + private function export_() { + $before_posts = $this->get_xml_from_method( 'export_before_posts' ); + $after_posts = $this->get_xml_from_method( 'export_after_posts' ); + $size_of_non_posts = strlen( $before_posts ) + strlen( $after_posts ); + $wxr = $before_posts; + foreach( $this->xml_generator->posts() as $post ) { + $post_wxr = $this->xml_generator->post( $post ); + if ( strlen( $post_wxr ) + $size_of_non_posts > $this->max_file_size_in_bytes ) { + $this->write_next_file( $wxr . $after_posts ); + $wxr = ''; + } + $wxr .= $post_wxr; + } + $this->write_next_file( $wxr . $after_posts ); + } + + private function get_xml_from_method( $method_name ) { + $this->result = ''; + $this->accumulate = true; + $this->$method_name(); + $this->accumulate = false; + } + + function write( $s ) { + if ( $this->accumulate ) { + $this->result = $s; + } else { + fwrite( $this->f, $s ); + } + } +} diff --git a/wp-settings.php b/wp-settings.php index 553ec2476c64..20b609c03375 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -126,6 +126,9 @@ require( ABSPATH . WPINC . '/comment.php' ); require( ABSPATH . WPINC . '/comment-template.php' ); require( ABSPATH . WPINC . '/rewrite.php' ); +require( ABSPATH . WPINC . '/export/class-wp-wxr-export.php' ); +require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); +require( ABSPATH . WPINC . '/export/writers.php' ); require( ABSPATH . WPINC . '/feed.php' ); require( ABSPATH . WPINC . '/bookmark.php' ); require( ABSPATH . WPINC . '/bookmark-template.php' ); From a2c2b753f42565d2274662a2f0d1ae2329dec69a Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 17:38:12 +0200 Subject: [PATCH 02/56] Allow WP_WXR_Export to be init without arguments It assumes we want everything in this case. --- wp-includes/export/class-wp-wxr-export.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 24dc99732887..3abf81dc038f 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -11,7 +11,7 @@ class WP_WXR_Export { private $filters; private $xml_gen; - public function __construct( $filters ) { + public function __construct( $filters = array( 'all' => true ) ) { $this->filters = wp_parse_args( $filters, self::$defaults ); $this->post_ids = $this->calculate_post_ids(); } From cc3c940acc430a8cbcec8412650cc3d5349343ff Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 17:56:45 +0200 Subject: [PATCH 03/56] Rename export() to export_using_writer_class() export() was too generic and suggested many people should use it. --- wp-includes/export/class-wp-wxr-export.php | 29 ++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 3abf81dc038f..faafc5c723ad 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -1,6 +1,6 @@ export( 'WP_WXR_XML_Returner' ); + return $this->export_using_writer_class( 'WP_WXR_XML_Returner' ); } public function export_to_xml_file( $file_name ) { - return $this->export( 'WP_WXR_XML_File_Writer', $file_name ); + return $this->export_using_writer_class( 'WP_WXR_XML_File_Writer', array( $file_name ) ); } - public function export() { - $args = func_get_args(); - $writer_class_name = array_shift( $args ); - $writer_args = $args; + /** + * Exports the current data using a specific export writer class + * + * You should use this method only when you need to export using a + * custom writer. For built-in writers, please see the other public + * methods like get_xml(), export_to_xml_file(), etc. + * + * Example: + * $export = new WP_WXR_Export(…); + * $export->export( 'WP_WXR_CSV_Writer', array( '/home/baba/baba.csv', ';' ); + * + * @param string $writer_class_name The name of the PHP class representing the writer + * @param mixed[] $writer_args Optional additional arguments with which to call the writer constructor + */ + public function export_using_writer_class( $writer_class_name, $writer_args = array() ) { $xml_generator = new WP_WXR_XML_Generator( $this ); array_unshift( $writer_args, $xml_generator ); $writer_class = new ReflectionClass( $writer_class_name ); $writer = $writer_class->newInstanceArgs( $writer_args ); + return $this->export_using_writer( $writer ); + } + + private function export_using_writer( $writer ) { try { return $writer->export(); } catch ( WP_WXR_Exception $e ) { From dde464673b8052b08ef2026109562d0b6a46205d Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 18:11:16 +0200 Subject: [PATCH 04/56] Remove XML from writer's names Writing XML isn't part of their core responsibilities, so we don't need to clutter their names. --- wp-includes/export/class-wp-wxr-export.php | 4 ++-- wp-includes/export/writers.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index faafc5c723ad..81352c7be5fa 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -21,11 +21,11 @@ public function post_ids() { } public function get_xml() { - return $this->export_using_writer_class( 'WP_WXR_XML_Returner' ); + return $this->export_using_writer_class( 'WP_WXR_Returner' ); } public function export_to_xml_file( $file_name ) { - return $this->export_using_writer_class( 'WP_WXR_XML_File_Writer', array( $file_name ) ); + return $this->export_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); } /** diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 7707a3e04f4d..e45bbb93b804 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -1,5 +1,5 @@ Date: Sun, 11 Nov 2012 18:26:04 +0200 Subject: [PATCH 05/56] Filter out empty WHERE clauses If any of the *_where() methods returns an empty condition it breaks the SQL query, because we add en extra AND for it. --- wp-includes/export/class-wp-wxr-export.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 81352c7be5fa..8c57328666fa 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -94,7 +94,7 @@ private function calculate_post_ids() { $wheres[] = $this->post_type_where(); $wheres[] = $this->post_status_where(); - $where = implode( ' AND ', $wheres ); + $where = implode( ' AND ', array_filter( $wheres ) ); if ( $where ) $where = "WHERE $where"; return $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); } From 47b711a2137b3a90f4bdd54de3c3b841c89cefc0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 18:38:12 +0200 Subject: [PATCH 06/56] Make the defaults array more readable Split into multiple lines, since we will be adding more to it. --- wp-includes/export/class-wp-wxr-export.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 8c57328666fa..28508d0e47a5 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -5,7 +5,11 @@ * An immutable object, which gathers all data needed for the export. */ class WP_WXR_Export { - private static $defaults = array( 'post_ids' => null, 'post_type' => null, 'post_status' => null, ); + private static $defaults = array( + 'post_ids' => null, + 'post_type' => null, + 'post_status' => null, + ); private $post_ids; private $filters; From d2531ba34a94520c859ac6437829135d3c59565e Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 18:40:57 +0200 Subject: [PATCH 07/56] Rename post_status to status We don't need to match the post member var and introduce extra specificity. --- wp-includes/export/class-wp-wxr-export.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 28508d0e47a5..0df783b66bf0 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -8,7 +8,7 @@ class WP_WXR_Export { private static $defaults = array( 'post_ids' => null, 'post_type' => null, - 'post_status' => null, + 'status' => null, ); private $post_ids; @@ -96,7 +96,7 @@ private function calculate_post_ids() { $wheres = array(); $wheres[] = $this->post_type_where(); - $wheres[] = $this->post_status_where(); + $wheres[] = $this->status_where(); $where = implode( ' AND ', array_filter( $wheres ) ); if ( $where ) $where = "WHERE $where"; @@ -116,11 +116,11 @@ private function post_type_where() { return $this->build_IN_condition( 'p.post_type', $post_types ); } - private function post_status_where() { + private function status_where() { global $wpdb; - if ( !$this->filters['post_status'] ) + if ( !$this->filters['status'] ) return "p.post_status != 'auto-draft'"; - return $wpdb->prepare( 'p.post_status = %s', $this->filters['post_status'] ); + return $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); } private function build_IN_condition( $column_name, $values ) { From 9195adcc7881f35021e945e9f632d79a5fe1368a Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 18:50:27 +0200 Subject: [PATCH 08/56] Filter by author --- wp-includes/export/class-wp-wxr-export.php | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 0df783b66bf0..09f95821a720 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -9,6 +9,7 @@ class WP_WXR_Export { 'post_ids' => null, 'post_type' => null, 'status' => null, + 'author' => null, ); private $post_ids; @@ -97,6 +98,7 @@ private function calculate_post_ids() { $wheres[] = $this->post_type_where(); $wheres[] = $this->status_where(); + $wheres[] = $this->author_where(); $where = implode( ' AND ', array_filter( $wheres ) ); if ( $where ) $where = "WHERE $where"; @@ -123,6 +125,16 @@ private function status_where() { return $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); } + private function author_where() { + global $wpdb; + $user = $this->find_user_from_any_object( $this->filters['author'] ); + if ( !$user || is_wp_error( $user ) ) { + return false; + } + return $wpdb->prepare( 'p.post_author = %d', $user->ID ); + + } + private function build_IN_condition( $column_name, $values ) { global $wpdb; if ( !is_array( $values ) || empty( $values ) ) { @@ -131,6 +143,17 @@ private function build_IN_condition( $column_name, $values ) { $esses = implode( ', ', array_fill( 0, count( $values ), '%s' ) ); return $wpdb->prepare( "$column_name IN ($esses)", $values ); } + + private function find_user_from_any_object( $user ) { + if ( is_numeric( $user ) ) + return get_user_by( 'id', $user ); + elseif ( is_string( $user ) ) { + return get_user_by( 'login', $user ); + } elseif ( isset( $user->ID ) ) { + return get_user_by( 'id', $user->ID ); + } + return false; + } } class WP_WXR_Exception extends RuntimeException { From 597542e35dcd233e8e73c6ea2499fcd0fe029590 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 19:54:28 +0200 Subject: [PATCH 09/56] Move public methods on top and order them sanely Place the most used ones on top. --- wp-includes/export/class-wp-wxr-export.php | 67 +++++++++++----------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 09f95821a720..115444f01478 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -21,10 +21,6 @@ public function __construct( $filters = array( 'all' => true ) ) { $this->post_ids = $this->calculate_post_ids(); } - public function post_ids() { - return $this->post_ids; - } - public function get_xml() { return $this->export_using_writer_class( 'WP_WXR_Returner' ); } @@ -33,34 +29,8 @@ public function export_to_xml_file( $file_name ) { return $this->export_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); } - /** - * Exports the current data using a specific export writer class - * - * You should use this method only when you need to export using a - * custom writer. For built-in writers, please see the other public - * methods like get_xml(), export_to_xml_file(), etc. - * - * Example: - * $export = new WP_WXR_Export(…); - * $export->export( 'WP_WXR_CSV_Writer', array( '/home/baba/baba.csv', ';' ); - * - * @param string $writer_class_name The name of the PHP class representing the writer - * @param mixed[] $writer_args Optional additional arguments with which to call the writer constructor - */ - public function export_using_writer_class( $writer_class_name, $writer_args = array() ) { - $xml_generator = new WP_WXR_XML_Generator( $this ); - array_unshift( $writer_args, $xml_generator ); - $writer_class = new ReflectionClass( $writer_class_name ); - $writer = $writer_class->newInstanceArgs( $writer_args ); - return $this->export_using_writer( $writer ); - } - - private function export_using_writer( $writer ) { - try { - return $writer->export(); - } catch ( WP_WXR_Exception $e ) { - return new WP_Error( 'wxr-error', $e->getMessage() ); - } + public function post_ids() { + return $this->post_ids; } public function charset() { @@ -84,8 +54,26 @@ public function wp_generator_tag() { return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' ); } - private function bloginfo_rss( $section ) { - return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); + /** + * Exports the current data using a specific export writer class + * + * You should use this method only when you need to export using a + * custom writer. For built-in writers, please see the other public + * methods like get_xml(), export_to_xml_file(), etc. + * + * Example: + * $export = new WP_WXR_Export(…); + * $export->export( 'WP_WXR_CSV_Writer', array( '/home/baba/baba.csv', ';' ); + * + * @param string $writer_class_name The name of the PHP class representing the writer + * @param mixed[] $writer_args Optional additional arguments with which to call the writer constructor + */ + public function export_using_writer_class( $writer_class_name, $writer_args = array() ) { + $xml_generator = new WP_WXR_XML_Generator( $this ); + array_unshift( $writer_args, $xml_generator ); + $writer_class = new ReflectionClass( $writer_class_name ); + $writer = $writer_class->newInstanceArgs( $writer_args ); + return $this->export_using_writer( $writer ); } private function calculate_post_ids() { @@ -132,7 +120,18 @@ private function author_where() { return false; } return $wpdb->prepare( 'p.post_author = %d', $user->ID ); + } + + private function export_using_writer( $writer ) { + try { + return $writer->export(); + } catch ( WP_WXR_Exception $e ) { + return new WP_Error( 'wxr-error', $e->getMessage() ); + } + } + private function bloginfo_rss( $section ) { + return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); } private function build_IN_condition( $column_name, $values ) { From 49ccd5c1c73f89a1fc26101480d05ab18ea4380f Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 20:16:27 +0200 Subject: [PATCH 10/56] Add start/end date filtering --- wp-includes/export/class-wp-wxr-export.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 115444f01478..850c11a540ec 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -10,6 +10,8 @@ class WP_WXR_Export { 'post_type' => null, 'status' => null, 'author' => null, + 'start_date' => null, + 'end_date' => null, ); private $post_ids; @@ -87,6 +89,8 @@ private function calculate_post_ids() { $wheres[] = $this->post_type_where(); $wheres[] = $this->status_where(); $wheres[] = $this->author_where(); + $wheres[] = $this->start_date_where(); + $wheres[] = $this->end_date_where(); $where = implode( ' AND ', array_filter( $wheres ) ); if ( $where ) $where = "WHERE $where"; @@ -122,6 +126,24 @@ private function author_where() { return $wpdb->prepare( 'p.post_author = %d', $user->ID ); } + private function start_date_where() { + global $wpdb; + $timestamp = strtotime( $this->filters['start_date'] ); + if ( !$timestamp ) { + return false; + } + return $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) ); + } + + private function end_date_where() { + global $wpdb; + $timestamp = strtotime( $this->filters['end_date'] ); + if ( !$timestamp ) { + return false; + } + return $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); + } + private function export_using_writer( $writer ) { try { return $writer->export(); From 09359b505b8fb6ed71add4c7abe7dc9d14e33a6b Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 20:24:30 +0200 Subject: [PATCH 11/56] Get rid if 'all' => true It's superseded by creating the object without any arguments. --- wp-includes/export/class-wp-wxr-export.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 850c11a540ec..12fba72250bc 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -18,7 +18,7 @@ class WP_WXR_Export { private $filters; private $xml_gen; - public function __construct( $filters = array( 'all' => true ) ) { + public function __construct( $filters = array() ) { $this->filters = wp_parse_args( $filters, self::$defaults ); $this->post_ids = $this->calculate_post_ids(); } From 2297bdbf8def3d41279107c0f1ba52a599f9d0d0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 20:47:50 +0200 Subject: [PATCH 12/56] Add attachment of custom post types Otherwise, if we choose to query a CPT, they get filtered. See http://core.trac.wordpress.org/ticket/17379 --- wp-includes/export/class-wp-wxr-export.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 12fba72250bc..00f2069966e1 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -5,6 +5,8 @@ * An immutable object, which gathers all data needed for the export. */ class WP_WXR_Export { + const QUERY_CHUNK = 100; + private static $defaults = array( 'post_ids' => null, 'post_type' => null, @@ -94,14 +96,16 @@ private function calculate_post_ids() { $where = implode( ' AND ', array_filter( $wheres ) ); if ( $where ) $where = "WHERE $where"; - return $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); + $post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); + $post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) ); + return $post_ids; } private function post_type_where() { global $wpdb; $post_types_filters = array( 'can_export' => true ); if ( $this->filters['post_type'] ) { - $post_types_filters += array( 'name' => $this->filters['post_type'] ); + $post_types_filters = array_merge( $post_types_filters, array( 'name' => $this->filters['post_type'] ) ); } $post_types = get_post_types( $post_types_filters ); if ( !$post_types ) { @@ -144,6 +148,19 @@ private function end_date_where() { return $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); } + private function attachments_for_specific_post_types( $post_ids ) { + global $wpdb; + if ( !$this->filters['post_type'] ) { + return array(); + } + $attachment_ids = array(); + while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::QUERY_CHUNK ) ) { + $post_parent_condition = $this->build_IN_condition( 'post_parent', $batch_of_post_ids ); + $attachment_ids = array_merge( $attachment_ids, (array)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND $post_parent_condition" ) ); + } + return array_map( 'intval', $attachment_ids ); + } + private function export_using_writer( $writer ) { try { return $writer->export(); From 6a0e3e4dfc138065bd2c3b16489236b227ac178b Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 20:59:42 +0200 Subject: [PATCH 13/56] Switch *_where() methods to modify $this->wheres Some of them need to touch both $wheres and $joins and having them return values gets unwieldy. --- wp-includes/export/class-wp-wxr-export.php | 40 +++++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 00f2069966e1..fccac257f45a 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -20,6 +20,9 @@ class WP_WXR_Export { private $filters; private $xml_gen; + private $wheres = array(); + private $joins = array(); + public function __construct( $filters = array() ) { $this->filters = wp_parse_args( $filters, self::$defaults ); $this->post_ids = $this->calculate_post_ids(); @@ -88,13 +91,13 @@ private function calculate_post_ids() { $join = ''; $wheres = array(); - $wheres[] = $this->post_type_where(); - $wheres[] = $this->status_where(); - $wheres[] = $this->author_where(); - $wheres[] = $this->start_date_where(); - $wheres[] = $this->end_date_where(); + $this->post_type_where(); + $this->status_where(); + $this->author_where(); + $this->start_date_where(); + $this->end_date_where(); - $where = implode( ' AND ', array_filter( $wheres ) ); + $where = implode( ' AND ', array_filter( $this->wheres ) ); if ( $where ) $where = "WHERE $where"; $post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); $post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) ); @@ -109,43 +112,46 @@ private function post_type_where() { } $post_types = get_post_types( $post_types_filters ); if ( !$post_types ) { - return 'p.post_type IS NULL'; + $this->wheres[] = 'p.post_type IS NULL'; + return; } - return $this->build_IN_condition( 'p.post_type', $post_types ); + $this->wheres[] = $this->build_IN_condition( 'p.post_type', $post_types ); } private function status_where() { global $wpdb; - if ( !$this->filters['status'] ) - return "p.post_status != 'auto-draft'"; - return $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); + if ( !$this->filters['status'] ) { + $this->wheres[] = "p.post_status != 'auto-draft'"; + return; + } + $this->wheres[] = $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); } private function author_where() { global $wpdb; $user = $this->find_user_from_any_object( $this->filters['author'] ); if ( !$user || is_wp_error( $user ) ) { - return false; + return; } - return $wpdb->prepare( 'p.post_author = %d', $user->ID ); + $this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->ID ); } private function start_date_where() { global $wpdb; $timestamp = strtotime( $this->filters['start_date'] ); if ( !$timestamp ) { - return false; + return; } - return $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) ); + $this->wheres[] = $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) ); } private function end_date_where() { global $wpdb; $timestamp = strtotime( $this->filters['end_date'] ); if ( !$timestamp ) { - return false; + return; } - return $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); + $this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); } private function attachments_for_specific_post_types( $post_ids ) { From 9648ab15e505219d23d5a3b2a3f4e1a1f9fc9949 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 21:31:32 +0200 Subject: [PATCH 14/56] Add category filtering --- wp-includes/export/class-wp-wxr-export.php | 36 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index fccac257f45a..cc25e3ae7843 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -14,6 +14,7 @@ class WP_WXR_Export { 'author' => null, 'start_date' => null, 'end_date' => null, + 'category' => null, ); private $post_ids; @@ -88,17 +89,17 @@ private function calculate_post_ids() { if ( is_array( $this->filters['post_ids'] ) ) { return $this->filters['post_ids']; } - $join = ''; - $wheres = array(); - $this->post_type_where(); $this->status_where(); $this->author_where(); $this->start_date_where(); $this->end_date_where(); + $this->category_where(); $where = implode( ' AND ', array_filter( $this->wheres ) ); if ( $where ) $where = "WHERE $where"; + $join = implode( ' ', array_filter( $this->joins ) ); + $post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); $post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) ); return $post_ids; @@ -154,6 +155,19 @@ private function end_date_where() { $this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); } + private function category_where() { + global $wpdb; + if ( 'post' != $this->filters['post_type'] ) { + return; + } + $category = $this->find_category_from_any_object( $this->filters['category'] ); + if ( !$category ) { + return; + } + $this->joins[] = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)"; + $this->wheres[] = $wpdb->prepare( 'tr.term_taxonomy_id = %d', $category->term_taxonomy_id ); + } + private function attachments_for_specific_post_types( $post_ids ) { global $wpdb; if ( !$this->filters['post_type'] ) { @@ -189,15 +203,27 @@ private function build_IN_condition( $column_name, $values ) { } private function find_user_from_any_object( $user ) { - if ( is_numeric( $user ) ) + if ( is_numeric( $user ) ) { return get_user_by( 'id', $user ); - elseif ( is_string( $user ) ) { + } elseif ( is_string( $user ) ) { return get_user_by( 'login', $user ); } elseif ( isset( $user->ID ) ) { return get_user_by( 'id', $user->ID ); } return false; } + + private function find_category_from_any_object( $category ) { + if ( is_numeric( $category ) ) { + return get_term( $category, 'category' ); + } elseif ( is_string( $category ) ) { + $term = term_exists( $category, 'category' ); + return isset( $term['term_id'] )? get_term( $term['term_id'], 'category' ) : false; + } elseif ( isset( $category->term_id ) ) { + return get_term( $category->term_id, 'category' ); + } + return false; + } } class WP_WXR_Exception extends RuntimeException { From 7f12020b70697ea2da94f49401009dc890065082 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 22:19:14 +0200 Subject: [PATCH 15/56] Add authors listing --- wp-includes/export/class-wp-wxr-export.php | 11 +++++++ .../export/class-wp-wxr-xml-generator.php | 31 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index cc25e3ae7843..2738a6ab4cc2 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -62,6 +62,17 @@ public function wp_generator_tag() { return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' ); } + public function authors() { + global $wpdb; + $authors = array(); + $author_ids = $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" ); + foreach ( (array) $author_ids as $author_id ) { + $authors[] = get_userdata( $author_id ); + } + $authors = array_filter( $authors ); + return $authors; + } + /** * Exports the current data using a specific export writer class * diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 0ee4f3299136..392d1386c101 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -62,6 +62,23 @@ function site_metadata() { } function authors() { + $authors = $this->export->authors(); + $xml = ''; + foreach ( $authors as $author ) { + self::make_object_fields_cdata( $author, array( 'display_name', 'user_firstname', 'user_lastname' ) ); + $xml .= << + {$author->ID} + {$author->user_login} + {$author->user_email} + {$author->display_name_cdata} + {$author->user_firstname_cdata} + {$author->user_lastname_cdata} + + +XML; + } + return $xml; } function categories() { @@ -86,4 +103,18 @@ function footer() { XML; } + + private static function make_object_fields_cdata( $object, $fields = array() ) { + foreach( $fields as $field ) { + $field_cdata = "{$field}_cdata"; + $object->$field_cdata = self::cdata( $object->$field ); + } + } + + private static function cdata( $text ) { + if ( !seems_utf8( $text ) ) { + $text = utf8_encode( $text ); + } + return '', ']]]]>', $text ) . ']]>'; + } } From a888c634363c02683ac207420685788a8c802551 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sun, 11 Nov 2012 23:48:15 +0200 Subject: [PATCH 16/56] Set vars when we are filtering on user or category This saves us some guesswork after that if we need to know if the current export is filtered on a user or on a category. For example we need that when listing all the categories and in case we are filtering on one category, we don't need to show any others. --- wp-includes/export/class-wp-wxr-export.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 2738a6ab4cc2..ed635b88b3fb 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -24,6 +24,9 @@ class WP_WXR_Export { private $wheres = array(); private $joins = array(); + private $author; + private $category; + public function __construct( $filters = array() ) { $this->filters = wp_parse_args( $filters, self::$defaults ); $this->post_ids = $this->calculate_post_ids(); @@ -145,6 +148,7 @@ private function author_where() { if ( !$user || is_wp_error( $user ) ) { return; } + $this->author = $user; $this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->ID ); } @@ -175,6 +179,7 @@ private function category_where() { if ( !$category ) { return; } + $this->category = $category; $this->joins[] = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)"; $this->wheres[] = $wpdb->prepare( 'tr.term_taxonomy_id = %d', $category->term_taxonomy_id ); } From 71400067023306f314c14d95263cf18a616bec5f Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 00:22:49 +0200 Subject: [PATCH 17/56] Add categories listing --- wp-includes/export/class-wp-wxr-export.php | 23 +++++++++++++++++++ .../export/class-wp-wxr-xml-generator.php | 17 ++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index ed635b88b3fb..942237a4de5d 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -76,6 +76,18 @@ public function authors() { return $authors; } + public function categories() { + if ( $this->category ) { + return $this->category; + } + if ( $this->filters['post_type'] ) { + return array(); + } + $categories = (array) get_categories( array( 'get' => 'all' ) ); + $categories = self::topologically_sort_terms( $categories ); + return $categories; + } + /** * Exports the current data using a specific export writer class * @@ -240,6 +252,17 @@ private function find_category_from_any_object( $category ) { } return false; } + + private static function topologically_sort_terms( $terms ) { + $sorted = array(); + while ( $term = array_shift( $terms ) ) { + if ( $term->parent == 0 || isset( $sorted[$term->parent] ) ) + $sorted[$term->term_id] = $term; + else + $terms[] = $term; + } + return $sorted; + } } class WP_WXR_Exception extends RuntimeException { diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 392d1386c101..207ad034380b 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -82,6 +82,23 @@ function authors() { } function categories() { + $categories = $this->export->categories(); + $xml = ''; + foreach( $categories as $term_id => $category ) { + self::make_object_fields_cdata( $category, array( 'name', 'description' ) ); + $category->parent_slug = $category->parent? $categories[$category->parent]->slug : ''; + $xml .= << + {$category->term_id} + {$category->slug} + {$category->parent_slug} + {$category->name_cdata} + {$category->description_cdata} + + +XML; + } + return $xml; } function tags() { From 16ed47fb1d636f130942ca6167f8d18f1f64b3a1 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 14:53:54 +0200 Subject: [PATCH 18/56] Add tags listing --- wp-includes/export/class-wp-wxr-export.php | 8 ++++++++ .../export/class-wp-wxr-xml-generator.php | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 942237a4de5d..26f71715d8f9 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -88,6 +88,14 @@ public function categories() { return $categories; } + public function tags() { + if ( $this->filters['post_type'] ) { + return array(); + } + $tags = (array) get_tags( array( 'get' => 'all' ) ); + return $tags; + } + /** * Exports the current data using a specific export writer class * diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 207ad034380b..2888eb6003aa 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -102,6 +102,22 @@ function categories() { } function tags() { + $tags = $this->export->tags(); + $xml = ''; + foreach( $tags as $tag ) { + self::make_object_fields_cdata( $tag, array( 'name', 'description' ) ); + $xml .= << + {$tag->term_id} + {$tag->slug} + {$tag->parent_slug} + {$tag->name_cdata} + {$tag->description_cdata} + + +XML; + } + return $xml; } function nav_menu_terms() { From 0ddb31aadd231016a84370ada972f4a4c129bd46 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 16:01:47 +0200 Subject: [PATCH 19/56] Tags don't have parents --- wp-includes/export/class-wp-wxr-xml-generator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 2888eb6003aa..522047069deb 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -110,7 +110,6 @@ function tags() { {$tag->term_id} {$tag->slug} - {$tag->parent_slug} {$tag->name_cdata} {$tag->description_cdata} From 131f6238ec7b1ddd033088079069977983e30eed Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 16:02:15 +0200 Subject: [PATCH 20/56] List custom taxonomies terms --- wp-includes/export/class-wp-wxr-export.php | 14 ++++++++++ .../export/class-wp-wxr-xml-generator.php | 26 +++++++++++++++++-- wp-includes/export/writers.php | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 26f71715d8f9..b2642579b25c 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -96,6 +96,20 @@ public function tags() { return $tags; } + public function custom_taxonomies_terms() { + if ( $this->filters['post_type'] ) { + return array(); + } + $custom_taxonomies = get_taxonomies( array( '_builtin' => false ) ); + $custom_terms = (array) get_terms( $custom_taxonomies, array( 'get' => 'all' ) ); + $custom_terms = self::topologically_sort_terms( $custom_terms ); + return $custom_terms; + } + + public function nav_menu_terms() { + return wp_get_nav_menus(); + } + /** * Exports the current data using a specific export writer class * diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 522047069deb..9110b567a287 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -8,7 +8,7 @@ function __construct( $export ) { } static function get_parts() { - return array( 'header', 'site_metadata', 'authors', 'categories', 'tags', 'nav_menu_terms', 'other_terms', 'posts', 'footer', ); + return array( 'header', 'site_metadata', 'authors', 'categories', 'tags', 'nav_menu_terms', 'custom_taxonomies_terms', 'posts', 'footer', ); } function header() { @@ -120,9 +120,31 @@ function tags() { } function nav_menu_terms() { + return $this->terms( $this->export->nav_menu_terms() ); } - function other_terms() { + function custom_taxonomies_terms() { + return $this->terms( $this->export->custom_taxonomies_terms() ); + } + + private function terms( $terms ) { + $xml = ''; + foreach( $terms as $term ) { + $term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; + self::make_object_fields_cdata( $term, array( 'name', 'description' ) ); + $xml .= << + {$term->term_id} + {$term->taxonomy} + {$term->slug} + {$term->parent_slug} + {$term->name_cdata} + {$term->description_cdata} + + +XML; + } + return $xml; } function posts() { diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index e45bbb93b804..009ea360d768 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -19,7 +19,7 @@ protected function export_before_posts() { $this->write( $this->xml_generator->categories() ); $this->write( $this->xml_generator->tags() ); $this->write( $this->xml_generator->nav_menu_terms() ); - $this->write( $this->xml_generator->other_terms() ); + $this->write( $this->xml_generator->custom_taxonomies_terms() ); } protected function export_posts() { From 8af7970bd4f0412c53d0fb3c0fafb047f48f171f Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 17:42:49 +0200 Subject: [PATCH 21/56] Move build_IN_condition() to wpdb It could be used in more places, not just export. --- wp-includes/export/class-wp-wxr-export.php | 13 ++----------- wp-includes/wp-db.php | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index b2642579b25c..9cd5a5fd5cca 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -164,7 +164,7 @@ private function post_type_where() { $this->wheres[] = 'p.post_type IS NULL'; return; } - $this->wheres[] = $this->build_IN_condition( 'p.post_type', $post_types ); + $this->wheres[] = $wpdb->build_IN_condition( 'p.post_type', $post_types ); } private function status_where() { @@ -225,7 +225,7 @@ private function attachments_for_specific_post_types( $post_ids ) { } $attachment_ids = array(); while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::QUERY_CHUNK ) ) { - $post_parent_condition = $this->build_IN_condition( 'post_parent', $batch_of_post_ids ); + $post_parent_condition = $wpdb->build_IN_condition( 'post_parent', $batch_of_post_ids ); $attachment_ids = array_merge( $attachment_ids, (array)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND $post_parent_condition" ) ); } return array_map( 'intval', $attachment_ids ); @@ -243,15 +243,6 @@ private function bloginfo_rss( $section ) { return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); } - private function build_IN_condition( $column_name, $values ) { - global $wpdb; - if ( !is_array( $values ) || empty( $values ) ) { - return false; - } - $esses = implode( ', ', array_fill( 0, count( $values ), '%s' ) ); - return $wpdb->prepare( "$column_name IN ($esses)", $values ); - } - private function find_user_from_any_object( $user ) { if ( is_numeric( $user ) ) { return get_user_by( 'id', $user ); diff --git a/wp-includes/wp-db.php b/wp-includes/wp-db.php index 068351b76644..50d1a5ee47bd 100644 --- a/wp-includes/wp-db.php +++ b/wp-includes/wp-db.php @@ -1749,4 +1749,22 @@ function get_caller() { function db_version() { return preg_replace( '/[^0-9.].*/', '', mysql_get_server_info( $this->dbh ) ); } + + /** + * Builds a SQL condition in the form "post_id IN (1, 2, 3, 4)" + * + * @since 3.6.0 + * + * @param string $column_name The name of the table column from the IN condition + * @param array $values Array of values in which the column value should be + * @param string $format Optional printf format specifier for the elements of the array. Defaults to %s. + * @return string The IN condition, with escaped values. If there are no values, the return value is an empty string. + */ + function build_IN_condition( $column_name, $values, $format = '%s' ) { + if ( !is_array( $values ) || empty( $values ) ) { + return ''; + } + $formats = implode( ', ', array_fill( 0, count( $values ), $format ) ); + return $this->prepare( "$column_name IN ($formats)", $values ); + } } From e94898f08ce64f37c5498c9fe507b0007716d815 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 18:16:38 +0200 Subject: [PATCH 22/56] Add Post IDs iterator It allows to query a lot of posts by chunks given their post IDs. --- wp-includes/class-wp-post-ids-iterator.php | 69 ++++++++++++++++++++++ wp-settings.php | 1 + 2 files changed, 70 insertions(+) create mode 100644 wp-includes/class-wp-post-ids-iterator.php diff --git a/wp-includes/class-wp-post-ids-iterator.php b/wp-includes/class-wp-post-ids-iterator.php new file mode 100644 index 000000000000..45b3c205dea4 --- /dev/null +++ b/wp-includes/class-wp-post-ids-iterator.php @@ -0,0 +1,69 @@ +db = $GLOBALS['wpdb']; + $this->post_ids = $post_ids; + $this->ids_left = $post_ids; + if ( !is_null( $limit ) ) { + $this->limit = $limit; + } + } + + public function current() { + return $this->results[$this->index_in_results]; + } + + public function key() { + return $this->global_index; + } + + public function next() { + $this->index_in_results++; + $this->global_index++; + } + + public function rewind() { + $this->results = array(); + $this->global_index = 0; + $this->index_in_results = 0; + $this->ids_left = $this->post_ids; + } + + public function valid() { + if ( isset( $this->results[$this->index_in_results] ) ) { + return true; + } + if ( empty( $this->ids_left ) ) { + return false; + } + $has_posts = $this->load_next_posts_from_db(); + if ( !$has_posts ) { + return false; + } + $this->index_in_results = 0; + return true; + } + + private function load_next_posts_from_db() { + $next_batch_post_ids = array_splice( $this->ids_left, 0, $this->limit ); + $in_post_ids_sql = $this->db->build_IN_condition( 'ID', $next_batch_post_ids ); + $this->results = $this->db->get_results( "SELECT * FROM {$this->db->posts} WHERE $in_post_ids_sql" ); + if ( !$this->results ) { + if ( $this->db->last_error ) { + throw new WP_Iterator_Exception( 'Database error: ' . $this->db->last_error ); + } else { + return false; + } + } + return true; + } +} + +class WP_Iterator_Exception extends Exception { +} diff --git a/wp-settings.php b/wp-settings.php index 20b609c03375..87151c99ba5a 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -126,6 +126,7 @@ require( ABSPATH . WPINC . '/comment.php' ); require( ABSPATH . WPINC . '/comment-template.php' ); require( ABSPATH . WPINC . '/rewrite.php' ); +require( ABSPATH . WPINC . '/class-wp-post-ids-iterator.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-export.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); require( ABSPATH . WPINC . '/export/writers.php' ); From ef43160b18583d723de10e643d7511a637de8dff Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 19:07:43 +0200 Subject: [PATCH 23/56] Rename class-wp-post-iterator.php to iterators.php The new name is more generic and allows us to use the same file for more iterators. --- wp-includes/{class-wp-post-ids-iterator.php => iterators.php} | 0 wp-settings.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename wp-includes/{class-wp-post-ids-iterator.php => iterators.php} (100%) diff --git a/wp-includes/class-wp-post-ids-iterator.php b/wp-includes/iterators.php similarity index 100% rename from wp-includes/class-wp-post-ids-iterator.php rename to wp-includes/iterators.php diff --git a/wp-settings.php b/wp-settings.php index 87151c99ba5a..738b73bbb9aa 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -126,7 +126,7 @@ require( ABSPATH . WPINC . '/comment.php' ); require( ABSPATH . WPINC . '/comment-template.php' ); require( ABSPATH . WPINC . '/rewrite.php' ); -require( ABSPATH . WPINC . '/class-wp-post-ids-iterator.php' ); +require( ABSPATH . WPINC . '/iterators.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-export.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); require( ABSPATH . WPINC . '/export/writers.php' ); From 063ca52d6b42153799891f49dd5b885a70531e62 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 19:08:44 +0200 Subject: [PATCH 24/56] Add WP_Map_Iterator Accepts a callback and maps all the values using it. --- wp-includes/iterators.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wp-includes/iterators.php b/wp-includes/iterators.php index 45b3c205dea4..6a4613cbbce6 100644 --- a/wp-includes/iterators.php +++ b/wp-includes/iterators.php @@ -1,4 +1,15 @@ callback = $callback; + parent::__construct( $iterator ); + } + + function current() { + $original_current = parent::current(); + return call_user_func( $this->callback, $original_current ); + } +} class WP_Post_IDs_Iterator implements Iterator { private $limit = 100; From 0f0bb5e6b6f088a530446dc6873c1fc70d7600b0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 19:50:08 +0200 Subject: [PATCH 25/56] Move WXR_VERSION out of wp-admin --- wp-admin/includes/export.php | 9 --------- wp-includes/export/class-wp-wxr-xml-generator.php | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wp-admin/includes/export.php b/wp-admin/includes/export.php index 91d88a18abe5..bcb17e65637d 100644 --- a/wp-admin/includes/export.php +++ b/wp-admin/includes/export.php @@ -6,15 +6,6 @@ * @subpackage Administration */ -/** - * Version number for the export format. - * - * Bump this when something changes that might affect compatibility. - * - * @since 2.5.0 - */ -define( 'WXR_VERSION', '1.2' ); - /** * Generates the WXR export file for download * diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 9110b567a287..2a23071316c9 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -1,4 +1,13 @@ Date: Mon, 12 Nov 2012 20:08:45 +0200 Subject: [PATCH 26/56] Glue posts handling The export returns a map iterator with exportify_post() which adds some useful post fields. Then the XML generator takes it and returns another map generator, which generates XML out of the post object. Now when the writer loops over the posts it has no idea what happens behind the scenes. It looks as if it got just an array of strings with XML in them. --- wp-includes/export/class-wp-wxr-export.php | 15 +++++++++++++++ wp-includes/export/class-wp-wxr-xml-generator.php | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 9cd5a5fd5cca..a9e8c15dd031 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -110,6 +110,21 @@ public function nav_menu_terms() { return wp_get_nav_menus(); } + public function exportify_post( $post ) { + $GLOBALS['wp_query']->in_the_loop = true; + $GLOBALS['post'] = $post; + setup_postdata( $post ); + $post->post_title_rss = apply_filters( 'the_title_rss', $post->post_title ); + $post->is_sticky = is_sticky( $post->ID ) ? 1 : 0; + // TODO: add the rest of the extra fields, modifications, etc. + return $post; + } + + public function posts() { + $posts_iterator = new WP_Post_IDs_Iterator( $this->post_ids, self::QUERY_CHUNK ); + return new WP_Map_Iterator( $posts_iterator, array( $this, 'exportify_post' ) ); + } + /** * Exports the current data using a specific export writer class * diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 2a23071316c9..a1761889aece 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -157,7 +157,20 @@ private function terms( $terms ) { } function posts() { - return array( 'a', 'b' ); + return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); + } + + function post( $post ) { + global $wpdb; + $xml = << + {$post->post_title_rss} + + {$post->is_sticky} + + +XML; + return $xml; } function footer() { From 7f89f6d61455ca202d33f4c1bfbaeaf81f43a9c0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Mon, 12 Nov 2012 22:12:22 +0200 Subject: [PATCH 27/56] Introduce serve_xml() and its writer --- wp-includes/export/class-wp-wxr-export.php | 4 ++++ wp-includes/export/writers.php | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index a9e8c15dd031..0111013965ef 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -40,6 +40,10 @@ public function export_to_xml_file( $file_name ) { return $this->export_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); } + public function serve_xml( $file_name ) { + return $this->export_using_writer_class( 'WP_WXR_XML_Over_HTTP', array( $file_name ) ); + } + public function post_ids() { return $this->post_ids; } diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 009ea360d768..b4877af32420 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -35,6 +35,26 @@ protected function export_after_posts() { abstract protected function write( $xml ); } +class WP_WXR_XML_Over_HTTP extends WP_WXR_Base_Writer { + private $file_name; + + function __construct( $xml_generator, $file_name ) { + $this->file_name = $file_name; + parent::__construct( $xml_generator ); + } + + public function export() { + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: attachment; filename=' . $this->file_name ); + header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); + parent::export(); + } + + protected function write( $xml ) { + echo $xml; + } +} + class WP_WXR_Returner extends WP_WXR_Base_Writer { private $result = ''; From f1cc0fd004e5fe335271283aa31a8d1e71039f2d Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 13 Nov 2012 00:05:29 +0200 Subject: [PATCH 28/56] Simplify writers Introduce before_posts() and after_posts() methods in the XML generator, so that if they don't want to get very granular, the writers can use them. This made the writers code (especially that of the split files writers) much simpler. --- .../export/class-wp-wxr-xml-generator.php | 25 ++++- wp-includes/export/writers.php | 104 ++++++++++-------- 2 files changed, 79 insertions(+), 50 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index a1761889aece..1a35107491e7 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -20,6 +20,27 @@ static function get_parts() { return array( 'header', 'site_metadata', 'authors', 'categories', 'tags', 'nav_menu_terms', 'custom_taxonomies_terms', 'posts', 'footer', ); } + + function before_posts() { + $before_posts_xml = ''; + $before_posts_xml .= $this->header(); + $before_posts_xml .= $this->site_metadata(); + $before_posts_xml .= $this->authors(); + $before_posts_xml .= $this->categories(); + $before_posts_xml .= $this->tags(); + $before_posts_xml .= $this->nav_menu_terms(); + $before_posts_xml .= $this->custom_taxonomies_terms(); + return $before_posts_xml; + } + + function posts() { + return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); + } + + function after_posts() { + return $this->footer(); + } + function header() { $wxr_version = WXR_VERSION; $charset = $this->export->charset(); @@ -156,10 +177,6 @@ private function terms( $terms ) { return $xml; } - function posts() { - return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); - } - function post( $post ) { global $wpdb; $xml = <<export_before_posts(); - $this->export_posts(); - $this->export_after_posts(); - } - - protected function export_before_posts() { - $this->write( $this->xml_generator->header() ); - $this->write( $this->xml_generator->site_metadata() ); - $this->write( $this->xml_generator->authors() ); - $this->write( $this->xml_generator->categories() ); - $this->write( $this->xml_generator->tags() ); - $this->write( $this->xml_generator->nav_menu_terms() ); - $this->write( $this->xml_generator->custom_taxonomies_terms() ); - } - - protected function export_posts() { + $this->write( $this->xml_generator->before_posts() ); foreach( $this->xml_generator->posts() as $post_in_wxr ) { $this->write( $post_in_wxr ); } + $this->write( $this->xml_generator->after_posts() ); } - protected function export_after_posts() { - $this->write( $this->xml_generator->footer() ); + protected function write( $xml ) { + echo $xml; } - - abstract protected function write( $xml ); } class WP_WXR_XML_Over_HTTP extends WP_WXR_Base_Writer { private $file_name; function __construct( $xml_generator, $file_name ) { - $this->file_name = $file_name; parent::__construct( $xml_generator ); + $this->file_name = $file_name; } public function export() { @@ -73,8 +57,8 @@ class WP_WXR_File_Writer extends WP_WXR_Base_Writer { private $file_name; public function __construct( $xml_generator, $file_name ) { - $this->file_name = $file_name; parent::__construct( $xml_generator ); + $this->file_name = $file_name; } public function export() { @@ -96,39 +80,67 @@ protected function write( $xml ) { class WP_WXR_Split_Files_Writer extends WP_WXR_Base_Writer { private $result = ''; + private $f; + private $next_file_number = 0; + private $current_file_size = 0; - function __construct( $export, $destination_directory, $filename_template, $max_file_size_in_bytes = null ) { - $this->max_file_size_in_bytes = is_null( $max_file_size_in_bytes ) ? 15 * MB_IN_BYTES : $max_file_size_in_bytes; + function __construct( $xml_generator, $destination_directory, $filename_template, $max_file_size = null ) { + parent::__construct( $xml_generator ); + $this->max_file_size = is_null( $max_file_size ) ? 15 * MB_IN_BYTES : $max_file_size; + $this->destination_directory = $destination_directory; + $this->filename_template = $filename_template; + $this->before_posts_xml = $this->xml_generator->before_posts(); + $this->after_posts_xml = $this->xml_generator->after_posts(); } - private function export_() { - $before_posts = $this->get_xml_from_method( 'export_before_posts' ); - $after_posts = $this->get_xml_from_method( 'export_after_posts' ); - $size_of_non_posts = strlen( $before_posts ) + strlen( $after_posts ); - $wxr = $before_posts; - foreach( $this->xml_generator->posts() as $post ) { - $post_wxr = $this->xml_generator->post( $post ); - if ( strlen( $post_wxr ) + $size_of_non_posts > $this->max_file_size_in_bytes ) { - $this->write_next_file( $wxr . $after_posts ); - $wxr = ''; + public function export() { + $this->start_new_file(); + foreach( $this->xml_generator->posts() as $post_xml ) { + if ( $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) { + $this->start_new_file(); } - $wxr .= $post_wxr; + $this->write( $post_xml ); } - $this->write_next_file( $wxr . $after_posts ); + $this->close_current_file(); } - private function get_xml_from_method( $method_name ) { - $this->result = ''; - $this->accumulate = true; - $this->$method_name(); - $this->accumulate = false; + protected function write( $xml ) { + $res = fwrite( $this->f, $xml); + if ( false === $res ) { + throw new WP_WXR_Exception( __( 'WXR Export: error writing to export file.' ) ); + } + $this->current_file_size += strlen( $xml ); } - function write( $s ) { - if ( $this->accumulate ) { - $this->result = $s; - } else { - fwrite( $this->f, $s ); + private function start_new_file() { + if ( $this->f ) { + $this->close_current_file(); + } + $file_path = $this->next_file_path(); + $this->f = fopen( $file_path, 'w' ); + if ( !$this->f ) { + throw new WP_WXR_Exception( sprintf( __( 'WXR Export: error opening %s for writing.' ), $file_path ) ); } + $this->current_file_size = 0; + $this->write( $this->before_posts_xml ); } + + private function close_current_file() { + if ( !$this->f ) { + return; + } + $this->write( $this->after_posts_xml ); + fclose( $this->f ); + } + + private function next_file_name() { + $next_file_name = sprintf( $this->filename_template, $this->next_file_number ); + $this->next_file_number++; + return $next_file_name; + } + + private function next_file_path() { + return untrailingslashit( $this->destination_directory ) . DIRECTORY_SEPARATOR . $this->next_file_name(); + } + } From 3cde7dfd94d57c16e6cc5240d9d27cbbce05191d Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 13 Nov 2012 00:05:48 +0200 Subject: [PATCH 29/56] Expose the split files writer through the export --- wp-includes/export/class-wp-wxr-export.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 0111013965ef..323067965d10 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -40,6 +40,10 @@ public function export_to_xml_file( $file_name ) { return $this->export_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); } + public function export_to_xml_files( $destination_directory, $filename_template, $max_file_size = null ) { + return $this->export_using_writer_class( 'WP_WXR_Split_Files_Writer', array( $destination_directory, $filename_template, $max_file_size ) ); + } + public function serve_xml( $file_name ) { return $this->export_using_writer_class( 'WP_WXR_XML_Over_HTTP', array( $file_name ) ); } From 11cf991a3cd01ae231a266fd14d0ea64ab8725fe Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 13 Nov 2012 19:09:04 +0200 Subject: [PATCH 30/56] We don't need to have a list of the parts We were using them only for tests and since we now have before_posts() and after_posts() it's much easier on the tests side. --- wp-includes/export/class-wp-wxr-xml-generator.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 1a35107491e7..08420a69e09a 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -16,11 +16,6 @@ function __construct( $export ) { $this->export = $export; } - static function get_parts() { - return array( 'header', 'site_metadata', 'authors', 'categories', 'tags', 'nav_menu_terms', 'custom_taxonomies_terms', 'posts', 'footer', ); - } - - function before_posts() { $before_posts_xml = ''; $before_posts_xml .= $this->header(); From 8dc05ac2abc6b792511c38c1bcb490d0a48a5e9a Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 13 Nov 2012 19:52:12 +0200 Subject: [PATCH 31/56] Make the writer abstract again We want every sub-class to write its own write() implementation, the echo here is rather arbitrary. --- wp-includes/export/writers.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 2bdf3233bd69..faaf0e91f231 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -14,9 +14,7 @@ public function export() { $this->write( $this->xml_generator->after_posts() ); } - protected function write( $xml ) { - echo $xml; - } + abstract protected function write( $xml ); } class WP_WXR_XML_Over_HTTP extends WP_WXR_Base_Writer { From ced3b05c60a78275482f3a7953a9ba364fca0e44 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 13 Nov 2012 19:52:58 +0200 Subject: [PATCH 32/56] Rename to export_xml_using_writer_class() The result is always coupled with the XML generator, so we'd better be honest in our naming. --- wp-includes/export/class-wp-wxr-export.php | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-wxr-export.php index 323067965d10..c5d73e5e7416 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-wxr-export.php @@ -33,19 +33,19 @@ public function __construct( $filters = array() ) { } public function get_xml() { - return $this->export_using_writer_class( 'WP_WXR_Returner' ); + return $this->export_xml_using_writer_class( 'WP_WXR_Returner' ); } public function export_to_xml_file( $file_name ) { - return $this->export_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); + return $this->export_xml_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); } public function export_to_xml_files( $destination_directory, $filename_template, $max_file_size = null ) { - return $this->export_using_writer_class( 'WP_WXR_Split_Files_Writer', array( $destination_directory, $filename_template, $max_file_size ) ); + return $this->export_xml_using_writer_class( 'WP_WXR_File_Writer', array( $destination_directory, $filename_template, $max_file_size ) ); } public function serve_xml( $file_name ) { - return $this->export_using_writer_class( 'WP_WXR_XML_Over_HTTP', array( $file_name ) ); + return $this->export_xml_using_writer_class( 'WP_WXR_XML_Over_HTTP', array( $file_name ) ); } public function post_ids() { @@ -147,7 +147,7 @@ public function posts() { * @param string $writer_class_name The name of the PHP class representing the writer * @param mixed[] $writer_args Optional additional arguments with which to call the writer constructor */ - public function export_using_writer_class( $writer_class_name, $writer_args = array() ) { + public function export_xml_using_writer_class( $writer_class_name, $writer_args = array() ) { $xml_generator = new WP_WXR_XML_Generator( $this ); array_unshift( $writer_args, $xml_generator ); $writer_class = new ReflectionClass( $writer_class_name ); @@ -155,6 +155,14 @@ public function export_using_writer_class( $writer_class_name, $writer_args = ar return $this->export_using_writer( $writer ); } + private function export_using_writer( $writer ) { + try { + return $writer->export(); + } catch ( WP_WXR_Exception $e ) { + return new WP_Error( 'wxr-error', $e->getMessage() ); + } + } + private function calculate_post_ids() { global $wpdb; if ( is_array( $this->filters['post_ids'] ) ) { @@ -254,14 +262,6 @@ private function attachments_for_specific_post_types( $post_ids ) { return array_map( 'intval', $attachment_ids ); } - private function export_using_writer( $writer ) { - try { - return $writer->export(); - } catch ( WP_WXR_Exception $e ) { - return new WP_Error( 'wxr-error', $e->getMessage() ); - } - } - private function bloginfo_rss( $section ) { return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); } From c4b2ec19782af96e884e8247cb89835e7784c13a Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Wed, 14 Nov 2012 22:58:50 +0200 Subject: [PATCH 33/56] Rename WP_WXR_Export to WP_Export_Query The class is responsible only for querying the right data and not about WXR at all. --- .../{class-wp-wxr-export.php => class-wp-export-query.php} | 4 ++-- wp-includes/export/class-wp-wxr-xml-generator.php | 4 +++- wp-settings.php | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) rename wp-includes/export/{class-wp-wxr-export.php => class-wp-export-query.php} (99%) diff --git a/wp-includes/export/class-wp-wxr-export.php b/wp-includes/export/class-wp-export-query.php similarity index 99% rename from wp-includes/export/class-wp-wxr-export.php rename to wp-includes/export/class-wp-export-query.php index c5d73e5e7416..81d03c18c21a 100644 --- a/wp-includes/export/class-wp-wxr-export.php +++ b/wp-includes/export/class-wp-export-query.php @@ -4,7 +4,7 @@ * * An immutable object, which gathers all data needed for the export. */ -class WP_WXR_Export { +class WP_Export_Query { const QUERY_CHUNK = 100; private static $defaults = array( @@ -141,7 +141,7 @@ public function posts() { * methods like get_xml(), export_to_xml_file(), etc. * * Example: - * $export = new WP_WXR_Export(…); + * $export = new WP_Export_Query(…); * $export->export( 'WP_WXR_CSV_Writer', array( '/home/baba/baba.csv', ';' ); * * @param string $writer_class_name The name of the PHP class representing the writer diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-wxr-xml-generator.php index 08420a69e09a..b0aca4cbdd3c 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-wxr-xml-generator.php @@ -8,8 +8,10 @@ */ define( 'WXR_VERSION', '1.2' ); +require_once ABSPATH . WPINC . '/xml-builder/Builder.php'; + /** - * Responsible for generating the WXR XML from the data in WP_WXR_Export + * Responsible for generating the WXR XML from the data in WP_Export_Query */ class WP_WXR_XML_Generator { function __construct( $export ) { diff --git a/wp-settings.php b/wp-settings.php index 738b73bbb9aa..94a7f975b1a5 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -127,7 +127,7 @@ require( ABSPATH . WPINC . '/comment-template.php' ); require( ABSPATH . WPINC . '/rewrite.php' ); require( ABSPATH . WPINC . '/iterators.php' ); -require( ABSPATH . WPINC . '/export/class-wp-wxr-export.php' ); +require( ABSPATH . WPINC . '/export/class-wp-export-query.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); require( ABSPATH . WPINC . '/export/writers.php' ); require( ABSPATH . WPINC . '/feed.php' ); From aff57131af6dfb96cb17d6276bdac58482bd20ef Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Wed, 14 Nov 2012 23:50:00 +0200 Subject: [PATCH 34/56] Move out the export methods from WP_Export_Query They don't belong there, because they cut through many concerns. We have them now all in a wp_export() function in its own file. --- wp-includes/export/class-wp-export-query.php | 46 -------------------- wp-includes/export/functions.export.php | 21 +++++++++ wp-settings.php | 1 + 3 files changed, 22 insertions(+), 46 deletions(-) create mode 100644 wp-includes/export/functions.export.php diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 81d03c18c21a..33a577d0e4fe 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -32,22 +32,6 @@ public function __construct( $filters = array() ) { $this->post_ids = $this->calculate_post_ids(); } - public function get_xml() { - return $this->export_xml_using_writer_class( 'WP_WXR_Returner' ); - } - - public function export_to_xml_file( $file_name ) { - return $this->export_xml_using_writer_class( 'WP_WXR_File_Writer', array( $file_name ) ); - } - - public function export_to_xml_files( $destination_directory, $filename_template, $max_file_size = null ) { - return $this->export_xml_using_writer_class( 'WP_WXR_File_Writer', array( $destination_directory, $filename_template, $max_file_size ) ); - } - - public function serve_xml( $file_name ) { - return $this->export_xml_using_writer_class( 'WP_WXR_XML_Over_HTTP', array( $file_name ) ); - } - public function post_ids() { return $this->post_ids; } @@ -133,36 +117,6 @@ public function posts() { return new WP_Map_Iterator( $posts_iterator, array( $this, 'exportify_post' ) ); } - /** - * Exports the current data using a specific export writer class - * - * You should use this method only when you need to export using a - * custom writer. For built-in writers, please see the other public - * methods like get_xml(), export_to_xml_file(), etc. - * - * Example: - * $export = new WP_Export_Query(…); - * $export->export( 'WP_WXR_CSV_Writer', array( '/home/baba/baba.csv', ';' ); - * - * @param string $writer_class_name The name of the PHP class representing the writer - * @param mixed[] $writer_args Optional additional arguments with which to call the writer constructor - */ - public function export_xml_using_writer_class( $writer_class_name, $writer_args = array() ) { - $xml_generator = new WP_WXR_XML_Generator( $this ); - array_unshift( $writer_args, $xml_generator ); - $writer_class = new ReflectionClass( $writer_class_name ); - $writer = $writer_class->newInstanceArgs( $writer_args ); - return $this->export_using_writer( $writer ); - } - - private function export_using_writer( $writer ) { - try { - return $writer->export(); - } catch ( WP_WXR_Exception $e ) { - return new WP_Error( 'wxr-error', $e->getMessage() ); - } - } - private function calculate_post_ids() { global $wpdb; if ( is_array( $this->filters['post_ids'] ) ) { diff --git a/wp-includes/export/functions.export.php b/wp-includes/export/functions.export.php new file mode 100644 index 000000000000..0109a18e5712 --- /dev/null +++ b/wp-includes/export/functions.export.php @@ -0,0 +1,21 @@ + array(), + 'format' => 'WP_WXR_XML_Generator', + 'writer' => 'WP_Export_Returner', + 'writer_args' => array(), + ); + $args = wp_parse_args( $args, $defaults ); + $export_query = new WP_Export_Query( $args['filters'] ); + $generator = new $args['format']( $export_query ); + array_unshift( $args['writer_args'], $generator ); + $writer_class = new ReflectionClass( $args['writer'] ); + $writer = $writer_class->newInstanceArgs( $args['writer_args'] ); + try { + return $writer->export(); + } catch ( WP_Export_Exception $e ) { + return new WP_Error( 'wp-export-error', $e->getMessage() ); + } +} diff --git a/wp-settings.php b/wp-settings.php index 94a7f975b1a5..d552206e70da 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -130,6 +130,7 @@ require( ABSPATH . WPINC . '/export/class-wp-export-query.php' ); require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); require( ABSPATH . WPINC . '/export/writers.php' ); +require( ABSPATH . WPINC . '/export/functions.export.php' ); require( ABSPATH . WPINC . '/feed.php' ); require( ABSPATH . WPINC . '/bookmark.php' ); require( ABSPATH . WPINC . '/bookmark-template.php' ); From 8913ad2f8ef01c220f07c404349328a97cca7632 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Wed, 14 Nov 2012 23:55:32 +0200 Subject: [PATCH 35/56] Remove more WXR references --- wp-includes/export/class-wp-export-query.php | 2 +- wp-includes/export/writers.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 33a577d0e4fe..4e7c553ad222 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -255,5 +255,5 @@ private static function topologically_sort_terms( $terms ) { } } -class WP_WXR_Exception extends RuntimeException { +class WP_Export_Exception extends RuntimeException { } diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index faaf0e91f231..e7103320c623 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -1,5 +1,5 @@ f = fopen( $this->file_name, 'w' ); if ( !$this->f ) { - throw new WP_WXR_Exception( sprintf( __( 'WXR Export: error opening %s for writing.' ), $this->ile_name ) ); + throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $this->file_name ) ); } parent::export(); fclose( $this->f ); @@ -71,12 +71,12 @@ public function export() { protected function write( $xml ) { $res = fwrite( $this->f, $xml); if ( false === $res ) { - throw new WP_WXR_Exception( __( 'WXR Export: error writing to export file.' ) ); + throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); } } } -class WP_WXR_Split_Files_Writer extends WP_WXR_Base_Writer { +class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { private $result = ''; private $f; private $next_file_number = 0; @@ -105,7 +105,7 @@ public function export() { protected function write( $xml ) { $res = fwrite( $this->f, $xml); if ( false === $res ) { - throw new WP_WXR_Exception( __( 'WXR Export: error writing to export file.' ) ); + throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); } $this->current_file_size += strlen( $xml ); } @@ -117,7 +117,7 @@ private function start_new_file() { $file_path = $this->next_file_path(); $this->f = fopen( $file_path, 'w' ); if ( !$this->f ) { - throw new WP_WXR_Exception( sprintf( __( 'WXR Export: error opening %s for writing.' ), $file_path ) ); + throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $file_path ) ); } $this->current_file_size = 0; $this->write( $this->before_posts_xml ); From 8874067d6b189d2299285b8728b501c5d582b848 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sat, 26 Jan 2013 15:37:58 +0200 Subject: [PATCH 36/56] Remove duplicate YEAR_IN_SECONDS definition --- wp-includes/default-constants.php | 1 - 1 file changed, 1 deletion(-) diff --git a/wp-includes/default-constants.php b/wp-includes/default-constants.php index 9bdc8bfb2fef..87caf4b82b2c 100644 --- a/wp-includes/default-constants.php +++ b/wp-includes/default-constants.php @@ -87,7 +87,6 @@ function wp_initial_constants() { define( 'DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS ); define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS ); define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); - define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); // Constants for expressing human-readable data sizes // in their respective number of bytes. From 0e4eaa608bbb08026a822ae4a7fd251342843eff Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Wed, 30 Jan 2013 12:52:56 +0200 Subject: [PATCH 37/56] Assume the extra writers argument is single value If we have more than one, we'd better move them to an associative array. --- wp-includes/export/functions.export.php | 6 ++---- wp-includes/export/writers.php | 9 +++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/wp-includes/export/functions.export.php b/wp-includes/export/functions.export.php index 0109a18e5712..37a4c12fa58d 100644 --- a/wp-includes/export/functions.export.php +++ b/wp-includes/export/functions.export.php @@ -5,14 +5,12 @@ function wp_export( $args = array() ) { 'filters' => array(), 'format' => 'WP_WXR_XML_Generator', 'writer' => 'WP_Export_Returner', - 'writer_args' => array(), + 'writer_args' => null, ); $args = wp_parse_args( $args, $defaults ); $export_query = new WP_Export_Query( $args['filters'] ); $generator = new $args['format']( $export_query ); - array_unshift( $args['writer_args'], $generator ); - $writer_class = new ReflectionClass( $args['writer'] ); - $writer = $writer_class->newInstanceArgs( $args['writer_args'] ); + $writer = new $args['writer']( $generator, $args['writer_args'] ); try { return $writer->export(); } catch ( WP_Export_Exception $e ) { diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index e7103320c623..f265d5d3ab07 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -82,11 +82,12 @@ class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { private $next_file_number = 0; private $current_file_size = 0; - function __construct( $xml_generator, $destination_directory, $filename_template, $max_file_size = null ) { + function __construct( $xml_generator, $writer_args = array() ) { parent::__construct( $xml_generator ); - $this->max_file_size = is_null( $max_file_size ) ? 15 * MB_IN_BYTES : $max_file_size; - $this->destination_directory = $destination_directory; - $this->filename_template = $filename_template; + //TODO: check if args are not missing + $this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $max_file_size; + $this->destination_directory = $writer_args['destination_directory']; + $this->filename_template = $writer_args['filename_template']; $this->before_posts_xml = $this->xml_generator->before_posts(); $this->after_posts_xml = $this->xml_generator->after_posts(); } From 604aa0c5c005233618ae616cdaf20e3ade0ed486 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Wed, 30 Jan 2013 13:20:05 +0200 Subject: [PATCH 38/56] Rename XML generators to formatters --- ....php => class-wp-export-wxr-formatter.php} | 4 +-- wp-includes/export/functions.export.php | 6 ++-- wp-includes/export/writers.php | 30 +++++++++---------- wp-settings.php | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) rename wp-includes/export/{class-wp-wxr-xml-generator.php => class-wp-export-wxr-formatter.php} (98%) diff --git a/wp-includes/export/class-wp-wxr-xml-generator.php b/wp-includes/export/class-wp-export-wxr-formatter.php similarity index 98% rename from wp-includes/export/class-wp-wxr-xml-generator.php rename to wp-includes/export/class-wp-export-wxr-formatter.php index b0aca4cbdd3c..fd22e224e91b 100644 --- a/wp-includes/export/class-wp-wxr-xml-generator.php +++ b/wp-includes/export/class-wp-export-wxr-formatter.php @@ -11,9 +11,9 @@ require_once ABSPATH . WPINC . '/xml-builder/Builder.php'; /** - * Responsible for generating the WXR XML from the data in WP_Export_Query + * Responsible for formatting the data in WP_Export_Query to WXR */ -class WP_WXR_XML_Generator { +class WP_Export_WXR_Formatter { function __construct( $export ) { $this->export = $export; } diff --git a/wp-includes/export/functions.export.php b/wp-includes/export/functions.export.php index 37a4c12fa58d..dd3b1778c003 100644 --- a/wp-includes/export/functions.export.php +++ b/wp-includes/export/functions.export.php @@ -3,14 +3,14 @@ function wp_export( $args = array() ) { $defaults = array( 'filters' => array(), - 'format' => 'WP_WXR_XML_Generator', + 'format' => 'WP_Export_WXR_Formatter', 'writer' => 'WP_Export_Returner', 'writer_args' => null, ); $args = wp_parse_args( $args, $defaults ); $export_query = new WP_Export_Query( $args['filters'] ); - $generator = new $args['format']( $export_query ); - $writer = new $args['writer']( $generator, $args['writer_args'] ); + $formatter = new $args['format']( $export_query ); + $writer = new $args['writer']( $formatter, $args['writer_args'] ); try { return $writer->export(); } catch ( WP_Export_Exception $e ) { diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index f265d5d3ab07..02663f7913d6 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -1,17 +1,17 @@ xml_generator = $xml_generator; + function __construct( $formatter ) { + $this->formatter = $formatter; } public function export() { - $this->write( $this->xml_generator->before_posts() ); - foreach( $this->xml_generator->posts() as $post_in_wxr ) { + $this->write( $this->formatter->before_posts() ); + foreach( $this->formatter->posts() as $post_in_wxr ) { $this->write( $post_in_wxr ); } - $this->write( $this->xml_generator->after_posts() ); + $this->write( $this->formatter->after_posts() ); } abstract protected function write( $xml ); @@ -20,8 +20,8 @@ abstract protected function write( $xml ); class WP_Export_XML_Over_HTTP extends WP_Export_Base_Writer { private $file_name; - function __construct( $xml_generator, $file_name ) { - parent::__construct( $xml_generator ); + function __construct( $formatter, $file_name ) { + parent::__construct( $formatter ); $this->file_name = $file_name; } @@ -54,8 +54,8 @@ class WP_Export_File_Writer extends WP_Export_Base_Writer { private $f; private $file_name; - public function __construct( $xml_generator, $file_name ) { - parent::__construct( $xml_generator ); + public function __construct( $formatter, $file_name ) { + parent::__construct( $formatter ); $this->file_name = $file_name; } @@ -82,19 +82,19 @@ class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { private $next_file_number = 0; private $current_file_size = 0; - function __construct( $xml_generator, $writer_args = array() ) { - parent::__construct( $xml_generator ); + function __construct( $formatter, $writer_args = array() ) { + parent::__construct( $formatter ); //TODO: check if args are not missing $this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $max_file_size; $this->destination_directory = $writer_args['destination_directory']; $this->filename_template = $writer_args['filename_template']; - $this->before_posts_xml = $this->xml_generator->before_posts(); - $this->after_posts_xml = $this->xml_generator->after_posts(); + $this->before_posts_xml = $this->formatter->before_posts(); + $this->after_posts_xml = $this->formatter->after_posts(); } public function export() { $this->start_new_file(); - foreach( $this->xml_generator->posts() as $post_xml ) { + foreach( $this->formatter->posts() as $post_xml ) { if ( $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) { $this->start_new_file(); } diff --git a/wp-settings.php b/wp-settings.php index d552206e70da..7684175534c8 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -128,7 +128,7 @@ require( ABSPATH . WPINC . '/rewrite.php' ); require( ABSPATH . WPINC . '/iterators.php' ); require( ABSPATH . WPINC . '/export/class-wp-export-query.php' ); -require( ABSPATH . WPINC . '/export/class-wp-wxr-xml-generator.php' ); +require( ABSPATH . WPINC . '/export/class-wp-export-wxr-formatter.php' ); require( ABSPATH . WPINC . '/export/writers.php' ); require( ABSPATH . WPINC . '/export/functions.export.php' ); require( ABSPATH . WPINC . '/feed.php' ); From 36b8af592969560c48d09ec4f35226de6797cfa2 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sat, 9 Feb 2013 17:56:16 -0800 Subject: [PATCH 39/56] Gather post information on the export query Move the responsibility from the formatter to the query, so that all formatters can get the same information. --- wp-includes/export/class-wp-export-query.php | 39 +++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 4e7c553ad222..e7bd3c2bf83b 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -106,9 +106,12 @@ public function exportify_post( $post ) { $GLOBALS['wp_query']->in_the_loop = true; $GLOBALS['post'] = $post; setup_postdata( $post ); - $post->post_title_rss = apply_filters( 'the_title_rss', $post->post_title ); + $post->post_content = apply_filters( 'the_content_export', $post->post_content ); + $post->post_excerpt = apply_filters( 'the_excerpt_export', $post->post_excerpt ); $post->is_sticky = is_sticky( $post->ID ) ? 1 : 0; - // TODO: add the rest of the extra fields, modifications, etc. + $post->terms = self::get_terms_for_post( $post ); + $post->meta = self::get_meta_for_post( $post ); + $post->comments = self::get_comments_for_post( $post ); return $post; } @@ -253,6 +256,38 @@ private static function topologically_sort_terms( $terms ) { } return $sorted; } + + private static function get_terms_for_post( $post ) { + $taxonomies = get_object_taxonomies( $post->post_type ); + if ( empty( $taxonomies ) ) + return array(); + $terms = wp_get_object_terms( $post->ID, $taxonomies ); + $terms = $terms? $terms : array(); + return $terms; + } + + private static function get_meta_for_post( $post ) { + global $wpdb; + $meta_for_export = array(); + $meta_from_db = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) ); + foreach ( $meta_from_db as $meta ) { + if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) + continue; + $meta_for_export[] = $meta; + } + return $meta_for_export; + } + + private static function get_comments_for_post( $post ) { + global $wpdb; + $comments = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) ); + foreach( $comments as $comment ) { + $meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); + $meta = $meta? $meta : array(); + $comment->meta = $meta; + } + return $comments; + } } class WP_Export_Exception extends RuntimeException { From 15e064021474012ff8e28c7634c03af900879b6f Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Sat, 9 Feb 2013 17:57:20 -0800 Subject: [PATCH 40/56] Bring back the post global after we're finished --- wp-includes/export/class-wp-export-query.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index e7bd3c2bf83b..7236d397a1ee 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -104,6 +104,7 @@ public function nav_menu_terms() { public function exportify_post( $post ) { $GLOBALS['wp_query']->in_the_loop = true; + $previous_global_post = $GLOBALS['post']; $GLOBALS['post'] = $post; setup_postdata( $post ); $post->post_content = apply_filters( 'the_content_export', $post->post_content ); @@ -112,6 +113,7 @@ public function exportify_post( $post ) { $post->terms = self::get_terms_for_post( $post ); $post->meta = self::get_meta_for_post( $post ); $post->comments = self::get_comments_for_post( $post ); + $GLOBALS['post'] = $previous_global_post; return $post; } From bdb4f234a42d816f220190a4b34ebfd74c27ffb3 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 02:00:28 +0200 Subject: [PATCH 41/56] It should be OK if there's not global $post --- wp-includes/export/class-wp-export-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 7236d397a1ee..79e8d80dc4a3 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -104,7 +104,7 @@ public function nav_menu_terms() { public function exportify_post( $post ) { $GLOBALS['wp_query']->in_the_loop = true; - $previous_global_post = $GLOBALS['post']; + $previous_global_post = isset( $GLOBALS['post'] )? $GLOBALS['post'] : null; $GLOBALS['post'] = $post; setup_postdata( $post ); $post->post_content = apply_filters( 'the_content_export', $post->post_content ); From c70a2a7b02901ae286a8db3af5d7eab535278caa Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 02:00:53 +0200 Subject: [PATCH 42/56] We don't need terms' descriptions --- wp-includes/export/class-wp-export-query.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 79e8d80dc4a3..bdd63d49d22a 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -99,7 +99,11 @@ public function custom_taxonomies_terms() { } public function nav_menu_terms() { - return wp_get_nav_menus(); + $nav_menus = wp_get_nav_menus(); + foreach( $nav_menus as &$term ) { + $term->description = ''; + } + return $nav_menus; } public function exportify_post( $post ) { From 6f5f18c91c0d76405575bb5fa711ea0d8310aac6 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 02:03:01 +0200 Subject: [PATCH 43/56] Use Oxymel for generating XML More info at https://github.com/nb/oxymel --- wp-includes/Oxymel.php | 1 + .../export/class-wp-export-wxr-formatter.php | 309 +++++++++++------- 2 files changed, 190 insertions(+), 120 deletions(-) create mode 120000 wp-includes/Oxymel.php diff --git a/wp-includes/Oxymel.php b/wp-includes/Oxymel.php new file mode 120000 index 000000000000..4ad8fc53ef11 --- /dev/null +++ b/wp-includes/Oxymel.php @@ -0,0 +1 @@ +/Users/nb/dev/oxymel/Oxymel.php \ No newline at end of file diff --git a/wp-includes/export/class-wp-export-wxr-formatter.php b/wp-includes/export/class-wp-export-wxr-formatter.php index fd22e224e91b..5953b7742efe 100644 --- a/wp-includes/export/class-wp-export-wxr-formatter.php +++ b/wp-includes/export/class-wp-export-wxr-formatter.php @@ -8,7 +8,23 @@ */ define( 'WXR_VERSION', '1.2' ); -require_once ABSPATH . WPINC . '/xml-builder/Builder.php'; +require_once ABSPATH . WPINC . '/Oxymel.php'; + +class WP_Export_Oxymel extends Oxymel { + public function optional( $tag_name, $contents ) { + if ( $contents ) { + $this->$tag_name( $contents ); + } + return $this; + } + + public function optional_cdata( $tag_name, $contents ) { + if ( $contents ) { + $this->$tag_name->contains->cdata( $contents )->end; + } + return $this; + } +} /** * Responsible for formatting the data in WP_Export_Query to WXR @@ -16,6 +32,7 @@ class WP_Export_WXR_Formatter { function __construct( $export ) { $this->export = $export; + $this->wxr_version = WXR_VERSION; } function before_posts() { @@ -27,6 +44,7 @@ function before_posts() { $before_posts_xml .= $this->tags(); $before_posts_xml .= $this->nav_menu_terms(); $before_posts_xml .= $this->custom_taxonomies_terms(); + $before_posts_xml .= $this->rss2_head_action(); return $before_posts_xml; } @@ -39,111 +57,105 @@ function after_posts() { } function header() { - $wxr_version = WXR_VERSION; + $oxymel = new Oxymel; $charset = $this->export->charset(); $wp_generator_tag = $this->export->wp_generator_tag(); - return << - - - - - - - - - - - - - - - - -$wp_generator_tag - - - -XML; + $comment = <<xml + ->comment( $comment ) + ->raw( $wp_generator_tag ) + ->open_rss( array( + 'version' => '2.0', + 'xmlns:excerpt' => "http://wordpress.org/export/{$this->wxr_version}/excerpt/", + 'xmlns:content' => "http://purl.org/rss/1.0/modules/content/", + 'xmlns:wfw' => "http://wellformedweb.org/CommentAPI/", + 'xmlns:dc' => "http://purl.org/dc/elements/1.1/", + 'xmlns:wp' => "http://wordpress.org/export/{$this->wxr_version}/", + ) ) + ->open_channel + ->to_string(); + } function site_metadata() { + $oxymel = new Oxymel; $metadata = $this->export->site_metadata(); - return <<{$metadata['name']} - - - - - - - - -XML; + return $oxymel + ->title( $metadata['name'] ) + ->link( $metadata['url'] ) + ->description( $metadata['description'] ) + ->pubDate( $metadata['pubDate'] ) + ->language( $metadata['language'] ) + ->tag( 'wp:wxr_version', $this->wxr_version ) + ->tag( 'wp:base_site_url', $metadata['site_url'] ) + ->tag( 'wp:base_blog_url', $metadata['blog_url'] ) + ->to_string(); } function authors() { + $oxymel = new Oxymel; $authors = $this->export->authors(); - $xml = ''; foreach ( $authors as $author ) { - self::make_object_fields_cdata( $author, array( 'display_name', 'user_firstname', 'user_lastname' ) ); - $xml .= << - {$author->ID} - {$author->user_login} - {$author->user_email} - {$author->display_name_cdata} - {$author->user_firstname_cdata} - {$author->user_lastname_cdata} - - -XML; + $oxymel + ->tag( 'wp:wp_author' )->contains + ->tag( 'wp:author_login', $author->user_login ) + ->tag( 'wp:author_email', $author->user_email ) + ->tag( 'wp:author_display_name' )->contains->cdata( $author->display_name )->end + ->tag( 'wp:author_first_name' )->contains->cdata( $author->user_first_name )->end + ->tag( 'wp:author_last_name' )->contains->cdata( $author->user_last_name )->end + ->end; } - return $xml; + return $oxymel->to_string(); } function categories() { + $oxymel = new WP_Export_Oxymel; $categories = $this->export->categories(); - $xml = ''; foreach( $categories as $term_id => $category ) { - self::make_object_fields_cdata( $category, array( 'name', 'description' ) ); $category->parent_slug = $category->parent? $categories[$category->parent]->slug : ''; - $xml .= << - {$category->term_id} - {$category->slug} - {$category->parent_slug} - {$category->name_cdata} - {$category->description_cdata} - - -XML; + $oxymel->tag( 'wp:category' )->contains + ->tag( 'wp:term_id', $category->term_id ) + ->tag( 'wp:category_nicename', $category->slug ) + ->tag( 'wp:category_parent', $category->parent_slug ) + ->optional_cdata( 'wp:cat_name', $category->name ) + ->optional_cdata( 'wp:category_description', $category->description ) + ->end; } - return $xml; + return $oxymel->to_string(); } function tags() { + $oxymel = new WP_Export_Oxymel; $tags = $this->export->tags(); - $xml = ''; foreach( $tags as $tag ) { - self::make_object_fields_cdata( $tag, array( 'name', 'description' ) ); - $xml .= << - {$tag->term_id} - {$tag->slug} - {$tag->name_cdata} - {$tag->description_cdata} - - -XML; + $oxymel->tag( 'wp:tag' )->contains + ->tag( 'wp:term_id', $tag->term_id ) + ->tag( 'wp:tag_slug', $tag->slug ) + ->optional_cdata( 'wp:tag_name', $tag->name ) + ->optional_cdata( 'wp:tag_description', $tag->description ) + ->end; } - return $xml; + return $oxymel->to_string(); } function nav_menu_terms() { @@ -154,57 +166,114 @@ function custom_taxonomies_terms() { return $this->terms( $this->export->custom_taxonomies_terms() ); } + function rss2_head_action() { + ob_start(); + do_action( 'rss2_head' ); + $action_output = ob_get_clean(); + return $action_output; + } + private function terms( $terms ) { - $xml = ''; + $oxymel = new WP_Export_Oxymel; foreach( $terms as $term ) { $term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; - self::make_object_fields_cdata( $term, array( 'name', 'description' ) ); - $xml .= << - {$term->term_id} - {$term->taxonomy} - {$term->slug} - {$term->parent_slug} - {$term->name_cdata} - {$term->description_cdata} - - -XML; + $oxymel->tag( 'wp:term' )->contains + ->tag( 'wp:term_id', $term->term_id ) + ->tag( 'wp:term_taxonomy', $term->taxonomy ) + ->tag( 'wp:term_slug', $term->slug ); + if ( 'nav_menu' != $term->taxonomy ) { + $oxymel + ->tag( 'wp:term_parent', $term->parent_slug ); + } + $oxymel + ->optional_cdata( 'wp:term_name', $term->name ) + ->optional_cdata( 'wp:term_description', $term->description ) + ->end; } - return $xml; + return $oxymel->to_string(); } function post( $post ) { - global $wpdb; - $xml = << - {$post->post_title_rss} - - {$post->is_sticky} - - -XML; - return $xml; - } + $oxymel = new WP_Export_Oxymel; + $GLOBALS['wp_query']->in_the_loop = true; + $GLOBALS['post'] = $post; + setup_postdata( $post ); - function footer() { - return << - -XML; + $oxymel->item->contains + ->title( apply_filters( 'the_title_rss', $post->post_title ) ) + ->link( apply_filters('the_permalink_rss', get_permalink() ) ) + ->pubDate( mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) ) + ->tag( 'dc:creator', get_the_author_meta( 'login' ) ) + ->guid( get_the_guid(), array( 'isPermaLink' => 'false' ) ) + ->description( '' ) + ->tag( 'content:encoded' )->contains->cdata( $post->post_content )->end + ->tag( 'excerpt:encoded' )->contains->cdata( $post->post_excerpt )->end + ->tag( 'wp:post_id', $post->ID ) + ->tag( 'wp:post_date', $post->post_date ) + ->tag( 'wp:post_date_gmt', $post->post_date_gmt ) + ->tag( 'wp:comment_status', $post->comment_status ) + ->tag( 'wp:ping_status', $post->ping_status ) + ->tag( 'wp:post_name', $post->post_name ) + ->tag( 'wp:status', $post->post_status ) + ->tag( 'wp:post_parent', $post->post_parent ) + ->tag( 'wp:menu_order', $post->menu_order ) + ->tag( 'wp:post_type', $post->post_type ) + ->tag( 'wp:post_password', $post->post_password ) + ->tag( 'wp:is_sticky', $post->is_sticky ) + ->optional( 'wp:attachment_url', wp_get_attachment_url( $post->ID ) ); + foreach( $post->terms as $term ) { + $oxymel + ->category( array( 'domain' => $term->taxonomy, 'nicename' => $term->slug ) )->contains->cdata( $term->name )->end; + } + foreach( $post->meta as $meta ) { + $oxymel + ->tag( 'wp:postmeta' )->contains + ->tag( 'wp:meta_key', $meta->meta_key ) + ->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end + ->end; + } + foreach( $post->comments as $comment ) { + $oxymel + ->tag( 'wp:comment' )->contains + ->tag( 'wp:comment_id', $comment->comment_ID ) + ->tag( 'wp:comment_author' )->contains->cdata( $comment->comment_author )->end + ->tag( 'wp:comment_author_email', $comment->comment_author_email ) + ->tag( 'wp:comment_author_url', $comment->comment_author_url ) + ->tag( 'wp:comment_author_IP', $comment->comment_author_IP ) + ->tag( 'wp:comment_date', $comment->comment_date ) + ->tag( 'wp:comment_date_gmt', $comment->comment_date_gmt ) + ->tag( 'wp:comment_content' )->contains->cdata( $comment->comment_content )->end + ->tag( 'wp:comment_approved', $comment->comment_approved ) + ->tag( 'wp:comment_type', $comment->comment_type ) + ->tag( 'wp:comment_parent', $comment->comment_parent ) + ->tag( 'wp:comment_user_id', $comment->user_id ) + ->oxymel( $this->comment_meta( $comment ) ) + ->end; + } + $oxymel + ->end; + return $oxymel->to_string(); } - private static function make_object_fields_cdata( $object, $fields = array() ) { - foreach( $fields as $field ) { - $field_cdata = "{$field}_cdata"; - $object->$field_cdata = self::cdata( $object->$field ); + private function comment_meta( $comment ) { + global $wpdb; + $metas = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); + if ( !$metas ) { + return new Oxymel; + } + $oxymel = new WP_Export_Oxymel; + foreach( $metas as $meta ) { + $oxymel->tag( 'wp:commentmeta' )->contains + ->tag( 'wp:meta_key', $meta->meta_key ) + ->tag( 'wp:meta_value', $meta->meta_value ) + ->end; } + return $oxymel; } - private static function cdata( $text ) { - if ( !seems_utf8( $text ) ) { - $text = utf8_encode( $text ); - } - return '', ']]]]>', $text ) . ']]>'; + + function footer() { + $oxymel = new Oxymel; + return $oxymel->close_channel->close_rss->to_string(); } } From db2f9c52af82f4a62dfb50d05125c1addfc4eb42 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 02:04:40 +0200 Subject: [PATCH 44/56] Don't use the Oxymel symlink --- wp-includes/Oxymel.php | 212 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) mode change 120000 => 100644 wp-includes/Oxymel.php diff --git a/wp-includes/Oxymel.php b/wp-includes/Oxymel.php deleted file mode 120000 index 4ad8fc53ef11..000000000000 --- a/wp-includes/Oxymel.php +++ /dev/null @@ -1 +0,0 @@ -/Users/nb/dev/oxymel/Oxymel.php \ No newline at end of file diff --git a/wp-includes/Oxymel.php b/wp-includes/Oxymel.php new file mode 100644 index 000000000000..857329e1f897 --- /dev/null +++ b/wp-includes/Oxymel.php @@ -0,0 +1,211 @@ +xml = ''; + $this->init_new_dom(); + } + + public function to_string() { + return $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level ); + } + + public function __call( $name, $args ) { + array_unshift( $args, $name ); + return call_user_func_array( array( $this, 'tag' ), $args ); + } + + public function __get( $name ) { + return $this->$name(); + } + + public function contains() { + $this->contains_nesting_level++; + $this->nesting_level++; + if ( $this->go_deep_on_next_element ) { + throw new OxymelException( 'contains cannot be used consecutively more than once' ); + } + $this->go_deep_on_next_element++; + return $this; + } + + public function end() { + $this->contains_nesting_level--; + $this->nesting_level--; + if ( $this->contains_nesting_level < 0 ) { + throw new OxymelException( 'end is used without a matching contains' ); + } + $this->go_up_on_next_element++; + return $this; + } + + public function tag( $name, $content_or_attributes = null, $attributes = array() ) { + list( $content, $attributes ) = $this->get_content_and_attributes_from_tag_args( $content_or_attributes, $attributes ); + $is_opening = 0 === strpos( $name, 'open_' ); + $is_closing = 0 === strpos( $name, 'close_' ); + $name = preg_replace("/^(open|close)_/", '', $name ); + + $element = $this->create_element( $name, $content, $attributes ); + + if ( !$is_opening && !$is_closing ) + $this->add_element_to_dom( $element ); + elseif ( $is_opening ) + $this->add_opening_tag_from_element( $element ); + elseif ( $is_closing ) + $this->add_closing_tag_from_tag_name( $name ); + + return $this; + } + + public function cdata( $text ) { + $this->add_element_to_dom( $this->dom->createCDATASection( $text ) ); + return $this; + } + + public function text( $text ) { + $this->add_element_to_dom( $this->dom->createTextNode( $text ) ); + return $this; + } + + public function comment( $text ) { + $this->add_element_to_dom( $this->dom->createComment( $text ) ); + return $this; + } + + public function xml() { + $this->add_element_to_dom( $this->dom->createProcessingInstruction( 'xml', 'version="1.0" encoding="UTF-8"' ) ); + return $this; + } + + public function oxymel( Oxymel $other ) { + foreach( $other->dom->childNodes as $child ) { + $child = $this->dom->importNode( $child, true ); + $this->add_element_to_dom( $child ); + } + return $this; + } + + public function raw( $raw_xml ) { + if ( !$raw_xml ) { + return $this; + } + $fragment = $this->dom->createDocumentFragment(); + $fragment->appendXML($raw_xml); + $this->add_element_to_dom( $fragment ); + return $this; + } + + private function add_element_to_dom( $element ) { + $this->move_current_element_deep(); + $this->move_current_element_up(); + $this->last_inserted = $this->current_element->appendChild($element); + } + + private function move_current_element_deep() { + if ( $this->go_deep_on_next_element ) { + if ( !$this->last_inserted ) { + throw new OxymelException( 'contains has been used before adding any tags' ); + } + $this->current_element = $this->last_inserted; + $this->go_deep_on_next_element--; + } + } + + private function move_current_element_up() { + if ( $this->go_up_on_next_element ) { + while ( $this->go_up_on_next_element ) { + $this->current_element = $this->current_element->parentNode; + $this->go_up_on_next_element--; + } + } + } + + private function get_content_and_attributes_from_tag_args( $content_or_attributes, array $attributes ) { + $content = null; + if ( !$attributes ) { + if ( is_array( $content_or_attributes ) ) + $attributes = $content_or_attributes; + else + $content = $content_or_attributes; + } else { + $content = $content_or_attributes; + } + return array( $content, $attributes ); + } + + private function init_new_dom() { + unset( $this->dom, $this->current_element ); + $this->dom = new DOMDocument(); + $this->dom->formatOutput = true; + $this->current_element = $this->dom; + $this->last_inserted = null; + } + + private function xml_from_dom() { + if ( 0 !== $this->contains_nesting_level ) { + throw new OxymelException( 'contains and end calls do not match' ); + } + $xml = ''; + foreach( $this->dom->childNodes as $child ) { + $xml .= $this->dom->saveXML( $child ) . "\n"; + } + return $xml; + } + + private function create_element( $name, $content, $attributes ) { + if ( !is_null( $content ) ) + $element = $this->dom->createElement( $name, $content ); + else + $element = $this->dom->createElement( $name ); + + foreach( $attributes as $attribute_name => $attribute_value ) { + $element->setAttribute( $attribute_name, $attribute_value ); + } + + return $element; + } + + private function add_opening_tag_from_element( $element ) { + $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level ); + $tag = $this->dom->saveXML($element); + $this->xml .= $this->indent( str_replace( '/>', '>', $tag ) . "\n", $this->nesting_level ); + $this->nesting_level++; + $this->init_new_dom(); + } + + private function add_closing_tag_from_tag_name( $name ) { + $this->xml .= $this->xml_from_dom(); + $this->nesting_level--; + if ( $this->nesting_level < 0 ) { + $this->xml = $this->indent( $this->xml, -$this->nesting_level ); + $this->nesting_level = 0; + } + $this->xml .= $this->indent( "\n", $this->nesting_level ); + $this->init_new_dom(); + } + + private function indent( $string, $level ) { + if ( !$level ) { + return $string; + } + $lines = explode( "\n", $string ); + foreach( $lines as &$line ) { + if ( !trim( $line ) ) + continue; + $line = str_repeat( $this->indentation, $level ) . $line; + } + return implode( "\n", $lines ); + } +} + +class OxymelException extends Exception { +} From 93e1e7b1d7853902e3857787ce84d6a71efe85c9 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 15:16:53 +0200 Subject: [PATCH 45/56] Move WP_Export_Oxymel into its own file --- wp-includes/export/class-wp-export-oxymel.php | 27 +++++++++++++++++++ .../export/class-wp-export-wxr-formatter.php | 18 +------------ 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 wp-includes/export/class-wp-export-oxymel.php diff --git a/wp-includes/export/class-wp-export-oxymel.php b/wp-includes/export/class-wp-export-oxymel.php new file mode 100644 index 000000000000..6c2bc9674276 --- /dev/null +++ b/wp-includes/export/class-wp-export-oxymel.php @@ -0,0 +1,27 @@ +$tag_name( $contents ); + } + return $this; + } + + public function optional_cdata( $tag_name, $contents ) { + if ( $contents ) { + $this->$tag_name->contains->cdata( $contents )->end; + } + return $this; + } + + public function cdata( $text ) { + if ( !seems_utf8( $text ) ) { + $text = utf8_encode( $text ); + } + return parent::cdata( $text ); + } +} + diff --git a/wp-includes/export/class-wp-export-wxr-formatter.php b/wp-includes/export/class-wp-export-wxr-formatter.php index 5953b7742efe..5b682c6e6e4b 100644 --- a/wp-includes/export/class-wp-export-wxr-formatter.php +++ b/wp-includes/export/class-wp-export-wxr-formatter.php @@ -8,23 +8,7 @@ */ define( 'WXR_VERSION', '1.2' ); -require_once ABSPATH . WPINC . '/Oxymel.php'; - -class WP_Export_Oxymel extends Oxymel { - public function optional( $tag_name, $contents ) { - if ( $contents ) { - $this->$tag_name( $contents ); - } - return $this; - } - - public function optional_cdata( $tag_name, $contents ) { - if ( $contents ) { - $this->$tag_name->contains->cdata( $contents )->end; - } - return $this; - } -} +require_once ABSPATH . WPINC . '/export/class-wp-export-oxymel.php'; /** * Responsible for formatting the data in WP_Export_Query to WXR From 2b5ac7f022c6adf0a1eec5ad9139f9d84d87beaa Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 15:24:10 +0200 Subject: [PATCH 46/56] Add explicit function access identifiers --- .../export/class-wp-export-wxr-formatter.php | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/wp-includes/export/class-wp-export-wxr-formatter.php b/wp-includes/export/class-wp-export-wxr-formatter.php index 5b682c6e6e4b..78407d0e108e 100644 --- a/wp-includes/export/class-wp-export-wxr-formatter.php +++ b/wp-includes/export/class-wp-export-wxr-formatter.php @@ -14,12 +14,12 @@ * Responsible for formatting the data in WP_Export_Query to WXR */ class WP_Export_WXR_Formatter { - function __construct( $export ) { + public function __construct( $export ) { $this->export = $export; $this->wxr_version = WXR_VERSION; } - function before_posts() { + public function before_posts() { $before_posts_xml = ''; $before_posts_xml .= $this->header(); $before_posts_xml .= $this->site_metadata(); @@ -32,15 +32,15 @@ function before_posts() { return $before_posts_xml; } - function posts() { + public function posts() { return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); } - function after_posts() { + public function after_posts() { return $this->footer(); } - function header() { + public function header() { $oxymel = new Oxymel; $charset = $this->export->charset(); $wp_generator_tag = $this->export->wp_generator_tag(); @@ -81,7 +81,7 @@ function header() { } - function site_metadata() { + public function site_metadata() { $oxymel = new Oxymel; $metadata = $this->export->site_metadata(); return $oxymel @@ -96,7 +96,7 @@ function site_metadata() { ->to_string(); } - function authors() { + public function authors() { $oxymel = new Oxymel; $authors = $this->export->authors(); foreach ( $authors as $author ) { @@ -112,7 +112,7 @@ function authors() { return $oxymel->to_string(); } - function categories() { + public function categories() { $oxymel = new WP_Export_Oxymel; $categories = $this->export->categories(); foreach( $categories as $term_id => $category ) { @@ -128,7 +128,7 @@ function categories() { return $oxymel->to_string(); } - function tags() { + public function tags() { $oxymel = new WP_Export_Oxymel; $tags = $this->export->tags(); foreach( $tags as $tag ) { @@ -142,42 +142,22 @@ function tags() { return $oxymel->to_string(); } - function nav_menu_terms() { + public function nav_menu_terms() { return $this->terms( $this->export->nav_menu_terms() ); } - function custom_taxonomies_terms() { + public function custom_taxonomies_terms() { return $this->terms( $this->export->custom_taxonomies_terms() ); } - function rss2_head_action() { + public function rss2_head_action() { ob_start(); do_action( 'rss2_head' ); $action_output = ob_get_clean(); return $action_output; } - private function terms( $terms ) { - $oxymel = new WP_Export_Oxymel; - foreach( $terms as $term ) { - $term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; - $oxymel->tag( 'wp:term' )->contains - ->tag( 'wp:term_id', $term->term_id ) - ->tag( 'wp:term_taxonomy', $term->taxonomy ) - ->tag( 'wp:term_slug', $term->slug ); - if ( 'nav_menu' != $term->taxonomy ) { - $oxymel - ->tag( 'wp:term_parent', $term->parent_slug ); - } - $oxymel - ->optional_cdata( 'wp:term_name', $term->name ) - ->optional_cdata( 'wp:term_description', $term->description ) - ->end; - } - return $oxymel->to_string(); - } - - function post( $post ) { + public function post( $post ) { $oxymel = new WP_Export_Oxymel; $GLOBALS['wp_query']->in_the_loop = true; $GLOBALS['post'] = $post; @@ -239,7 +219,32 @@ function post( $post ) { return $oxymel->to_string(); } - private function comment_meta( $comment ) { + public function footer() { + $oxymel = new Oxymel; + return $oxymel->close_channel->close_rss->to_string(); + } + + protected function terms( $terms ) { + $oxymel = new WP_Export_Oxymel; + foreach( $terms as $term ) { + $term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; + $oxymel->tag( 'wp:term' )->contains + ->tag( 'wp:term_id', $term->term_id ) + ->tag( 'wp:term_taxonomy', $term->taxonomy ) + ->tag( 'wp:term_slug', $term->slug ); + if ( 'nav_menu' != $term->taxonomy ) { + $oxymel + ->tag( 'wp:term_parent', $term->parent_slug ); + } + $oxymel + ->optional_cdata( 'wp:term_name', $term->name ) + ->optional_cdata( 'wp:term_description', $term->description ) + ->end; + } + return $oxymel->to_string(); + } + + protected function comment_meta( $comment ) { global $wpdb; $metas = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); if ( !$metas ) { @@ -254,10 +259,4 @@ private function comment_meta( $comment ) { } return $oxymel; } - - - function footer() { - $oxymel = new Oxymel; - return $oxymel->close_channel->close_rss->to_string(); - } } From ae91937f758bb711f1edb40102e7f2f6293d9424 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 16:41:23 +0200 Subject: [PATCH 47/56] Use the end of a month for end date --- wp-includes/export/class-wp-export-query.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index bdd63d49d22a..c38a3c84da6f 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -191,13 +191,21 @@ private function start_date_where() { private function end_date_where() { global $wpdb; - $timestamp = strtotime( $this->filters['end_date'] ); + if ( preg_match( '/^\d{4}-\d{2}$/', $this->filters['end_date'] ) ) { + $timestamp = $this->get_timestamp_for_the_last_day_of_a_month( $this->filters['end_date'] ); + } else { + $timestamp = strtotime( $this->filters['end_date'] ); + } if ( !$timestamp ) { return; } $this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); } + private function get_timestamp_for_the_last_day_of_a_month( $yyyy_mm ) { + return strtotime( "$yyyy_mm +1month -1day" ); + } + private function category_where() { global $wpdb; if ( 'post' != $this->filters['post_type'] ) { From a0515703f6d315c89d8e52a69e91f77e26f656d2 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 16:41:44 +0200 Subject: [PATCH 48/56] Introduce a function to convert old args to new --- wp-includes/export/functions.export.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wp-includes/export/functions.export.php b/wp-includes/export/functions.export.php index dd3b1778c003..441736ef8752 100644 --- a/wp-includes/export/functions.export.php +++ b/wp-includes/export/functions.export.php @@ -17,3 +17,14 @@ function wp_export( $args = array() ) { return new WP_Error( 'wp-export-error', $e->getMessage() ); } } + +function wp_export_new_style_args_from_old_style_args( $args ) { + if ( isset( $args['content'] ) ) { + if ( 'all' == $args['content'] ) { + unset( $args['content'] ); + } else { + $args['post_type'] = $args['content']; + } + } + return $args; +} From fc5adb79ccfabf899386e30a5fa049ed2cf2fd6d Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 16:59:33 +0200 Subject: [PATCH 49/56] Always return an array, even if it's only one cat --- wp-includes/export/class-wp-export-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index c38a3c84da6f..4171084aac9b 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -70,7 +70,7 @@ public function authors() { public function categories() { if ( $this->category ) { - return $this->category; + return array( $this->category ); } if ( $this->filters['post_type'] ) { return array(); From 5c507dc867dcb9628c8d454eede81fa23426fe0b Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 16:59:52 +0200 Subject: [PATCH 50/56] Switch from export_wp() to wp_export() --- wp-admin/export.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wp-admin/export.php b/wp-admin/export.php index 4f67c08520a8..233629ed6a92 100644 --- a/wp-admin/export.php +++ b/wp-admin/export.php @@ -103,7 +103,15 @@ function export_add_js() { */ $args = apply_filters( 'export_args', $args ); - export_wp( $args ); + $args = wp_export_new_style_args_from_old_style_args( $args ); + + $sitename = sanitize_key( get_bloginfo( 'name' ) ); + if ( ! empty($sitename) ) $sitename .= '.'; + $file_name = $sitename . 'wordpress.' . date( 'Y-m-d' ) . '.xml'; + + do_action( 'export_wp' ); + + wp_export( array( 'filters' => $args, 'writer' => 'WP_Export_XML_Over_HTTP', 'writer_args' => $file_name ) ); die(); } From dceaaf20ca7915477721918f23725cb2543898cc Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 18:50:22 +0200 Subject: [PATCH 51/56] Skip some useless/too revealing metadata --- wp-includes/export/class-wp-export-query.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wp-includes/export/class-wp-export-query.php b/wp-includes/export/class-wp-export-query.php index 4171084aac9b..5522cc06a815 100644 --- a/wp-includes/export/class-wp-export-query.php +++ b/wp-includes/export/class-wp-export-query.php @@ -287,6 +287,9 @@ private static function get_meta_for_post( $post ) { foreach ( $meta_from_db as $meta ) { if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) continue; + if ( in_array( $meta->meta_key, array( '_edit_lock', '_wp_attachment_metadata', '_wp_attached_file' ) ) ) { + continue; + } $meta_for_export[] = $meta; } return $meta_for_export; From 19af0010258fa4368e7ebec54ec65b328e87d0f0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 19 Feb 2013 19:49:34 +0200 Subject: [PATCH 52/56] Escape some URLs to prevent DOMDocument warnings See http://www.php.net/manual/en/domdocument.createelement.php#73617 We'll need to investigate this further. --- wp-includes/export/class-wp-export-wxr-formatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wp-includes/export/class-wp-export-wxr-formatter.php b/wp-includes/export/class-wp-export-wxr-formatter.php index 78407d0e108e..f4850ac3251e 100644 --- a/wp-includes/export/class-wp-export-wxr-formatter.php +++ b/wp-includes/export/class-wp-export-wxr-formatter.php @@ -165,7 +165,7 @@ public function post( $post ) { $oxymel->item->contains ->title( apply_filters( 'the_title_rss', $post->post_title ) ) - ->link( apply_filters('the_permalink_rss', get_permalink() ) ) + ->link( esc_url( apply_filters('the_permalink_rss', get_permalink() ) ) ) ->pubDate( mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) ) ->tag( 'dc:creator', get_the_author_meta( 'login' ) ) ->guid( get_the_guid(), array( 'isPermaLink' => 'false' ) ) @@ -202,7 +202,7 @@ public function post( $post ) { ->tag( 'wp:comment_id', $comment->comment_ID ) ->tag( 'wp:comment_author' )->contains->cdata( $comment->comment_author )->end ->tag( 'wp:comment_author_email', $comment->comment_author_email ) - ->tag( 'wp:comment_author_url', $comment->comment_author_url ) + ->tag( 'wp:comment_author_url', esc_url( $comment->comment_author_url ) ) ->tag( 'wp:comment_author_IP', $comment->comment_author_IP ) ->tag( 'wp:comment_date', $comment->comment_date ) ->tag( 'wp:comment_date_gmt', $comment->comment_date_gmt ) From 05c3cd903ba381bdf9651fbbd8262e894d48ad60 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 5 Mar 2013 11:41:49 +0200 Subject: [PATCH 53/56] Solidify operator precendence --- wp-includes/export/writers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 02663f7913d6..c2dff2d9578e 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -95,7 +95,7 @@ function __construct( $formatter, $writer_args = array() ) { public function export() { $this->start_new_file(); foreach( $this->formatter->posts() as $post_xml ) { - if ( $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) { + if ( ( $this->current_file_size + strlen( $post_xml ) ) > $this->max_file_size ) { $this->start_new_file(); } $this->write( $post_xml ); From ec7af906823dd7d9ac2ee29221b5184dfa496a51 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Tue, 5 Mar 2013 11:44:32 +0200 Subject: [PATCH 54/56] Use correct writer arg value --- wp-includes/export/writers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index c2dff2d9578e..52a68d7025cd 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -85,7 +85,7 @@ class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { function __construct( $formatter, $writer_args = array() ) { parent::__construct( $formatter ); //TODO: check if args are not missing - $this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $max_file_size; + $this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $writer_args['max_file_size']; $this->destination_directory = $writer_args['destination_directory']; $this->filename_template = $writer_args['filename_template']; $this->before_posts_xml = $this->formatter->before_posts(); From c6ad2cf1a1c2a9ee15c0ae92f582071e43388fc0 Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Fri, 25 Oct 2013 16:25:19 +0100 Subject: [PATCH 55/56] Initialize the correct variable --- wp-includes/export/writers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 52a68d7025cd..9a558244da82 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -41,7 +41,7 @@ class WP_Export_Returner extends WP_Export_Base_Writer { private $result = ''; public function export() { - $this->private = ''; + $this->result = ''; parent::export(); return $this->result; } From 5d2e4e9e34812c16ee75314dae27d42f2601afaa Mon Sep 17 00:00:00 2001 From: Nikolay Bachiyski Date: Fri, 25 Oct 2013 16:56:34 +0100 Subject: [PATCH 56/56] Don't output anything before successful export Currently when exporting over HTTP, if an error occurs mid-way we will halt export, after we already had output some headers and part of the export. This makes for a louse user experience, so here we accumulate the export and only output it if nothing stopped the execution. Props @dllh: http://core.trac.wordpress.org/ticket/22435#comment:33 --- wp-includes/export/writers.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/wp-includes/export/writers.php b/wp-includes/export/writers.php index 9a558244da82..57d4782f8120 100644 --- a/wp-includes/export/writers.php +++ b/wp-includes/export/writers.php @@ -26,14 +26,25 @@ function __construct( $formatter, $file_name ) { } public function export() { - header( 'Content-Description: File Transfer' ); - header( 'Content-Disposition: attachment; filename=' . $this->file_name ); - header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); - parent::export(); + $export = $this->get_export(); + $this->send_headers(); + echo $export; } protected function write( $xml ) { - echo $xml; + $this->result .= $xml; + } + + protected function get_export() { + $this->result = ''; + parent::export(); + return $this->result; + } + + protected function send_headers() { + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: attachment; filename=' . $this->file_name ); + header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); } }