PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.6
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 / XMLExporter.php

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

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