PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.2
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Admin / WPExporter.php

WPExporter.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 3.5.2, at includes/Admin/WPExporter.php

769 lines 26.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Admin;
4
5 use WP_Post;
6 use WP_Term;
7 use wpdb;
8
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 /*
15 * Originally made by WordPress.
16 *
17 * What changed (by Elementor):
18 * Remove echos.
19 * Fix indents.
20 * Add methods
21 * indent.
22 * wxr_categories_list.
23 * wxr_tags_list.
24 * wxr_terms_list.
25 * wxr_posts_list.
26 *
27 * What changed (by Templately)
28 * Added post__in capabilities for query.
29 * nav_menu_item from terms
30 * Some indent, and data type fixes
31 * query_args added as default args
32 */
33
34 #[\AllowDynamicProperties]
35 class WPExporter {
36 const WXR_VERSION = '1.2';
37
38 private static $default_args = [
39 'content' => 'all',
40 'author' => false,
41 'category' => false,
42 'start_date' => false,
43 'end_date' => false,
44 'status' => false,
45 'offset' => 0,
46 'limit' => -1,
47 'meta_query' => [], // If specified `meta_key` then will include all post(s) that have this meta_key.
48 'query_args' => []
49 ];
50
51 /**
52 * @var array
53 */
54 private $args;
55
56 /**
57 * @var wpdb
58 */
59 private $wpdb;
60
61 private $terms;
62
63 private $nav_menu_terms;
64
65 /**
66 * Run export, by requested args.
67 * Returns XML with exported data.
68 *
69 * @return array
70 */
71 public function run(): array {
72
73 $ptype = get_post_type_object( $this->args['content'] );
74 if ( 'docs' === $this->args['content'] ) {
75 $where = $this->wpdb->prepare( "{$this->wpdb->posts}.post_type = %s", $this->args['content'] );// phpcs:ignore
76 } else {
77 return [];
78 }
79
80 if ( $this->args['status'] && 'docs' === $this->args['content'] ) {
81 $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_status = %s", $this->args['status'] );// phpcs:ignore
82 } else {
83 $where .= " AND {$this->wpdb->posts}.post_status != 'auto-draft'";
84 }
85
86 if ( ! empty( $this->args['post__in'] ) ) {
87 $post_in = [implode(', ', $this->args['post__in'])];
88 $ids_placeholder = implode( ', ', array_fill( 0, count( $post_in ), '%d' ) );
89 $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.ID IN ($ids_placeholder)", $post_in );// phpcs:ignore
90 }
91
92
93
94 $join = '';
95
96 if ( isset( $this->args['category_terms'] ) && 'docs' === $this->args['content'] ) {
97 $join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)";
98 foreach ( $this->args['category_terms'] as $term_id ) {
99 $term = term_exists( $term_id, 'doc_category' );
100 if ( $term ) {
101 $where .= $this->wpdb->prepare( " AND {$this->wpdb->term_relationships}.term_taxonomy_id = %d", $term['term_taxonomy_id'] );// phpcs:ignore
102 }
103 }
104 } else if ( isset( $this->args['kb_terms'] ) && 'docs' === $this->args['content'] ) {
105 $join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)";
106 foreach ( $this->args['kb_terms'] as $term_id ) {
107 $term = term_exists( $term_id, 'knowledge_base' );
108 if ( $term ) {
109 $where .= $this->wpdb->prepare( " AND {$this->wpdb->term_relationships}.term_taxonomy_id = %d", $term['term_taxonomy_id'] );// phpcs:ignore
110 }
111 }
112 }
113
114 if ( $this->args['content'] ) {
115 if ( $this->args['author'] ) {
116 $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_author = %d", $this->args['author'] );// phpcs:ignore
117 }
118
119 if ( $this->args['start_date'] ) {
120 $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date >= %s", gmdate( 'Y-m-d', strtotime( $this->args['start_date'] ) ) );// phpcs:ignore
121 }
122
123 if ( $this->args['end_date'] ) {
124 $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_date < %s", gmdate( 'Y-m-d', strtotime( '+1 month', strtotime( $this->args['end_date'] ) ) ) );// phpcs:ignore
125 }
126 }
127
128 $limit = '';
129
130 // if ( - 1 !== (int) $this->args['limit'] ) {
131 // $limit = 'LIMIT ' . (int) $this->args['limit'] . ' OFFSET ' . (int) $this->args['offset'];
132 // }
133
134 if ( ! empty( $this->args['meta_query'] ) ) {
135 if ( $join ) {
136 $join .= ' ';
137 }
138
139 if ( $where ) {
140 $where .= ' ';
141 }
142
143 $meta_query = new \WP_Meta_Query( $this->args['meta_query'] );
144
145 global $wpdb;
146
147 $query_clauses = $meta_query->get_sql( 'docs', $wpdb->posts, 'ID' );
148
149 $join .= $query_clauses['join'];
150 $where .= $query_clauses['where'];
151 }
152
153 // Grab a snapshot of post IDs, just in case it changes during the export.
154 $post_ids = $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->posts} $join WHERE $where $limit" );// phpcs:ignore
155 $thumbnail_ids = [];
156
157 if ( ! empty( $this->args['include_post_featured_image_as_attachment'] ) ) {
158 foreach ( $post_ids as $post_id ) {
159 $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true );
160
161 if ( $thumbnail_id && ! in_array( $thumbnail_id, $post_ids, true ) ) {
162 $thumbnail_ids [] = $thumbnail_id;
163 }
164 }
165 }
166
167 $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.xml';
168 return [
169 'success' => true,
170 'data' => [
171 'filename' => $filename,
172 'filetype' => 'text/xml',
173 'download' => $this->get_xml_export( array_merge( $post_ids, $thumbnail_ids ) ),
174 ]
175 ];
176 }
177
178 /**
179 * Return tabulation characters, by `$columns`.
180 *
181 * @param int $columns
182 *
183 * @return string
184 */
185 private function indent( int $columns = 1 ): string {
186
187 $output = str_repeat( "\t", $columns );
188
189 return (string) $output;
190 }
191
192 /**
193 * Return wrapped given string in XML CDATA tag.
194 *
195 * @param string $str String to wrap in XML CDATA tag.
196 *
197 * @return string
198 */
199 private function wxr_cdata( $str ): string {
200 $str = (string) $str;
201
202 if ( ! seems_utf8( $str ) ) {
203 $str = utf8_encode( $str );
204 }
205
206 $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
207
208 return $str;
209 }
210
211 /**
212 * Return the URL of the site.
213 *
214 * @return string Site URL.
215 */
216 private function wxr_site_url(): string {
217 if ( is_multisite() ) {
218 // Multisite: the base URL.
219 return network_home_url();
220 } else {
221 // WordPress (single site): the blog URL.
222 return get_bloginfo_rss( 'url' );
223 }
224 }
225
226 /**
227 * Return a cat_name XML tag from a given category object.
228 *
229 * @param WP_Term $category Category Object
230 *
231 * @return string
232 */
233 private function wxr_cat_name( $category ): string {
234 if ( empty( $category->name ) ) {
235 return '';
236 }
237
238 return $this->indent( 3 ) . '<wp:cat_name>' . $this->wxr_cdata( $category->name ) . '</wp:cat_name>' . PHP_EOL;
239 }
240
241 /**
242 * Return a category_description XML tag from a given category object.
243 *
244 * @param WP_Term $category Category Object
245 *
246 * @return string
247 */
248 private function wxr_category_description( $category ): string {
249 if ( empty( $category->description ) ) {
250 return '';
251 }
252
253 return $this->indent( 3 ) . '<wp:category_description>' . $this->wxr_cdata( $category->description ) . "</wp:category_description>\n";
254 }
255
256 /**
257 * Return a tag_name XML tag from a given tag object.
258 *
259 * @param WP_Term $tag Tag Object
260 *
261 * @return string
262 */
263 private function wxr_tag_name( $tag ): string {
264 if ( empty( $tag->name ) ) {
265 return '';
266 }
267
268 return $this->indent( 3 ) . '<wp:tag_name>' . $this->wxr_cdata( $tag->name ) . '</wp:tag_name>' . PHP_EOL;
269 }
270
271 /**
272 * Return a tag_description XML tag from a given tag object.
273 *
274 * @param WP_Term $tag Tag Object
275 *
276 * @return string
277 */
278 private function wxr_tag_description( $tag ): string {
279 if ( empty( $tag->description ) ) {
280 return '';
281 }
282
283 return $this->indent( 3 ) . '<wp:tag_description>' . $this->wxr_cdata( $tag->description ) . '</wp:tag_description>' . PHP_EOL;
284 }
285
286 /**
287 * Return a term_name XML tag from a given term object.
288 *
289 * @param WP_Term $term Term Object
290 *
291 * @return string
292 */
293 private function wxr_term_name( $term ): string {
294 if ( empty( $term->name ) ) {
295 return '';
296 }
297
298 return $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $term->name ) . '</wp:term_name>' . PHP_EOL;
299 }
300
301 /**
302 * Return a term_description XML tag from a given term object.
303 *
304 * @param WP_Term $term Term Object
305 *
306 * @return string
307 */
308 private function wxr_term_description( $term ): string {
309 if ( empty( $term->description ) ) {
310 return '';
311 }
312
313 return $this->indent( 3 ) . '<wp:term_description>' . $this->wxr_cdata( $term->description ) . '</wp:term_description>' . PHP_EOL;
314 }
315
316 /**
317 * Return term meta XML tags for a given term object.
318 *
319 * @param WP_Term $term Term object.
320 *
321 * @return string
322 */
323 private function wxr_term_meta( $term ): string {
324 $result = '';
325 $termmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->termmeta} WHERE term_id = %d", $term->term_id ) );// phpcs:ignore
326
327 foreach ( $termmeta as $meta ) {
328 /**
329 * Filters whether to selectively skip term meta used for WXR exports.
330 *
331 * Returning a truthy value from the filter will skip the current meta
332 * object from being exported.
333 *
334 * @param bool $skip Whether to skip the current piece of term meta. Default false.
335 * @param string $meta_key Current meta key.
336 * @param object $meta Current meta object.
337 *
338 * @since 4.6.0
339 *
340 */
341 if ( ! apply_filters( 'wxr_export_skip_termmeta', false, $meta->meta_key, $meta ) ) {
342 $result .= sprintf( $this->indent( 3 ) . "<wp:termmeta>\n\t\t\t<wp:meta_key>%s</wp:meta_key>\n\t\t\t<wp:meta_value>%s</wp:meta_value>\n\t\t</wp:termmeta>\n", $this->wxr_cdata( $meta->meta_key ), $this->wxr_cdata( $meta->meta_value ) );
343 }
344 }
345
346 return $result;
347 }
348
349 /**
350 * Return list of authors with posts.
351 *
352 * @param int[] $post_ids Optional. Array of post IDs to filter the query by.
353 *
354 * @return string
355 */
356 private function wxr_authors_list( array $post_ids = null ): string {
357 $result = '';
358
359 if ( ! empty( $post_ids ) ) {
360 $post_ids = array_map( 'absint', $post_ids );
361 $and = 'AND ID IN ( ' . implode( ', ', $post_ids ) . ')';
362 } else {
363 $and = '';
364 }
365
366 $authors = [];
367 $results = $this->wpdb->get_results( "SELECT DISTINCT post_author FROM {$this->wpdb->posts} WHERE post_status != 'auto-draft' $and" );// phpcs:ignore
368 foreach ( (array) $results as $r ) {
369 $authors[] = get_userdata( $r->post_author );
370 }
371
372 $authors = array_filter( $authors );
373
374 foreach ( $authors as $author ) {
375 $result .= $this->indent( 2 ) . '<wp:author>' . PHP_EOL;
376
377 $result .= $this->indent( 3 ) . '<wp:author_id>' . (int) $author->ID . '</wp:author_id>' . PHP_EOL;
378 $result .= $this->indent( 3 ) . '<wp:author_login>' . $this->wxr_cdata( $author->user_login ) . '</wp:author_login>' . PHP_EOL;
379 $result .= $this->indent( 3 ) . '<wp:author_email>' . $this->wxr_cdata( $author->user_email ) . '</wp:author_email>' . PHP_EOL;
380 $result .= $this->indent( 3 ) . '<wp:author_display_name>' . $this->wxr_cdata( $author->display_name ) . '</wp:author_display_name>' . PHP_EOL;
381 $result .= $this->indent( 3 ) . '<wp:author_first_name>' . $this->wxr_cdata( $author->first_name ) . '</wp:author_first_name>' . PHP_EOL;
382 $result .= $this->indent( 3 ) . '<wp:author_last_name>' . $this->wxr_cdata( $author->last_name ) . '</wp:author_last_name>' . PHP_EOL;
383
384 $result .= $this->indent( 2 ) . '</wp:author>' . PHP_EOL;
385 }
386
387 return $result;
388 }
389
390 /**
391 * Return list of categories.
392 *
393 * @param array $cats
394 *
395 * @return string
396 */
397 private function wxr_categories_list( array $cats ): string {
398 $result = '';
399
400 foreach ( $cats as $c ) {
401 $result .= $this->indent( 2 ) . '<wp:category>' . PHP_EOL;
402
403 $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $c->term_id . '</wp:term_id>' . PHP_EOL;
404 $result .= $this->indent( 3 ) . '<wp:category_nicename>' . $this->wxr_cdata( $c->slug ) . '</wp:category_nicename>' . PHP_EOL;
405 $result .= $this->indent( 3 ) . '<wp:category_parent>' . $this->wxr_cdata( $c->parent ? $cats[ $c->parent ]->slug : '' ) . '</wp:category_parent>' . PHP_EOL;
406 $result .= $this->wxr_cat_name( $c ) . $this->wxr_category_description( $c ) . $this->wxr_term_meta( $c );
407
408 $result .= $this->indent( 2 ) . '</wp:category>' . PHP_EOL;
409 }
410
411 return $result;
412 }
413
414 /**
415 * Return list of tags.
416 *
417 * @param array $tags
418 *
419 * @return string
420 */
421 private function wxr_tags_list( array $tags ): string {
422 $result = '';
423
424 foreach ( $tags as $t ) {
425 $result .= $this->indent( 2 ) . '<wp:tag>' . PHP_EOL;
426
427 $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $t->term_id . '</wp:term_id>' . PHP_EOL;
428 $result .= $this->indent( 3 ) . '<wp:tag_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:tag_slug>' . PHP_EOL;
429 $result .= $this->wxr_tag_name( $t ) . $this->wxr_tag_description( $t ) . $this->wxr_term_meta( $t );
430
431 $result .= $this->indent( 2 ) . '</wp:tag>' . PHP_EOL;
432 }
433
434 return $result;
435 }
436
437 /**
438 * Return list of terms.
439 *
440 * @param array $terms
441 *
442 * @return string
443 */
444 private function wxr_terms_list( array $terms ): string {
445 $result = '';
446
447 foreach ( $terms as $t ) {
448 $result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL;
449
450 $result .= $this->indent( 3 ) . '<wp:term_id>' . $this->wxr_cdata( $t->term_id ) . '</wp:term_id>' . PHP_EOL;
451 $result .= $this->indent( 3 ) . '<wp:term_taxonomy>' . $this->wxr_cdata( $t->taxonomy ) . '</wp:term_taxonomy>' . PHP_EOL;
452 $result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $t->slug ) . '</wp:term_slug>' . PHP_EOL;
453 $result .= $this->indent( 3 ) . '<wp:term_parent>' . $this->wxr_cdata( $t->parent ? $terms[ $t->parent ]->slug : '' ) . '</wp:term_parent>' . PHP_EOL;
454 $result .= $this->wxr_term_name( $t ) . $this->wxr_term_description( $t ) . $this->wxr_term_meta( $t );
455
456 $result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL;
457 }
458
459 return $result;
460 }
461
462 private function get_terms( array $post_ids ) {
463 return wp_get_object_terms( $post_ids, get_object_taxonomies( $this->args['content'] ) );
464 }
465
466 /**
467 * Return list of posts, by requested `$post_ids`.
468 *
469 * @param array $post_ids
470 *
471 * @return string
472 */
473 private function wxr_posts_list( array $post_ids ): string {
474 $result = '';
475
476 if ( $post_ids ) {
477 global $wp_query;
478
479 // Fake being in the loop.
480 $wp_query->in_the_loop = true;
481
482 // Fetch 20 posts at a time rather than loading the entire table into memory.
483 while ( $next_posts = array_splice( $post_ids, 0, 20 ) ) {
484 $where = 'WHERE ID IN (' . implode( ',', $next_posts ) . ')';
485 $posts = $this->wpdb->get_results( "SELECT * FROM {$this->wpdb->posts} $where" );// phpcs:ignore
486
487 // Begin Loop.
488 foreach ( $posts as $post ) {
489 setup_postdata( $post );
490
491 $title = apply_filters( 'the_title_rss', $post->post_title );
492
493 /**
494 * Filters the post content used for WXR exports.
495 *
496 * @param string $post_content Content of the current post.
497 *
498 * @since 2.5.0
499 *
500 */
501 $content = $this->wxr_cdata( apply_filters( 'the_content_export', $post->post_content ) );
502
503 /**
504 * Filters the post excerpt used for WXR exports.
505 *
506 * @param string $post_excerpt Excerpt for the current post.
507 *
508 * @since 2.6.0
509 *
510 */
511 $excerpt = $this->wxr_cdata( apply_filters( 'the_excerpt_export', $post->post_excerpt ) );
512
513 $result .= $this->indent( 2 ) . '<item>' . PHP_EOL;
514
515 $result .= $this->indent( 3 ) . '<title>' . $title . '</title>' . PHP_EOL;
516 $result .= $this->indent( 3 ) . '<link>' . esc_url( get_permalink() ) . '</link>' . PHP_EOL;
517 $result .= $this->indent( 3 ) . '<pubDate>' . mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) . '</pubDate>' . PHP_EOL;
518 $result .= $this->indent( 3 ) . '<dc:creator>' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</dc:creator>' . PHP_EOL;
519 $result .= $this->indent( 3 ) . '<guid isPermaLink="false">' . $this->wxr_cdata( get_the_author_meta( 'login' ) ) . '</guid>' . PHP_EOL;
520 $result .= $this->indent( 3 ) . '<description></description>' . PHP_EOL;
521 $result .= $this->indent( 3 ) . '<content:encoded>' . $content . '</content:encoded>' . PHP_EOL;
522 $result .= $this->indent( 3 ) . '<excerpt:encoded>' . $excerpt . '</excerpt:encoded>' . PHP_EOL;
523 $result .= $this->indent( 3 ) . '<wp:post_id>' . (int) $post->ID . '</wp:post_id>' . PHP_EOL;
524 $result .= $this->indent( 3 ) . '<wp:post_date>' . $this->wxr_cdata( $post->post_date ) . '</wp:post_date>' . PHP_EOL;
525 $result .= $this->indent( 3 ) . '<wp:post_date_gmt>' . $this->wxr_cdata( $post->post_date_gmt ) . '</wp:post_date_gmt>' . PHP_EOL;
526 $result .= $this->indent( 3 ) . '<wp:comment_status>' . $this->wxr_cdata( $post->comment_status ) . '</wp:comment_status>' . PHP_EOL;
527 $result .= $this->indent( 3 ) . '<wp:ping_status>' . $this->wxr_cdata( $post->ping_status ) . '</wp:ping_status>' . PHP_EOL;
528 $result .= $this->indent( 3 ) . '<wp:post_name>' . $this->wxr_cdata( $post->post_name ) . '</wp:post_name>' . PHP_EOL;
529 $result .= $this->indent( 3 ) . '<wp:status>' . $this->wxr_cdata( $post->post_status ) . '</wp:status>' . PHP_EOL;
530 $result .= $this->indent( 3 ) . '<wp:post_parent>' . $this->wxr_cdata( $post->post_parent ) . '</wp:post_parent>' . PHP_EOL;
531 $result .= $this->indent( 3 ) . '<wp:menu_order>' . (int) $post->menu_order . '</wp:menu_order>' . PHP_EOL;
532 $result .= $this->indent( 3 ) . '<wp:post_type>' . $this->wxr_cdata( $post->post_type ) . '</wp:post_type>' . PHP_EOL;
533 $result .= $this->indent( 3 ) . '<wp:post_password>' . $this->wxr_cdata( $post->post_password ) . '</wp:post_password>' . PHP_EOL;
534 $result .= $this->indent( 3 ) . '<wp:is_sticky>' . ( is_sticky( $post->ID ) ? 1 : 0 ) . '</wp:is_sticky>' . PHP_EOL;
535
536 if ( 'attachment' === $post->post_type ) {
537 $result .= $this->indent( 3 ) . '<wp:attachment_url>' . $this->wxr_cdata( wp_get_attachment_url( $post->ID ) ) . '</wp:attachment_url>' . PHP_EOL;
538 }
539
540 $result .= $this->wxr_post_taxonomy( $post );
541
542 $postmeta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->postmeta} WHERE post_id = %d", $post->ID ) );// phpcs:ignore
543 foreach ( $postmeta as $meta ) {
544 /**
545 * Filters whether to selectively skip post meta used for WXR exports.
546 *
547 * Returning a truthy value from the filter will skip the current meta
548 * object from being exported.
549 *
550 * @param bool $skip Whether to skip the current post meta. Default false.
551 * @param string $meta_key Current meta key.
552 * @param object $meta Current meta object.
553 *
554 * @since 3.3.0
555 *
556 */
557 if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) {
558 continue;
559 }
560
561 $result .= $this->indent( 3 ) . '<wp:postmeta>' . PHP_EOL;
562
563 $result .= $this->indent( 4 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL;
564 $result .= $this->indent( 4 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_value ) . '</wp:meta_value>' . PHP_EOL;
565
566 $result .= $this->indent( 3 ) . '</wp:postmeta>' . PHP_EOL;
567 }
568
569 $_comments = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->comments} WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) );// phpcs:ignore
570 $comments = array_map( 'get_comment', $_comments );
571 foreach ( $comments as $c ) {
572
573 $result .= $this->indent( 3 ) . '<wp:comment>' . PHP_EOL;
574
575 $result .= $this->indent( 4 ) . '<wp:comment_id>' . (int) $c->comment_ID . '</wp:comment_id>' . PHP_EOL;
576 $result .= $this->indent( 4 ) . '<wp:comment_author>' . $this->wxr_cdata( $c->comment_author ) . '</wp:comment_author>' . PHP_EOL;
577 $result .= $this->indent( 4 ) . '<wp:comment_author_email>' . $this->wxr_cdata( $c->comment_author_email ) . '</wp:comment_author_email>' . PHP_EOL;
578 $result .= $this->indent( 4 ) . '<wp:comment_author_url>' . $this->wxr_cdata( $c->comment_author_url ) . '</wp:comment_author_url>' . PHP_EOL;
579 $result .= $this->indent( 4 ) . '<wp:comment_author_IP>' . $this->wxr_cdata( $c->comment_author_IP ) . '</wp:comment_author_IP>' . PHP_EOL;
580 $result .= $this->indent( 4 ) . '<wp:comment_date>' . $this->wxr_cdata( $c->comment_date ) . '</wp:comment_date>' . PHP_EOL;
581 $result .= $this->indent( 4 ) . '<wp:comment_date_gmt>' . $this->wxr_cdata( $c->comment_date_gmt ) . '</wp:comment_date_gmt>' . PHP_EOL;
582 $result .= $this->indent( 4 ) . '<wp:comment_content>' . $this->wxr_cdata( $c->comment_content ) . '</wp:comment_content>' . PHP_EOL;
583 $result .= $this->indent( 4 ) . '<wp:comment_approved>' . $this->wxr_cdata( $c->comment_approved ) . '</wp:comment_approved>' . PHP_EOL;
584 $result .= $this->indent( 4 ) . '<wp:comment_type>' . $this->wxr_cdata( $c->comment_type ) . '</wp:comment_type>' . PHP_EOL;
585 $result .= $this->indent( 4 ) . '<wp:comment_parent>' . $this->wxr_cdata( $c->comment_parent ) . '</wp:comment_parent>' . PHP_EOL;
586 $result .= $this->indent( 4 ) . '<wp:comment_user_id>' . (int) $c->user_id . '</wp:comment_user_id>' . PHP_EOL;
587
588 $c_meta = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->commentmeta} WHERE comment_id = %d", $c->comment_ID ) );// phpcs:ignore
589 foreach ( $c_meta as $meta ) {
590 /**
591 * Filters whether to selectively skip comment meta used for WXR exports.
592 *
593 * Returning a truthy value from the filter will skip the current meta
594 * object from being exported.
595 *
596 * @param bool $skip Whether to skip the current comment meta. Default false.
597 * @param string $meta_key Current meta key.
598 * @param object $meta Current meta object.
599 *
600 * @since 4.0.0
601 *
602 */
603 if ( apply_filters( 'wxr_export_skip_commentmeta', false, $meta->meta_key, $meta ) ) {
604 continue;
605 }
606
607 $result .= $this->indent( 4 ) . '<wp:commentmeta>' . PHP_EOL;
608
609 $result .= $this->indent( 5 ) . '<wp:meta_key>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_key>' . PHP_EOL;
610 $result .= $this->indent( 5 ) . '<wp:meta_value>' . $this->wxr_cdata( $meta->meta_key ) . '</wp:meta_value>' . PHP_EOL;
611
612 $result .= $this->indent( 4 ) . '</wp:commentmeta>' . PHP_EOL;
613 }
614
615 $result .= $this->indent( 3 ) . '</wp:comment>' . PHP_EOL;
616 }
617
618 $result .= $this->indent( 2 ) . '</item>' . PHP_EOL;
619 }
620 }
621 }
622
623 return $result;
624 }
625
626 /**
627 * Return all navigation menu terms
628 *
629 * @return string
630 */
631 private function wxr_nav_menu_terms(): string {
632 $args = [];
633
634 if( ! empty( $this->nav_menu_terms ) ) {
635 $args['include'] = $this->nav_menu_terms;
636 }
637
638 $nav_menus = wp_get_nav_menus( $args );
639 if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) {
640 return '';
641 }
642
643 $result = '';
644
645 foreach ( $nav_menus as $menu ) {
646 $this->terms[ $menu->term_id ] = $menu;
647
648 $result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL;
649 $result .= $this->indent( 3 ) . '<wp:term_id>' . (int) $menu->term_id . '</wp:term_id>' . PHP_EOL;
650 $result .= $this->indent( 3 ) . '<wp:term_taxonomy>nav_menu</wp:term_taxonomy>' . PHP_EOL;
651 $result .= $this->indent( 3 ) . '<wp:term_slug>' . $this->wxr_cdata( $menu->slug ) . '</wp:term_slug>' . PHP_EOL;
652 $result .= $this->indent( 3 ) . '<wp:term_name>' . $this->wxr_cdata( $menu->name ) . '</wp:term_name>' . PHP_EOL;
653 $result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL;
654 }
655
656 return $result;
657 }
658
659 /**
660 * Return list of taxonomy terms, in XML tag format, associated with a post
661 *
662 * @param object $post
663 *
664 * @return string
665 */
666 private function wxr_post_taxonomy( $post ): string {
667 $result = '';
668
669 $taxonomies = get_object_taxonomies( $post->post_type );
670
671 if ( empty( $taxonomies ) ) {
672 return $result;
673 }
674
675 $terms = wp_get_object_terms( $post->ID, $taxonomies );
676
677 foreach ( (array) $terms as $term ) {
678 $result .= $this->indent( 3 ) . "<category domain=\"{$term->taxonomy}\" nicename=\"{$term->slug}\">" . $this->wxr_cdata( $term->name ) . '</category>' . PHP_EOL;
679 }
680
681 return $result;
682 }
683
684 /**
685 * Get the XML export.
686 *
687 * @param array $post_ids
688 *
689 * @return string
690 */
691 private function get_xml_export( array $post_ids ): string {
692 $charset = get_bloginfo( 'charset' );
693 $generator = get_the_generator( 'export' );
694 $wxr_version = self::WXR_VERSION;
695 $wxr_site_url = $this->wxr_site_url();
696 $rss_info_name = get_bloginfo_rss( 'name' );
697 $rss_info_url = get_bloginfo_rss( 'url' );
698 $rss_info_description = get_bloginfo_rss( 'description' );
699 $rss_info_language = get_bloginfo_rss( 'language' );
700 $pub_date = gmdate( 'D, d M Y H:i:s +0000' );
701
702 $dynamic = $this->wxr_authors_list( $post_ids );
703
704 ob_start();
705 /** This action is documented in wp-includes/feed-rss2.php */
706 do_action( 'rss2_head' );
707 $rss2_head = ob_get_clean();
708
709 $dynamic .= $rss2_head;
710
711 if ( 'all' === $this->args['content'] || 'nav_menu_item' === $this->args['content'] ) {
712 $dynamic .= $this->wxr_nav_menu_terms();
713 }
714
715 $dynamic .= $this->wxr_terms_list( $this->get_terms( $post_ids ) );
716 $dynamic .= $this->wxr_posts_list( $post_ids );
717
718 $result = <<<EOT
719 <?xml version="1.0" encoding="$charset" ?>
720 <!-- This is a WordPress eXtended RSS file generated by WordPress as an export of your site. -->
721 <!-- It contains information about your site's posts, pages, comments, categories, and other content. -->
722 <!-- You may use this file to transfer that content from one site to another. -->
723 <!-- This file is not intended to serve as a complete backup of your site. -->
724
725 <!-- To import this information into a WordPress site follow these steps: -->
726 <!-- 1. Log in to that site as an administrator. -->
727 <!-- 2. Go to Tools: Import in the WordPress admin panel. -->
728 <!-- 3. Install the "WordPress" importer from the list. -->
729 <!-- 4. Activate & Run Importer. -->
730 <!-- 5. Upload this file using the form provided on that page. -->
731 <!-- 6. You will first be asked to map the authors in this export file to users -->
732 <!-- on the site. For each author, you may choose to map to an -->
733 <!-- existing user on the site or to create a new user. -->
734 <!-- 7. WordPress will then import each of the posts, pages, comments, categories, etc. -->
735 <!-- contained in this file into your site. -->
736 $generator
737 <rss version="2.0"
738 xmlns:excerpt="http://wordpress.org/export/$wxr_version/excerpt/"
739 xmlns:content="http://purl.org/rss/1.0/modules/content/"
740 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
741 xmlns:dc="http://purl.org/dc/elements/1.1/"
742 xmlns:wp="http://wordpress.org/export/$wxr_version/"
743 >
744 <channel>
745 <title>$rss_info_name</title>
746 <link>$rss_info_url</link>
747 <description>$rss_info_description</description>
748 <pubDate>$pub_date</pubDate>
749 <language>$rss_info_language</language>
750 <wp:wxr_version>$wxr_version</wp:wxr_version>
751 <wp:base_site_url>$wxr_site_url</wp:base_site_url>
752 <wp:base_blog_url>$rss_info_url</wp:base_blog_url>
753 $dynamic
754 </channel>
755 </rss>
756 EOT;
757
758 return $result;
759 }
760
761 public function __construct( array $args = [] ) {
762 global $wpdb;
763
764 $this->args = wp_parse_args( $args, self::$default_args );
765
766 $this->wpdb = $wpdb;
767 }
768 }
769