WordPress.org

Make WordPress Core

Ticket #22435: export.5.diff

File export.5.diff, 54.2 KB (added by rodrigosprimo, 4 years ago)

Consolidate all the previous patches (except export.diff) into a single patch to make it easier to test and use the new exporter

  • new file src/wp-includes/Oxymel.php

    diff --git a/src/wp-includes/Oxymel.php b/src/wp-includes/Oxymel.php
    new file mode 100644
    index 0000000..857329e
    - +  
     1<?php
     2class Oxymel {
     3        private $xml;
     4        private $dom;
     5        private $current_element;
     6        private $last_inserted;
     7        private $go_deep_on_next_element = 0;
     8        private $go_up_on_next_element = 0;
     9        private $nesting_level = 0;
     10        private $contains_nesting_level = 0;
     11        private $indentation= '  ';
     12
     13        public function __construct() {
     14                $this->xml = '';
     15                $this->init_new_dom();
     16        }
     17
     18        public function to_string() {
     19                return $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level );
     20        }
     21
     22        public function __call( $name, $args ) {
     23                array_unshift( $args, $name );
     24                return call_user_func_array( array( $this, 'tag' ), $args );
     25        }
     26
     27        public function __get( $name ) {
     28                return $this->$name();
     29        }
     30
     31        public function contains() {
     32                $this->contains_nesting_level++;
     33                $this->nesting_level++;
     34                if ( $this->go_deep_on_next_element ) {
     35                        throw new OxymelException( 'contains cannot be used consecutively more than once' );
     36                }
     37                $this->go_deep_on_next_element++;
     38                return $this;
     39        }
     40
     41        public function end() {
     42                $this->contains_nesting_level--;
     43                $this->nesting_level--;
     44                if ( $this->contains_nesting_level < 0 ) {
     45                        throw new OxymelException( 'end is used without a matching contains' );
     46                }
     47                $this->go_up_on_next_element++;
     48                return $this;
     49        }
     50
     51        public function tag( $name, $content_or_attributes = null, $attributes = array() ) {
     52                list( $content, $attributes ) = $this->get_content_and_attributes_from_tag_args( $content_or_attributes, $attributes );
     53                $is_opening =  0 === strpos( $name, 'open_' );
     54                $is_closing =  0 === strpos( $name, 'close_' );
     55                $name = preg_replace("/^(open|close)_/", '', $name );
     56
     57                $element = $this->create_element( $name, $content, $attributes );
     58
     59                if ( !$is_opening && !$is_closing )
     60                        $this->add_element_to_dom( $element );
     61                elseif ( $is_opening )
     62                        $this->add_opening_tag_from_element( $element );
     63                elseif ( $is_closing )
     64                        $this->add_closing_tag_from_tag_name( $name );
     65
     66                return $this;
     67        }
     68
     69        public function cdata( $text ) {
     70                $this->add_element_to_dom( $this->dom->createCDATASection( $text ) );
     71                return $this;
     72        }
     73
     74        public function text( $text ) {
     75                $this->add_element_to_dom( $this->dom->createTextNode( $text ) );
     76                return $this;
     77        }
     78
     79        public function comment( $text ) {
     80                $this->add_element_to_dom( $this->dom->createComment( $text ) );
     81                return $this;
     82        }
     83
     84        public function xml() {
     85                $this->add_element_to_dom( $this->dom->createProcessingInstruction( 'xml', 'version="1.0" encoding="UTF-8"' ) );
     86                return $this;
     87        }
     88
     89        public function oxymel( Oxymel $other ) {
     90                foreach( $other->dom->childNodes as $child ) {
     91                        $child = $this->dom->importNode( $child, true );
     92                        $this->add_element_to_dom( $child );
     93                }
     94                return $this;
     95        }
     96
     97        public function raw(  $raw_xml ) {
     98                if ( !$raw_xml ) {
     99                        return $this;
     100                }
     101                $fragment = $this->dom->createDocumentFragment();
     102                $fragment->appendXML($raw_xml);
     103                $this->add_element_to_dom( $fragment );
     104                return $this;
     105        }
     106
     107        private function add_element_to_dom( $element ) {
     108                $this->move_current_element_deep();
     109                $this->move_current_element_up();
     110                $this->last_inserted = $this->current_element->appendChild($element);
     111        }
     112
     113        private function move_current_element_deep() {
     114                if ( $this->go_deep_on_next_element ) {
     115                        if ( !$this->last_inserted ) {
     116                                throw new OxymelException( 'contains has been used before adding any tags' );
     117                        }
     118                        $this->current_element = $this->last_inserted;
     119                        $this->go_deep_on_next_element--;
     120                }
     121        }
     122
     123        private function move_current_element_up() {
     124                if ( $this->go_up_on_next_element ) {
     125                        while ( $this->go_up_on_next_element ) {
     126                                $this->current_element = $this->current_element->parentNode;
     127                                $this->go_up_on_next_element--;
     128                        }
     129                }
     130        }
     131
     132        private function get_content_and_attributes_from_tag_args( $content_or_attributes, array $attributes ) {
     133                $content = null;
     134                if ( !$attributes ) {
     135                        if ( is_array( $content_or_attributes ) )
     136                                $attributes = $content_or_attributes;
     137                        else
     138                                $content = $content_or_attributes;
     139                } else {
     140                        $content = $content_or_attributes;
     141                }
     142                return array( $content, $attributes );
     143        }
     144
     145        private function init_new_dom() {
     146                unset( $this->dom, $this->current_element );
     147                $this->dom = new DOMDocument();
     148                $this->dom->formatOutput = true;
     149                $this->current_element = $this->dom;
     150                $this->last_inserted = null;
     151        }
     152
     153        private function xml_from_dom() {
     154                if ( 0 !== $this->contains_nesting_level ) {
     155                        throw new OxymelException( 'contains and end calls do not match' );
     156                }
     157                $xml = '';
     158                foreach( $this->dom->childNodes as $child ) {
     159                        $xml .= $this->dom->saveXML( $child ) . "\n";
     160                }
     161                return $xml;
     162        }
     163
     164        private function create_element( $name, $content, $attributes ) {
     165                if ( !is_null( $content ) )
     166                        $element = $this->dom->createElement( $name, $content );
     167                else
     168                        $element = $this->dom->createElement( $name );
     169
     170                foreach( $attributes as $attribute_name => $attribute_value ) {
     171                        $element->setAttribute( $attribute_name, $attribute_value );
     172                }
     173
     174                return $element;
     175        }
     176
     177        private function add_opening_tag_from_element( $element ) {
     178                $this->xml .= $this->indent( $this->xml_from_dom(), $this->nesting_level );
     179                $tag = $this->dom->saveXML($element);
     180                $this->xml .= $this->indent( str_replace( '/>', '>', $tag ) . "\n", $this->nesting_level );
     181                $this->nesting_level++;
     182                $this->init_new_dom();
     183        }
     184
     185        private function add_closing_tag_from_tag_name( $name ) {
     186                $this->xml .= $this->xml_from_dom();
     187                $this->nesting_level--;
     188                if ( $this->nesting_level < 0 ) {
     189                        $this->xml = $this->indent( $this->xml, -$this->nesting_level );
     190                        $this->nesting_level = 0;
     191                }
     192                $this->xml .= $this->indent( "</$name>\n", $this->nesting_level );
     193                $this->init_new_dom();
     194        }
     195
     196        private function indent( $string, $level ) {
     197                if ( !$level ) {
     198                        return $string;
     199                }
     200                $lines = explode( "\n", $string );
     201                foreach( $lines as &$line ) {
     202                        if ( !trim( $line ) )
     203                                continue;
     204                        $line = str_repeat( $this->indentation, $level ) . $line;
     205                }
     206                return implode( "\n", $lines );
     207        }
     208}
     209
     210class OxymelException extends Exception {
     211}
  • src/wp-includes/default-constants.php

    diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php
    index 734509a..dc5c117 100644
    a b function wp_initial_constants() { 
    8787        define( 'DAY_IN_SECONDS',    24 * HOUR_IN_SECONDS   );
    8888        define( 'WEEK_IN_SECONDS',    7 * DAY_IN_SECONDS    );
    8989        define( 'YEAR_IN_SECONDS',  365 * DAY_IN_SECONDS    );
     90
     91        // Constants for expressing human-readable data sizes
     92        // in their respective number of bytes.
     93        define( 'KB_IN_BYTES', 1024 );
     94        define( 'MB_IN_BYTES', 1024 * KB_IN_BYTES );
     95        define( 'GB_IN_BYTES', 1024 * MB_IN_BYTES );
     96        define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES );
     97        define( 'PB_IN_BYTES', 1024 * TB_IN_BYTES );
     98        define( 'EB_IN_BYTES', 1024 * PB_IN_BYTES );
     99        define( 'ZB_IN_BYTES', 1024 * EB_IN_BYTES );
     100        define( 'YB_IN_BYTES', 1024 * ZB_IN_BYTES );
    90101}
    91102
    92103/**
  • new file src/wp-includes/export/class-wp-export-query.php

    diff --git a/src/wp-includes/export/class-wp-export-query.php b/src/wp-includes/export/class-wp-export-query.php
    new file mode 100644
    index 0000000..30de3fe
    - +  
     1<?php
     2/**
     3 * Represents a set of posts and other site data to be exported.
     4 *
     5 * An immutable object, which gathers all data needed for the export.
     6 */
     7class WP_Export_Query {
     8        const QUERY_CHUNK = 100;
     9
     10        private static $defaults = array(
     11                'post_ids' => null,
     12                'post_type' => null,
     13                'status' => null,
     14                'author' => null,
     15                'start_date' => null,
     16                'end_date' => null,
     17                'category' => null,
     18        );
     19
     20        private $post_ids;
     21        private $filters;
     22        private $xml_gen;
     23
     24        private $wheres = array();
     25        private $joins = array();
     26
     27        private $author;
     28        private $category;
     29
     30        public $missing_parents = false;
     31
     32        public function __construct( $filters = array() ) {
     33                $this->filters = wp_parse_args( $filters, self::$defaults );
     34                $this->post_ids = $this->calculate_post_ids();
     35        }
     36
     37        public function post_ids() {
     38                return $this->post_ids;
     39        }
     40
     41        public function charset() {
     42                return get_bloginfo( 'charset' );
     43        }
     44
     45        public function site_metadata() {
     46                $metadata = array(
     47                        'name' => $this->bloginfo_rss( 'name' ),
     48                        'url' => $this->bloginfo_rss( 'url' ),
     49                        'language' => $this->bloginfo_rss( 'language' ),
     50                        'description' => $this->bloginfo_rss( 'description' ),
     51                        'pubDate' => date( 'D, d M Y H:i:s +0000' ),
     52                        'site_url' => is_multisite()? network_home_url() : $this->bloginfo_rss( 'url' ),
     53                        'blog_url' => $this->bloginfo_rss( 'url' ),
     54                );
     55                return $metadata;
     56        }
     57
     58        public function wp_generator_tag() {
     59                return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' );
     60        }
     61
     62        public function authors() {
     63                global $wpdb;
     64                $authors = array();
     65                $author_ids = $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" );
     66                foreach ( (array) $author_ids as $author_id ) {
     67                        $authors[] = get_userdata( $author_id );
     68                }
     69                $authors = array_filter( $authors );
     70                return $authors;
     71        }
     72
     73        public function categories() {
     74                if ( $this->category ) {
     75                        return array( $this->category );
     76                }
     77                if ( $this->filters['post_type'] ) {
     78                        return array();
     79                }
     80                $categories = (array) get_categories( array( 'get' => 'all' ) );
     81
     82                $this->check_for_orphaned_terms( $categories );
     83
     84                $categories = self::topologically_sort_terms( $categories );
     85
     86                return $categories;
     87        }
     88
     89        public function tags() {
     90                if ( $this->filters['post_type'] ) {
     91                        return array();
     92                }
     93                $tags = (array) get_tags( array( 'get' => 'all' ) );
     94
     95                $this->check_for_orphaned_terms( $tags );
     96
     97                return $tags;
     98        }
     99
     100        public function custom_taxonomies_terms() {
     101                if ( $this->filters['post_type'] ) {
     102                        return array();
     103                }
     104                $custom_taxonomies = get_taxonomies( array( '_builtin' => false ) );
     105                $custom_terms = (array) get_terms( $custom_taxonomies, array( 'get' => 'all' ) );
     106                $this->check_for_orphaned_terms( $custom_terms );
     107                $custom_terms = self::topologically_sort_terms( $custom_terms );
     108                return $custom_terms;
     109        }
     110
     111        public function nav_menu_terms() {
     112                $nav_menus = wp_get_nav_menus();
     113                foreach( $nav_menus as &$term ) {
     114                        $term->description = '';
     115                }
     116                return $nav_menus;
     117        }
     118
     119        public function exportify_post( $post ) {
     120                $GLOBALS['wp_query']->in_the_loop = true;
     121                $previous_global_post = isset( $GLOBALS['post'] )? $GLOBALS['post'] : null;
     122                $GLOBALS['post'] = $post;
     123                setup_postdata( $post );
     124                $post->post_content = apply_filters( 'the_content_export', $post->post_content );
     125                $post->post_excerpt = apply_filters( 'the_excerpt_export', $post->post_excerpt );
     126                $post->is_sticky = is_sticky( $post->ID ) ? 1 : 0;
     127                $post->terms = self::get_terms_for_post( $post );
     128                $post->meta = self::get_meta_for_post( $post );
     129                $post->comments = self::get_comments_for_post( $post );
     130                $GLOBALS['post'] = $previous_global_post;
     131                return $post;
     132        }
     133
     134        public function posts() {
     135                $posts_iterator = new WP_Post_IDs_Iterator( $this->post_ids, self::QUERY_CHUNK );
     136                return new WP_Map_Iterator( $posts_iterator, array( $this, 'exportify_post' ) );
     137        }
     138
     139        private function calculate_post_ids() {
     140                global $wpdb;
     141                if ( is_array( $this->filters['post_ids'] ) ) {
     142                        return $this->filters['post_ids'];
     143                }
     144                $this->post_type_where();
     145                $this->status_where();
     146                $this->author_where();
     147                $this->start_date_where();
     148                $this->end_date_where();
     149                $this->category_where();
     150
     151                $where = implode( ' AND ', array_filter( $this->wheres ) );
     152                if ( $where ) $where = "WHERE $where";
     153                $join = implode( ' ', array_filter( $this->joins ) );
     154
     155                $post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" );
     156                $post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) );
     157                return $post_ids;
     158        }
     159
     160        private function post_type_where() {
     161                global $wpdb;
     162                $post_types_filters = array( 'can_export' => true );
     163                if ( $this->filters['post_type'] ) {
     164                        $post_types_filters = array_merge( $post_types_filters, array( 'name' => $this->filters['post_type'] ) );
     165                }
     166                $post_types = get_post_types( $post_types_filters );
     167                if ( !$post_types ) {
     168                        $this->wheres[] = 'p.post_type IS NULL';
     169                        return;
     170                }
     171                $this->wheres[] = $wpdb->build_IN_condition( 'p.post_type', $post_types );
     172        }
     173
     174        private function status_where() {
     175                global $wpdb;
     176                if ( !$this->filters['status'] ) {
     177                        $this->wheres[] = "p.post_status != 'auto-draft'";
     178                        return;
     179                }
     180                $this->wheres[] = $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] );
     181        }
     182
     183        private function author_where() {
     184                global $wpdb;
     185                $user = $this->find_user_from_any_object( $this->filters['author'] );
     186                if ( !$user || is_wp_error( $user ) ) {
     187                        return;
     188                }
     189                $this->author = $user;
     190                $this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->ID );
     191        }
     192
     193        private function start_date_where() {
     194                global $wpdb;
     195                $timestamp = strtotime( $this->filters['start_date'] );
     196                if ( !$timestamp ) {
     197                        return;
     198                }
     199                $this->wheres[] = $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) );
     200        }
     201
     202        private function end_date_where() {
     203                global $wpdb;
     204                $timestamp = strtotime( $this->filters['end_date'] );
     205                if ( !$timestamp ) {
     206                        return;
     207                }
     208                $this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) );
     209        }
     210
     211        private function category_where() {
     212                global $wpdb;
     213                if ( 'post' != $this->filters['post_type'] ) {
     214                        return;
     215                }
     216                $category = $this->find_category_from_any_object( $this->filters['category'] );
     217                if ( !$category ) {
     218                        return;
     219                }
     220                $this->category = $category;
     221                $this->joins[] = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)";
     222                $this->wheres[] = $wpdb->prepare( 'tr.term_taxonomy_id = %d', $category->term_taxonomy_id );
     223        }
     224
     225        private function attachments_for_specific_post_types( $post_ids ) {
     226                global $wpdb;
     227                if ( !$this->filters['post_type'] ) {
     228                        return array();
     229                }
     230                $attachment_ids = array();
     231                while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::QUERY_CHUNK ) ) {
     232                        $post_parent_condition = $wpdb->build_IN_condition( 'post_parent', $batch_of_post_ids );
     233                        $attachment_ids = array_merge( $attachment_ids, (array)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND $post_parent_condition" ) );
     234                }
     235                return array_map( 'intval', $attachment_ids );
     236        }
     237
     238        private function bloginfo_rss( $section ) {
     239                return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section );
     240        }
     241
     242        private function find_user_from_any_object( $user ) {
     243                if ( is_numeric( $user ) ) {
     244                        return get_user_by( 'id', $user );
     245                } elseif ( is_string( $user ) ) {
     246                        return get_user_by( 'login', $user );
     247                } elseif ( isset( $user->ID ) ) {
     248                        return get_user_by( 'id', $user->ID );
     249                }
     250                return false;
     251        }
     252
     253        private function find_category_from_any_object( $category ) {
     254                if ( is_numeric( $category ) ) {
     255                        return get_term( $category, 'category' );
     256                } elseif ( is_string( $category ) ) {
     257                        $term = term_exists( $category, 'category' );
     258                        return isset( $term['term_id'] )? get_term( $term['term_id'], 'category' ) : false;
     259                } elseif ( isset( $category->term_id ) ) {
     260                        return get_term( $category->term_id, 'category' );
     261                }
     262                return false;
     263        }
     264
     265        private static function topologically_sort_terms( $terms ) {
     266                $sorted = array();
     267                while ( $term = array_shift( $terms ) ) {
     268                        if ( $term->parent == 0 || isset( $sorted[$term->parent] ) )
     269                                $sorted[$term->term_id] = $term;
     270                        else
     271                                $terms[] = $term;
     272                }
     273                return $sorted;
     274        }
     275
     276        private function check_for_orphaned_terms( $terms ) {
     277                $term_ids = array();
     278                $have_parent = array();
     279
     280                foreach ( $terms as $term ) {
     281                        $term_ids[ $term->term_id ] = true;
     282                        if ( $term->parent != 0 )
     283                                $have_parent[] = $term;
     284                }
     285
     286                foreach ( $have_parent as $has_parent ) {
     287                        if ( ! isset( $term_ids[ $has_parent->parent ] ) ) {
     288                                $this->missing_parents = $has_parent;
     289                                throw new WP_Export_Term_Exception( __( 'Term is missing a parent.' ) );
     290                        }
     291                }
     292        }
     293
     294        private static function get_terms_for_post( $post ) {
     295                $taxonomies = get_object_taxonomies( $post->post_type );
     296                if ( empty( $taxonomies ) )
     297                        return array();
     298                $terms = wp_get_object_terms( $post->ID, $taxonomies );
     299                $terms = $terms? $terms : array();
     300                return $terms;
     301        }
     302
     303        private static function get_meta_for_post( $post ) {
     304                global $wpdb;
     305                $meta_for_export = array();
     306                $meta_from_db = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) );
     307                foreach ( $meta_from_db as $meta ) {
     308                        if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) )
     309                                continue;
     310                        $meta_for_export[] = $meta;
     311                }
     312                return $meta_for_export;
     313        }
     314
     315        private static function get_comments_for_post( $post ) {
     316                global $wpdb;
     317                $comments = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) );
     318                foreach( $comments as $comment ) {
     319                        $meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
     320                        $meta = $meta? $meta : array();
     321                        $comment->meta = $meta;
     322                }
     323                return $comments;
     324        }
     325}
     326
     327class WP_Export_Exception extends RuntimeException {
     328}
     329
     330class WP_Export_Term_Exception extends RuntimeException {
     331}
  • new file src/wp-includes/export/class-wp-export-wxr-formatter.php

    diff --git a/src/wp-includes/export/class-wp-export-wxr-formatter.php b/src/wp-includes/export/class-wp-export-wxr-formatter.php
    new file mode 100644
    index 0000000..073c271
    - +  
     1<?php
     2/**
     3 * Version number for the export format.
     4 *
     5 * Bump this when something changes that might affect compatibility.
     6 *
     7 * @since 2.5.0
     8 */
     9define( 'WXR_VERSION', '1.2' );
     10
     11require_once ABSPATH . WPINC . '/Oxymel.php';
     12
     13class WP_Export_Oxymel extends Oxymel {
     14        public function optional( $tag_name, $contents ) {
     15                if ( $contents ) {
     16                        $this->$tag_name( $contents );
     17                }
     18                return $this;
     19        }
     20
     21        public function optional_cdata( $tag_name, $contents ) {
     22                if ( $contents ) {
     23                        $this->$tag_name->contains->cdata( $contents )->end;
     24                }
     25                return $this;
     26        }
     27}
     28
     29/**
     30 * Responsible for formatting the data in WP_Export_Query to WXR
     31 */
     32class WP_Export_WXR_Formatter {
     33        function __construct( $export ) {
     34                $this->export = $export;
     35                $this->wxr_version = WXR_VERSION;
     36        }
     37
     38        function before_posts() {
     39                $before_posts_xml = '';
     40                $before_posts_xml .= $this->header();
     41                $before_posts_xml .= $this->site_metadata();
     42                $before_posts_xml .= $this->authors();
     43                $before_posts_xml .= $this->categories();
     44                $before_posts_xml .= $this->tags();
     45                $before_posts_xml .= $this->nav_menu_terms();
     46                $before_posts_xml .= $this->custom_taxonomies_terms();
     47                $before_posts_xml .= $this->rss2_head_action();
     48                return $before_posts_xml;
     49        }
     50
     51        function posts() {
     52                return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) );
     53        }
     54
     55        function after_posts() {
     56                return $this->footer();
     57        }
     58
     59        function header() {
     60                $oxymel = new Oxymel;
     61                $charset = $this->export->charset();
     62                $wp_generator_tag = $this->export->wp_generator_tag();
     63                $comment = <<<COMMENT
     64
     65 This is a WordPress eXtended RSS file generated by WordPress as an export of your site.
     66 It contains information about your site's posts, pages, comments, categories, and other content.
     67 You may use this file to transfer that content from one site to another.
     68 This file is not intended to serve as a complete backup of your site.
     69
     70 To import this information into a WordPress site follow these steps:
     71 1. Log in to that site as an administrator.
     72 2. Go to Tools: Import in the WordPress admin panel.
     73 3. Install the "WordPress" importer from the list.
     74 4. Activate & Run Importer.
     75 5. Upload this file using the form provided on that page.
     76 6. You will first be asked to map the authors in this export file to users
     77    on the site. For each author, you may choose to map to an
     78    existing user on the site or to create a new user.
     79 7. WordPress will then import each of the posts, pages, comments, categories, etc.
     80    contained in this file into your site.
     81
     82COMMENT;
     83                return $oxymel
     84                        ->xml
     85                        ->comment( $comment )
     86                        ->raw( $wp_generator_tag )
     87                        ->open_rss( array(
     88                                'version' => '2.0',
     89                                'xmlns:excerpt' => "http://wordpress.org/export/{$this->wxr_version}/excerpt/",
     90                                'xmlns:content' => "http://purl.org/rss/1.0/modules/content/",
     91                                'xmlns:wfw' => "http://wellformedweb.org/CommentAPI/",
     92                                'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
     93                                'xmlns:wp' => "http://wordpress.org/export/{$this->wxr_version}/",
     94                        ) )
     95                                ->open_channel
     96                                ->to_string();
     97
     98        }
     99
     100        function site_metadata() {
     101                $oxymel = new Oxymel;
     102                $metadata = $this->export->site_metadata();
     103                return $oxymel
     104                        ->title( $metadata['name'] )
     105                        ->link( $metadata['url'] )
     106                        ->description( $metadata['description'] )
     107                        ->pubDate( $metadata['pubDate'] )
     108                        ->language( $metadata['language'] )
     109                        ->tag( 'wp:wxr_version', $this->wxr_version )
     110                        ->tag( 'wp:base_site_url', $metadata['site_url'] )
     111                        ->tag( 'wp:base_blog_url', $metadata['blog_url'] )
     112                        ->to_string();
     113        }
     114
     115        function authors() {
     116                $oxymel = new Oxymel;
     117                $authors = $this->export->authors();
     118                foreach ( $authors as $author ) {
     119                        $oxymel
     120                                ->tag( 'wp:author' )->contains
     121                                        ->tag( 'wp:author_login', $author->user_login )
     122                                        ->tag( 'wp:author_email', $author->user_email )
     123                                        ->tag( 'wp:author_display_name' )->contains->cdata( $author->display_name )->end
     124                                        ->tag( 'wp:author_first_name' )->contains->cdata( $author->user_first_name )->end
     125                                        ->tag( 'wp:author_last_name' )->contains->cdata( $author->user_last_name )->end
     126                                        ->end;
     127                }
     128                return $oxymel->to_string();
     129        }
     130
     131        function categories() {
     132                $oxymel = new WP_Export_Oxymel;
     133                $categories = $this->export->categories();
     134                foreach( $categories as $term_id => $category ) {
     135                        $category->parent_slug = $category->parent? $categories[$category->parent]->slug : '';
     136                        $oxymel->tag( 'wp:category' )->contains
     137                                ->tag( 'wp:term_id', $category->term_id )
     138                                ->tag( 'wp:category_nicename', $category->slug )
     139                                ->tag( 'wp:category_parent', $category->parent_slug )
     140                                ->optional_cdata( 'wp:cat_name', $category->name )
     141                                ->optional_cdata( 'wp:category_description', $category->description )
     142                                ->end;
     143                }
     144                return $oxymel->to_string();
     145        }
     146
     147        function tags() {
     148                $oxymel = new WP_Export_Oxymel;
     149                $tags = $this->export->tags();
     150                foreach( $tags as $tag ) {
     151                        $oxymel->tag( 'wp:tag' )->contains
     152                                ->tag( 'wp:term_id', $tag->term_id )
     153                                ->tag( 'wp:tag_slug', $tag->slug )
     154                                ->optional_cdata( 'wp:tag_name', $tag->name )
     155                                ->optional_cdata( 'wp:tag_description', $tag->description )
     156                                ->end;
     157                }
     158                return $oxymel->to_string();
     159        }
     160
     161        function nav_menu_terms() {
     162                return $this->terms( $this->export->nav_menu_terms() );
     163        }
     164
     165        function custom_taxonomies_terms() {
     166                return $this->terms( $this->export->custom_taxonomies_terms() );
     167        }
     168
     169        function rss2_head_action() {
     170                ob_start();
     171                do_action( 'rss2_head' );
     172                $action_output = ob_get_clean();
     173                return $action_output;
     174        }
     175
     176        private function terms( $terms ) {
     177                $oxymel = new WP_Export_Oxymel;
     178                foreach( $terms as $term ) {
     179                        $term->parent_slug = $term->parent? $terms[$term->parent]->slug : '';
     180                        $oxymel->tag( 'wp:term' )->contains
     181                                ->tag( 'wp:term_id', $term->term_id )
     182                                ->tag( 'wp:term_taxonomy', $term->taxonomy )
     183                                ->tag( 'wp:term_slug', $term->slug );
     184                        if ( 'nav_menu' != $term->taxonomy ) {
     185                                $oxymel
     186                                ->tag( 'wp:term_parent', $term->parent_slug );
     187                        }
     188                                $oxymel
     189                                ->optional_cdata( 'wp:term_name', $term->name )
     190                                ->optional_cdata( 'wp:term_description', $term->description )
     191                                ->end;
     192                }
     193                return $oxymel->to_string();
     194        }
     195
     196        function post( $post ) {
     197                $oxymel = new WP_Export_Oxymel;
     198                $GLOBALS['wp_query']->in_the_loop = true;
     199                $GLOBALS['post'] = $post;
     200                setup_postdata( $post );
     201
     202                $oxymel->item->contains
     203                        ->title( apply_filters( 'the_title_rss', $post->post_title ) )
     204                        ->link( apply_filters('the_permalink_rss', get_permalink() ) )
     205                        ->pubDate( mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) )
     206                        ->tag( 'dc:creator', get_the_author_meta( 'login' ) )
     207                        ->guid( get_the_guid(), array( 'isPermaLink' => 'false' ) )
     208                        ->description( '' )
     209                        ->tag( 'content:encoded' )->contains->cdata( $post->post_content )->end
     210                        ->tag( 'excerpt:encoded' )->contains->cdata( $post->post_excerpt )->end
     211                        ->tag( 'wp:post_id', $post->ID )
     212                        ->tag( 'wp:post_date', $post->post_date )
     213                        ->tag( 'wp:post_date_gmt', $post->post_date_gmt )
     214                        ->tag( 'wp:comment_status', $post->comment_status )
     215                        ->tag( 'wp:ping_status', $post->ping_status )
     216                        ->tag( 'wp:post_name', $post->post_name )
     217                        ->tag( 'wp:status', $post->post_status )
     218                        ->tag( 'wp:post_parent', $post->post_parent )
     219                        ->tag( 'wp:menu_order', $post->menu_order )
     220                        ->tag( 'wp:post_type', $post->post_type )
     221                        ->tag( 'wp:post_password', $post->post_password )
     222                        ->tag( 'wp:is_sticky', $post->is_sticky )
     223                        ->optional( 'wp:attachment_url', wp_get_attachment_url( $post->ID ) );
     224                foreach( $post->terms as $term ) {
     225                        $oxymel
     226                        ->category( array( 'domain' => $term->taxonomy, 'nicename' => $term->slug  ) )->contains->cdata( $term->name )->end;
     227                }
     228                foreach( $post->meta as $meta ) {
     229                        $oxymel
     230                        ->tag( 'wp:postmeta' )->contains
     231                                ->tag( 'wp:meta_key', $meta->meta_key )
     232                                ->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end
     233                                ->end;
     234                }
     235                foreach( $post->comments as $comment ) {
     236                        $oxymel
     237                        ->tag( 'wp:comment' )->contains
     238                                ->tag( 'wp:comment_id', $comment->comment_ID )
     239                                ->tag( 'wp:comment_author' )->contains->cdata( $comment->comment_author )->end
     240                                ->tag( 'wp:comment_author_email', $comment->comment_author_email )
     241                                ->tag( 'wp:comment_author_url', $comment->comment_author_url )
     242                                ->tag( 'wp:comment_author_IP', $comment->comment_author_IP )
     243                                ->tag( 'wp:comment_date', $comment->comment_date )
     244                                ->tag( 'wp:comment_date_gmt', $comment->comment_date_gmt )
     245                                ->tag( 'wp:comment_content' )->contains->cdata( $comment->comment_content )->end
     246                                ->tag( 'wp:comment_approved', $comment->comment_approved )
     247                                ->tag( 'wp:comment_type', $comment->comment_type )
     248                                ->tag( 'wp:comment_parent', $comment->comment_parent )
     249                                ->tag( 'wp:comment_user_id', $comment->user_id )
     250                                ->oxymel( $this->comment_meta( $comment ) )
     251                                ->end;
     252                }
     253                $oxymel
     254                        ->end;
     255                return $oxymel->to_string();
     256        }
     257
     258        private function comment_meta( $comment ) {
     259                global $wpdb;
     260                $metas = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
     261                if ( !$metas ) {
     262                        return new Oxymel;
     263                }
     264                $oxymel = new WP_Export_Oxymel;
     265                foreach( $metas as $meta ) {
     266                        $oxymel->tag( 'wp:commentmeta' )->contains
     267                                ->tag( 'wp:meta_key', $meta->meta_key )
     268                                ->tag( 'wp:meta_value', $meta->meta_value )
     269                        ->end;
     270                }
     271                return $oxymel;
     272        }
     273
     274
     275        function footer() {
     276                $oxymel = new Oxymel;
     277                return $oxymel->close_channel->close_rss->to_string();
     278        }
     279}
  • new file src/wp-includes/export/functions.export.php

    diff --git a/src/wp-includes/export/functions.export.php b/src/wp-includes/export/functions.export.php
    new file mode 100644
    index 0000000..dd3b177
    - +  
     1<?php
     2
     3function wp_export( $args = array() ) {
     4        $defaults = array(
     5                'filters' => array(),
     6                'format' => 'WP_Export_WXR_Formatter',
     7                'writer' => 'WP_Export_Returner',
     8                'writer_args' => null,
     9        );
     10        $args = wp_parse_args( $args, $defaults );
     11        $export_query = new WP_Export_Query( $args['filters'] );
     12        $formatter = new $args['format']( $export_query );
     13        $writer = new $args['writer']( $formatter, $args['writer_args'] );
     14        try {
     15                return $writer->export();
     16        } catch ( WP_Export_Exception $e ) {
     17                return new WP_Error( 'wp-export-error', $e->getMessage() );
     18        }
     19}
  • new file src/wp-includes/export/writers.php

    diff --git a/src/wp-includes/export/writers.php b/src/wp-includes/export/writers.php
    new file mode 100644
    index 0000000..e0d1594
    - +  
     1<?php
     2abstract class WP_Export_Base_Writer {
     3        protected $formatter;
     4
     5        function __construct( $formatter ) {
     6                $this->formatter = $formatter;
     7        }
     8
     9        public function export() {
     10                $this->write( $this->formatter->before_posts() );
     11                foreach( $this->formatter->posts() as $post_in_wxr ) {
     12                        $this->write( $post_in_wxr );
     13                }
     14                $this->write( $this->formatter->after_posts() );
     15        }
     16
     17        abstract protected function write( $xml );
     18}
     19
     20class WP_Export_XML_Over_HTTP extends WP_Export_Base_Writer {
     21        private $file_name;
     22
     23        function __construct( $formatter, $file_name ) {
     24                parent::__construct( $formatter );
     25                $this->file_name = $file_name;
     26        }
     27
     28        public function export() {
     29                try {
     30                        $export = $this->get_export();
     31                        $this->send_headers();
     32                        echo $export;
     33                } catch ( WP_Export_Exception $e ) {
     34                        $message = apply_filters( 'export_error_message', $e->getMessage() );
     35                        wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) );
     36                } catch ( WP_Export_Term_Exception $e ) {
     37                        do_action( 'export_term_orphaned', $this->formatter->export->missing_parents );
     38                        $message = apply_filters( 'export_term_error_message', $e->getMessage() );
     39                        wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) );
     40                }
     41        }
     42
     43        protected function write( $xml ) {
     44                $this->result .= $xml;
     45        }
     46
     47        protected function get_export() {
     48                $this->result = '';
     49                parent::export();
     50                return $this->result;
     51        }
     52
     53        protected function send_headers() {
     54                header( 'Content-Description: File Transfer' );
     55                header( 'Content-Disposition: attachment; filename=' . $this->file_name );
     56                header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
     57        }
     58}
     59
     60class WP_Export_Returner extends WP_Export_Base_Writer {
     61        private $result = '';
     62
     63        public function export() {
     64                $this->private = '';
     65                try {
     66                        parent::export();
     67                } catch ( WP_Export_Exception $e ) {
     68                        $message = apply_filters( 'export_error_message', $e->getMessage() );
     69                        return new WP_Error( 'wp-export-error', $message );
     70                       
     71                } catch ( WP_Export_Term_Exception $e ) {
     72                        do_action( 'export_term_orphaned', $this->formatter->export->missing_parents );
     73                        $message = apply_filters( 'export_term_error_message', $e->getMessage() );
     74                        return new WP_Error( 'wp-export-error', $message );
     75                }
     76                return $this->result;
     77        }
     78        protected function write( $xml ) {
     79                $this->result .= $xml;
     80        }
     81}
     82
     83class WP_Export_File_Writer extends WP_Export_Base_Writer {
     84        private $f;
     85        private $file_name;
     86
     87        public function __construct( $formatter, $file_name ) {
     88                parent::__construct( $formatter );
     89                $this->file_name = $file_name;
     90        }
     91
     92        public function export() {
     93                $this->f = fopen( $this->file_name, 'w' );
     94                if ( !$this->f ) {
     95                        throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $this->file_name ) );
     96                }
     97
     98                try {
     99                        parent::export();
     100                } catch ( WP_Export_Exception $e ) {
     101                        throw $e;
     102                } catch ( WP_Export_Term_Exception $e ) {
     103                        throw $e;
     104                }
     105
     106                fclose( $this->f );
     107        }
     108
     109        protected function write( $xml ) {
     110                $res = fwrite( $this->f, $xml);
     111                if ( false === $res ) {
     112                        throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) );
     113                }
     114        }
     115}
     116
     117class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer {
     118        private $result = '';
     119        private $f;
     120        private $next_file_number = 0;
     121        private $current_file_size = 0;
     122
     123        function __construct( $formatter, $writer_args = array() ) {
     124                parent::__construct( $formatter );
     125                //TODO: check if args are not missing
     126                $this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $max_file_size;
     127                $this->destination_directory = $writer_args['destination_directory'];
     128                $this->filename_template = $writer_args['filename_template'];
     129                $this->before_posts_xml = $this->formatter->before_posts();
     130                $this->after_posts_xml = $this->formatter->after_posts();
     131        }
     132
     133        public function export() {
     134                $this->start_new_file();
     135                foreach( $this->formatter->posts() as $post_xml ) {
     136                        if ( $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) {
     137                                $this->start_new_file();
     138                        }
     139                        $this->write( $post_xml );
     140                }
     141                $this->close_current_file();
     142        }
     143
     144        protected function write( $xml ) {
     145                $res = fwrite( $this->f, $xml);
     146                if ( false === $res ) {
     147                        throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) );
     148                }
     149                $this->current_file_size += strlen( $xml );
     150        }
     151
     152        private function start_new_file() {
     153                if ( $this->f ) {
     154                        $this->close_current_file();
     155                }
     156                $file_path = $this->next_file_path();
     157                $this->f = fopen( $file_path, 'w' );
     158                if ( !$this->f ) {
     159                        throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $file_path ) );
     160                }
     161                $this->current_file_size = 0;
     162                $this->write( $this->before_posts_xml );
     163        }
     164
     165        private function close_current_file() {
     166                if ( !$this->f ) {
     167                        return;
     168                }
     169                $this->write( $this->after_posts_xml );
     170                fclose( $this->f );
     171        }
     172
     173        private function next_file_name() {
     174                $next_file_name = sprintf( $this->filename_template, $this->next_file_number );
     175                $this->next_file_number++;
     176                return $next_file_name;
     177        }
     178
     179        private function next_file_path() {
     180                return untrailingslashit( $this->destination_directory ) . DIRECTORY_SEPARATOR . $this->next_file_name();
     181        }
     182
     183}
  • new file src/wp-includes/iterators.php

    diff --git a/src/wp-includes/iterators.php b/src/wp-includes/iterators.php
    new file mode 100644
    index 0000000..6a4613c
    - +  
     1<?php
     2class WP_Map_Iterator extends IteratorIterator {
     3        function __construct( $iterator, $callback ) {
     4                $this->callback = $callback;
     5                parent::__construct( $iterator );
     6        }
     7
     8        function current() {
     9                $original_current = parent::current();
     10                return call_user_func( $this->callback, $original_current );
     11        }
     12}
     13
     14class WP_Post_IDs_Iterator implements Iterator {
     15        private $limit = 100;
     16        private $post_ids;
     17        private $ids_left;
     18        private $results = array();
     19
     20        public function __construct( $post_ids, $limit = null ) {
     21                $this->db = $GLOBALS['wpdb'];
     22                $this->post_ids = $post_ids;
     23                $this->ids_left = $post_ids;
     24                if ( !is_null( $limit ) ) {
     25                        $this->limit = $limit;
     26                }
     27        }
     28
     29        public function current() {
     30                return $this->results[$this->index_in_results];
     31        }
     32
     33        public function key() {
     34                return $this->global_index;
     35        }
     36
     37        public function next() {
     38                $this->index_in_results++;
     39                $this->global_index++;
     40        }
     41
     42        public function rewind() {
     43                $this->results = array();
     44                $this->global_index = 0;
     45                $this->index_in_results = 0;
     46                $this->ids_left = $this->post_ids;
     47        }
     48
     49        public function valid() {
     50                if ( isset( $this->results[$this->index_in_results] ) ) {
     51                        return true;
     52                }
     53                if ( empty( $this->ids_left ) ) {
     54                        return false;
     55                }
     56                $has_posts = $this->load_next_posts_from_db();
     57                if ( !$has_posts ) {
     58                        return false;
     59                }
     60                $this->index_in_results = 0;
     61                return true;
     62        }
     63
     64        private function load_next_posts_from_db() {
     65                $next_batch_post_ids = array_splice( $this->ids_left, 0, $this->limit );
     66                $in_post_ids_sql = $this->db->build_IN_condition( 'ID', $next_batch_post_ids );
     67                $this->results = $this->db->get_results( "SELECT * FROM {$this->db->posts} WHERE $in_post_ids_sql" );
     68                if ( !$this->results ) {
     69                        if ( $this->db->last_error ) {
     70                                throw new WP_Iterator_Exception( 'Database error: ' . $this->db->last_error );
     71                        } else {
     72                                return false;
     73                        }
     74                }
     75                return true;
     76        }
     77}
     78
     79class WP_Iterator_Exception extends Exception {
     80}
  • src/wp-includes/wp-db.php

    diff --git a/src/wp-includes/wp-db.php b/src/wp-includes/wp-db.php
    index 5393240..492a222 100644
    a b class wpdb { 
    28432843                }
    28442844                return preg_replace( '/[^0-9.].*/', '', $server_info );
    28452845        }
     2846
     2847        /**
     2848         * Builds a SQL condition in the form "post_id IN (1, 2, 3, 4)"
     2849         *
     2850         * @since 3.6.0
     2851         *
     2852         * @param string $column_name The name of the table column from the IN condition
     2853         * @param array $values Array of values in which the column value should be
     2854         * @param string $format Optional printf format specifier for the elements of the array. Defaults to %s.
     2855         * @return string The IN condition, with escaped values. If there are no values, the return value is an empty string.
     2856         */
     2857        function build_IN_condition( $column_name, $values, $format = '%s' ) {
     2858                if ( !is_array( $values ) || empty( $values ) ) {
     2859                        return '';
     2860                }
     2861                $formats = implode( ', ', array_fill( 0, count( $values ), $format ) );
     2862                return $this->prepare( "$column_name IN ($formats)", $values );
     2863        }
    28462864}
  • src/wp-settings.php

    diff --git a/src/wp-settings.php b/src/wp-settings.php
    index 715a2c2..97c5a94 100644
    a b require( ABSPATH . WPINC . '/category-template.php' ); 
    134134require( ABSPATH . WPINC . '/comment.php' );
    135135require( ABSPATH . WPINC . '/comment-template.php' );
    136136require( ABSPATH . WPINC . '/rewrite.php' );
     137require( ABSPATH . WPINC . '/iterators.php' );
     138require( ABSPATH . WPINC . '/export/class-wp-export-query.php' );
     139require( ABSPATH . WPINC . '/export/class-wp-export-wxr-formatter.php' );
     140require( ABSPATH . WPINC . '/export/writers.php' );
     141require( ABSPATH . WPINC . '/export/functions.export.php' );
    137142require( ABSPATH . WPINC . '/feed.php' );
    138143require( ABSPATH . WPINC . '/bookmark.php' );
    139144require( ABSPATH . WPINC . '/bookmark-template.php' );
  • new file tests/phpunit/tests/export/class-wp-export-query.php

    diff --git a/tests/phpunit/tests/export/class-wp-export-query.php b/tests/phpunit/tests/export/class-wp-export-query.php
    new file mode 100644
    index 0000000..5be2567
    - +  
     1<?php
     2
     3/**
     4 * Test WP_Export_Query class
     5 *
     6 * @group export
     7 * @ticket 22435
     8 */
     9class Test_WP_Export_Query extends WP_UnitTestCase {
     10        function setUp() {
     11                if ( ! class_exists( 'WP_Export_Query' ) ) {
     12                        $this->markTestSkipped( "WP_Export_Query class doesn't exist" );
     13                }
     14
     15                parent::setUp();
     16        }
     17
     18        function test_WP_Export_Query_should_be_initialized_with_an_array() {
     19                $export = new WP_Export_Query( array( 'author' => 'all' ) );
     20                $this->assertTrue( (bool) $export );
     21        }
     22
     23        function test_WP_Export_Query_should_use_post_ids_if_passed() {
     24                $export = new WP_Export_Query( array( 'post_ids' => array( 1, 2, 3 ) ) );
     25                $this->assertEquals( array( 1, 2, 3 ), $export->post_ids() );
     26        }
     27
     28        function test_WP_Export_Query_should_filter_all_posts_if_all_arg_is_true() {
     29                $post_id = $this->factory->post->create();
     30                $export = new WP_Export_Query();
     31                $this->assertEquals( array( $post_id ), $export->post_ids() );
     32        }
     33
     34        function test_WP_Export_Query_should_filter_all_posts_if_no_args_passed() {
     35                $post_id = $this->factory->post->create();
     36                $export = new WP_Export_Query();
     37                $this->assertEquals( array( $post_id ), $export->post_ids() );
     38        }
     39
     40        function test_WP_Export_Query_should_not_export_anything_if_post_type_arg_is_set_to_non_existing_post_type() {
     41                $post_id = $this->factory->post->create();
     42                $export = new WP_Export_Query( array( 'post_type' => 'baba' ) );
     43                $this->assertEquals( array(), $export->post_ids() );
     44        }
     45
     46        function test_WP_Export_Query_should_filter_only_posts_with_a_certain_post_type_if_the_post_type_arg_is_set() {
     47                register_post_type( 'baba' );
     48                $post_id = $this->factory->post->create( array( 'post_type' => 'baba' ) );
     49                $_       = $this->factory->post->create( array( 'post_type' => 'dyado' ) );
     50                $export = new WP_Export_Query( array( 'post_type' => 'baba' ) );
     51                $this->assertEquals( array( $post_id ), $export->post_ids() );
     52                _unregister_post_type( 'baba' );
     53        }
     54
     55        function test_WP_Export_Query_should_not_export_post_types_with_can_export_set_to_false() {
     56                register_post_type( 'non-exportable', array( 'can_export' => false ) );
     57                register_post_type( 'exportable', array( 'can_export' => true ) );
     58                $non_exportable_post_id = $this->factory->post->create( array( 'post_type' => 'non-exportable' ) );
     59                $exportable_post_id = $this->factory->post->create( array( 'post_type' => 'exportable' ) );
     60                $export = new WP_Export_Query();
     61                $this->assertEquals( array( $exportable_post_id ), $export->post_ids() );
     62                _unregister_post_type( 'non-exportable' );
     63                _unregister_post_type( 'exportable' );
     64        }
     65
     66        function test_WP_Export_Query_should_not_export_auto_drafts_by_default() {
     67                $post_id = $this->factory->post->create( array( 'post_status' => 'auto-draft' ) );
     68                $export = new WP_Export_Query();
     69                $this->assertEquals( array(), $export->post_ids() );
     70        }
     71
     72        function test_WP_Export_Query_should_filter_only_posts_with_certain_status_if_status_arg_is_set() {
     73                $post_id_baba = $this->factory->post->create( array( 'post_status' => 'baba' ) );
     74                $post_id_dudu = $this->factory->post->create( array( 'post_status' => 'dudu' ) );
     75                $export = new WP_Export_Query( array( 'status' => 'baba' ) );
     76                $this->assertEquals( array( $post_id_baba ), $export->post_ids() );
     77        }
     78
     79        function test_WP_Export_Query_should_filter_only_posts_with_certain_author_id_if_status_arg_is_a_number() {
     80                $user_id = $this->factory->user->create();
     81                $post_by_user = $this->factory->post->create( array( 'post_author' => $user_id ) );
     82                $other_post = $this->factory->post->create( array( 'post_author' => $user_id + 1 ) );
     83                $export = new WP_Export_Query( array( 'author' => $user_id ) );
     84                $this->assertEquals( array( $post_by_user ), $export->post_ids() );
     85        }
     86
     87        function test_WP_Export_Query_should_filter_only_posts_with_certain_author_name_if_status_arg_is_a_username() {
     88                $user = $this->factory->user->create_and_get( array( 'user_login' => 'baba' ) );
     89                $post_by_user = $this->factory->post->create( array( 'post_author' => $user->ID ) );
     90                $other_post = $this->factory->post->create( array( 'post_author' => $user->ID + 1 ) );
     91                $export = new WP_Export_Query( array( 'author' => 'baba' ) );
     92                $this->assertEquals( array( $post_by_user ), $export->post_ids() );
     93        }
     94
     95        function test_WP_Export_Query_should_filter_only_posts_with_certain_author_object_if_author_is_an_object_with_ID_member_variable() {
     96                $user = $this->factory->user->create_and_get();
     97                $post_by_user = $this->factory->post->create( array( 'post_author' => $user->ID ) );
     98                $other_post = $this->factory->post->create( array( 'post_author' => $user->ID + 1 ) );
     99                $export = new WP_Export_Query( array( 'author' => $user ) );
     100                $this->assertEquals( array( $post_by_user ), $export->post_ids() );
     101        }
     102
     103        function test_WP_Export_Query_should_filter_only_posts_after_certain_start_date_if_start_date_arg_is_passed() {
     104                $post_before = $this->factory->post->create( array( 'post_date' => '2012-11-10 23:59:59' ) );
     105                $post_after = $this->factory->post->create( array( 'post_date' => '2012-11-11 00:00:00' ) );
     106                $export = new WP_Export_Query( array( 'start_date' => '2012-11-11' ) );
     107                $this->assertEquals( array( $post_after ), $export->post_ids() );
     108        }
     109
     110        function test_WP_Export_Query_should_filter_only_posts_after_certain_end_date_if_end_date_arg_is_passed() {
     111                $post_before = $this->factory->post->create( array( 'post_date' => '2012-11-10 23:59:59' ) );
     112                $post_after = $this->factory->post->create( array( 'post_date' => '2012-11-11 00:00:00' ) );
     113                $export = new WP_Export_Query( array( 'end_date' => '2012-11-10' ) );
     114                $this->assertEquals( array( $post_before ), $export->post_ids() );
     115        }
     116
     117        function test_WP_Export_Query_should_filter_only_posts_with_certain_category_if_category_arg_is_passed() {
     118                $category_id = $this->factory->category->create( array( 'name' => 'baba' ) );
     119                $post_with_category = $this->factory->post->create( array( 'post_category' => array( $category_id ) ) );
     120                $post_without = $this->factory->post->create();
     121                $export = new WP_Export_Query( array( 'post_type' => 'post', 'category' => 'baba' ) );
     122                $this->assertEquals( array( $post_with_category ), $export->post_ids() );
     123        }
     124
     125        function test_WP_Export_Query_should_filter_only_posts_with_certain_category_id_if_category_arg_is_passed() {
     126                $category_id = $this->factory->category->create( array( 'name' => 'baba' ) );
     127                $post_with_category = $this->factory->post->create( array( 'post_category' => array( $category_id ) ) );
     128                $post_without = $this->factory->post->create();
     129                $export = new WP_Export_Query( array( 'post_type' => 'post', 'category' => $category_id ) );
     130                $this->assertEquals( array( $post_with_category ), $export->post_ids() );
     131        }
     132
     133        function test_WP_Export_Query_should_filter_posts_by_category_only_for_post_post_type() {
     134                $category_id = $this->factory->category->create( array( 'name' => 'baba' ) );
     135                $post_with_category = $this->factory->post->create( array( 'post_category' => array( $category_id ) ) );
     136                $post_without = $this->factory->post->create();
     137                $different_post_type = $this->factory->post->create( array( 'post_type' => 'page' ) );
     138                $export = new WP_Export_Query( array( 'category' => $category_id ) );
     139                $this->assertEqualSets( array( $post_with_category, $post_without, $different_post_type ), $export->post_ids() );
     140        }
     141
     142        function test_WP_Export_Query_should_include_attachments_of_posts_if_we_are_filtering_only_some_post_types() {
     143                register_post_type( 'baba' );
     144                $post_id = $this->factory->post->create( array( 'post_type' => 'baba' ) );
     145                $attachment_post_id = $this->factory->post->create( array( 'post_type' => 'attachment', 'post_parent' => $post_id ) );
     146                $export = new WP_Export_Query( array( 'post_type' => 'baba' ) );
     147                $this->assertEquals( array( $post_id, $attachment_post_id ), $export->post_ids() );
     148                _unregister_post_type( 'baba' );
     149        }
     150
     151        function test_authors_should_return_list_of_users_for_each_post_author() {
     152                $user_id = $this->factory->user->create();
     153                $this->factory->post->create( array( 'post_author' => $user_id ) );
     154                $export = new WP_Export_Query();
     155                $authors = $export->authors();
     156                $this->assertEquals( 1, count( $authors ) );
     157                $this->assertEquals( $user_id, $authors[0]->ID );
     158        }
     159
     160        function test_authors_should_skip_non_existing_authors() {
     161                $this->factory->post->create( array( 'post_author' => 11 ) );
     162                $export = new WP_Export_Query();
     163                $this->assertEquals( array(), $export->authors() );
     164        }
     165
     166        function test_authors_should_skip_auto_draft_authors() {
     167                $user_id = $this->factory->user->create();
     168                $this->factory->post->create( array( 'post_author' => $user_id, 'post_status' => 'auto-draft' ) );
     169                $export = new WP_Export_Query();
     170                $this->assertEquals( array(), $export->authors() );
     171        }
     172
     173        function test_categories_should_return_only_the_category_we_are_filtering_on() {
     174                $category_id = $this->factory->category->create( array( 'name' => 'baba' ) );
     175                $other_category_id = $this->factory->category->create( array( 'name' => 'dyado' ) );
     176                $export = new WP_Export_Query( array( 'post_type' => 'post', 'category' => $category_id ) );
     177                $categories = $export->categories();
     178                $this->assertTrue( is_array( $categories ) );
     179                $this->assertEquals( 1, count( $categories ) );
     180        }
     181
     182        function test_categories_should_return_no_categories_if_we_are_requesting_only_one_post_type() {
     183                $category_id = $this->factory->category->create();
     184                $export = new WP_Export_Query( array( 'post_type' => 'post' ) );
     185                $this->assertEquals( array(), $export->categories() );
     186        }
     187
     188        function test_categories_should_return_all_categories_if_we_are_requesting_all_post_types() {
     189                $category_id = $this->factory->category->create();
     190                $another_category_id = $this->factory->category->create();
     191                $export = new WP_Export_Query();
     192                $this->assertEqualSets( array( 1, $category_id, $another_category_id ), self::get_term_ids( $export->categories() ) );
     193        }
     194
     195        function test_categories_should_not_return_a_child_before_its_parent_category() {
     196                $child_category_id = $this->factory->category->create();
     197                $top_category_id = $this->factory->category->create();
     198                wp_update_term( $child_category_id, 'category', array( 'parent' => $top_category_id ) );
     199                $export = new WP_Export_Query();
     200                $this->assertNoChildBeforeParent( $export->categories() );
     201        }
     202
     203        function test_tags_should_return_all_tags() {
     204                $tag_id = $this->factory->tag->create();
     205                $export = new WP_Export_Query();
     206                $this->assertEquals( array( $tag_id ), self::get_term_ids( $export->tags() ) );
     207        }
     208
     209        function test_tags_should_return_no_tags_if_we_are_requesting_only_one_post_type() {
     210                $category_id = $this->factory->tag->create();
     211                $export = new WP_Export_Query( array( 'post_type' => 'post' ) );
     212                $this->assertEquals( array(), $export->tags() );
     213        }
     214
     215        function test_custom_taxonomies_terms_should_return_all_terms() {
     216                register_taxonomy( 'taxonomy_all', 'post' );
     217                $term_id = $this->factory->term->create( array( 'taxonomy' => 'taxonomy_all' ) );
     218                $export = new WP_Export_Query();
     219                $this->assertEquals( array( $term_id ), self::get_term_ids( $export->custom_taxonomies_terms() ) );
     220                _unregister_taxonomy( 'taxonomy_all' );
     221        }
     222
     223        function test_custom_taxonomes_terms_should_return_no_terms_if_we_are_requesting_only_one_post_type() {
     224                register_taxonomy( 'taxonomy_one_post_type', 'post' );
     225                $term_id = $this->factory->term->create( array( 'taxonomy' => 'taxonomy_one_post_type' ) );
     226                $export = new WP_Export_Query( array( 'post_type' => 'post' ) );
     227                $this->assertEquals( array(), $export->custom_taxonomies_terms() );
     228                _unregister_taxonomy( 'taxonomy_one_post_type' );
     229        }
     230
     231        function test_custom_taxonomies_terms_should_not_return_a_child_before_its_parent_term() {
     232                register_taxonomy( 'heir', 'post', array( 'hierarchical' => true ) );
     233                $child_term_id = $this->factory->term->create( array( 'taxonomy' => 'heir' ) );
     234                $top_term_id = $this->factory->term->create( array( 'taxonomy' => 'heir' ) );
     235                wp_update_term( $child_term_id, 'heir', array( 'parent' => $top_term_id ) );
     236                $export = new WP_Export_Query();
     237                $this->assertNoChildBeforeParent( $export->custom_taxonomies_terms() );
     238                _unregister_taxonomy( 'heir' );
     239        }
     240
     241        private function assertNoChildBeforeParent( $terms ) {
     242                $visited = array();
     243                foreach( $terms as $term ) {
     244                        $this->assertTrue( isset( $visited[$term->parent] ) || !$term->parent );
     245                        $visited[$term->term_id] = true;
     246                }
     247        }
     248
     249        private static function get_term_ids( $terms ) {
     250                return array_values( array_map( array( __CLASS__, '_get_term_ids_cb' ), $terms ) );
     251        }
     252
     253        private static function _get_term_ids_cb( $c ) {
     254                return intval( $c->term_id );
     255        }
     256
     257}
     258
  • new file tests/phpunit/tests/export/functions.export.php

    diff --git a/tests/phpunit/tests/export/functions.export.php b/tests/phpunit/tests/export/functions.export.php
    new file mode 100644
    index 0000000..e3b77e7
    - +  
     1<?php
     2
     3/**
     4 * Test export functions
     5 *
     6 * @group export
     7 * @ticket 22435
     8 */
     9class Test_WP_Export_Functions extends WP_UnitTestCase {
     10        function setUp() {
     11                if ( ! function_exists( 'wp_export' ) ) {
     12                        $this->markTestSkipped( "wp_export function doesn't exist" );
     13                }
     14
     15                parent::setUp();
     16        }
     17       
     18        function test_wp_export_returns_wp_error_if_the_writer_throws_Export_exception() {
     19                $this->assertTrue( is_wp_error( wp_export( array( 'writer' => 'Test_WP_Export_Stub_Writer_Throws_Export_Exception' ) ) ) );
     20        }
     21
     22        function test_wp_export_passes_the_exception_if_the_writer_throws_other_exception() {
     23                $this->setExpectedException( 'Exception' );
     24                wp_export( array( 'writer' => 'Test_WP_Export_Stub_Writer_Throws_Other_Exception' ) );
     25        }
     26
     27}
     28
     29class Test_WP_Export_Stub_Writer_Throws_Export_Exception {
     30        function __construct( $formatter ) {
     31        }
     32        function export() {
     33                throw new WP_Export_Exception( 'baba' );
     34        }
     35}
     36
     37class Test_WP_Export_Stub_Writer_Throws_Other_Exception {
     38        function __construct( $formatter ) {
     39        }
     40        function export() {
     41                throw new Exception( 'baba' );
     42        }
     43}
  • new file tests/phpunit/tests/export/writers.php

    diff --git a/tests/phpunit/tests/export/writers.php b/tests/phpunit/tests/export/writers.php
    new file mode 100644
    index 0000000..6715d17
    - +  
     1<?php
     2
     3/**
     4 * Test WP_Export_*_Writer classes
     5 *
     6 * @group export
     7 * @ticket 22435
     8 */
     9class Test_WP_Export_Writers extends WP_UnitTestCase {
     10        function test_export_returner_returns_all_the_return_values() {
     11                if ( ! class_exists( 'WP_Export_Returner' ) ) {
     12                        $this->markTestSkipped( "WP_Export_Returner class doesn't exist" );
     13                }
     14                $returner = new WP_Export_Returner( $this->get_x_formatter() );
     15                $this->assertEquals( 'xxx' , $returner->export() );
     16        }
     17
     18        private function get_x_formatter() {
     19                $methods = array( 'before_posts', 'posts', 'after_posts' );
     20                $formatter = $this->getMock( 'WP_Export_WXR_Formatter', $methods, array( null ) );
     21                foreach( $methods as $method ) {
     22                        $return = 'posts' == $method? array( 'x' ) : 'x';
     23                        $formatter->expects( $this->once() )->method( $method )->with()->will( $this->returnValue( $return ) );
     24                }
     25                return $formatter;
     26        }
     27}
     28
  • new file tests/phpunit/tests/iterators.php

    diff --git a/tests/phpunit/tests/iterators.php b/tests/phpunit/tests/iterators.php
    new file mode 100644
    index 0000000..bd5f0d7
    - +  
     1<?php
     2
     3/**
     4 * @ticket 22435
     5 */
     6class Test_WP_Post_IDs_Iterator extends WP_UnitTestCase {
     7        function setUp() {
     8                if ( ! class_exists( 'WP_Post_IDs_Iterator' ) ) {
     9                        $this->markTestSkipped( "WP_Post_IDs_Iterator class doesn't exist" );
     10                }
     11
     12                parent::setUp();
     13        }
     14
     15        function test_create() {
     16                new WP_Post_IDs_Iterator( array( 1, 2, 3 ) );
     17        }
     18
     19        function test_no_posts() {
     20                $this->assertIteratorReturnsSamePostIDs( array() );
     21        }
     22
     23        function test_less_ids_than_limit() {
     24                $post_id_0 = $this->factory->post->create();
     25                $post_id_1 = $this->factory->post->create();
     26                $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1 ), 10 );
     27        }
     28
     29        function test_ids_exactly_as_limit() {
     30                $post_id_0 = $this->factory->post->create();
     31                $post_id_1 = $this->factory->post->create();
     32                $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1 ), 2 );
     33        }
     34
     35        function test_more_ids_than_limit() {
     36                $post_id_0 = $this->factory->post->create();
     37                $post_id_1 = $this->factory->post->create();
     38                $post_id_2 = $this->factory->post->create();
     39                $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1, $post_id_2 ), 2 );
     40        }
     41
     42        function test_ids_exactly_twice_more_than_limit() {
     43                $post_id_0 = $this->factory->post->create();
     44                $post_id_1 = $this->factory->post->create();
     45                $post_id_2 = $this->factory->post->create();
     46                $post_id_3 = $this->factory->post->create();
     47                $this->assertIteratorReturnsSamePostIDs( array( $post_id_0, $post_id_1, $post_id_2, $post_id_3 ), 2 );
     48        }
     49
     50        private function assertIteratorReturnsSamePostIDs( $post_ids, $limit = 2 ) {
     51                $this->assertEquals( $post_ids, wp_list_pluck( iterator_to_array( new WP_Post_IDs_Iterator( $post_ids, $limit ) ), 'ID' ) );
     52        }
     53}