PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.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 All 200 releases
← All changes | includes/Admin/WPExporter.php +304 -650 3.4.24.9.2 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2
3 3 namespace WPDeveloper\BetterDocs\Admin;
4 4
5 -use WP_Post;
5 +use WPDeveloper\BetterDocs\Admin\ExportDefaults;
6 6 use WP_Term;
7 7 use wpdb;
8 8
9 9
@@ -10,45 +10,21 @@
10 10 if ( ! defined( 'ABSPATH' ) ) {
11 11 exit; // Exit if accessed directly.
12 12 }
13 13
14 -/*
15 - * Originally made by WordPress.
14 +/**
15 + * SQL building helpers below interpolate WP-provided $wpdb table identifiers
16 + * (`$wpdb->posts`, `$wpdb->term_relationships`, `$wpdb->term_taxonomy`) and use
17 + * dynamic %d placeholder lists derived from integer arrays. Suppressing the
18 + * related PHPCS notices class-wide rather than per-call.
16 19 *
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
20 + * phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
21 + * phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
22 + * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
23 + * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
32 24 */
33 -
34 25 #[\AllowDynamicProperties]
35 26 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 27 /**
52 28 * @var array
53 29 */
54 30 private $args;
@@ -57,712 +33,390 @@
57 33 * @var wpdb
58 34 */
59 35 private $wpdb;
60 36
61 - private $terms;
37 + public function __construct( array $args = [] ) {
38 + global $wpdb;
62 39
63 - private $nav_menu_terms;
40 + $this->args = wp_parse_args($args, ExportDefaults::get_default_args());
64 41
65 - /**
66 - * Run export, by requested args.
67 - * Returns XML with exported data.
68 - *
69 - * @return array
70 - */
71 - public function run(): array {
42 + $this->wpdb = $wpdb;
43 + }
72 44
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 [];
45 + public function exporter_ids(): array {
46 + // Build the base query
47 + $query_parts = $this->build_base_query();
48 + $where = $query_parts['where'];
49 + $join = $query_parts['join'];
50 +
51 + // Handle post status
52 + $where .= $this->build_status_condition();
53 +
54 + // Handle specific posts selection
55 + if ( ! empty( $this->args['post__in'] ) ) {
56 + $where .= $this->build_post_in_condition();
78 57 }
79 58
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'";
59 + // Handle category terms
60 + if ( isset( $this->args['category_terms'] ) ) {
61 + $category_query = $this->build_category_query();
62 + $join .= $category_query['join'];
63 + $where .= $category_query['where'];
84 64 }
85 65
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
66 + // Handle knowledge base terms
67 + if ( isset( $this->args['kb_terms'] ) ) {
68 + $kb_query = $this->build_kb_query();
69 + $join .= $kb_query['join'];
70 + $where .= $kb_query['where'];
90 71 }
91 72
73 + // Handle additional filters (author, dates, meta)
74 + $where .= $this->build_additional_filters();
92 75
76 + // $join/$where are composed from prepared fragments above; identifiers are WP-provided.
77 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
78 + $post_ids = $this->wpdb->get_col( "SELECT DISTINCT {$this->wpdb->posts}.ID FROM {$this->wpdb->posts} $join WHERE $where" );
93 79
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 - }
80 + // Handle FAQ posts separately
81 + $faq_post_ids = [];
82 + if ( ! empty( $this->args['include_faq'] ) ) {
83 + $faq_post_ids = $this->get_faq_posts();
112 84 }
113 85
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 - }
86 + // Combine post IDs
87 + $all_post_ids = array_merge( $post_ids, $faq_post_ids );
118 88
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 - }
89 + $all_post_ids = WPMLSupport::expand_with_translations( $all_post_ids );
122 90
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 - }
91 + // Handle featured images
92 + $thumbnail_ids = $this->get_thumbnail_ids( $all_post_ids );
127 93
128 - $limit = '';
94 + // Generate final post IDs array
95 + return array_unique(array_merge($all_post_ids, $thumbnail_ids));
129 96
130 - // if ( - 1 !== (int) $this->args['limit'] ) {
131 - // $limit = 'LIMIT ' . (int) $this->args['limit'] . ' OFFSET ' . (int) $this->args['offset'];
132 - // }
97 + }
133 98
134 - if ( ! empty( $this->args['meta_query'] ) ) {
135 - if ( $join ) {
136 - $join .= ' ';
137 - }
99 + // private function handle_glossaries_export(): array {
100 + public function get_glossary_term_ids(): array {
101 + if ( isset( $this->args['glossary_terms'] ) && ( count( $this->args['glossary_terms'] ) > 0 ) ) {
102 + $glossary_term_ids = [];
103 + foreach( $this->args['glossary_terms'] as $glossary_slug ) {
104 + $term_object = get_term_by('slug', $glossary_slug , 'glossaries');
105 + if( isset( $term_object->term_id ) && ! empty( $term_object->term_id ) ){
106 + array_push($glossary_term_ids, $term_object->term_id);
107 + }
108 + }
109 + } else {
110 + // $this->wpdb->term_taxonomy is a WP-core table identifier; %s placeholder binds taxonomy name.
111 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
112 + $glossary_term_ids = $this->wpdb->get_col(
113 + $this->wpdb->prepare(
114 + "SELECT term_id FROM {$this->wpdb->term_taxonomy} WHERE taxonomy = %s",
115 + (string) $this->args['content']
116 + )
117 + );
118 + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
119 + }
138 120
139 - if ( $where ) {
140 - $where .= ' ';
141 - }
121 + return $glossary_term_ids;
122 + }
142 123
143 - $meta_query = new \WP_Meta_Query( $this->args['meta_query'] );
124 + // The query-builder methods below interpolate only $wpdb core table names
125 + // ({$this->wpdb->posts}, etc.) — never user input — and bind every value via
126 + // %s/%d placeholders, so the InterpolatedNotPrepared warnings are spurious.
127 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
128 + public function build_base_query(): array {
129 + $where = $this->wpdb->prepare("{$this->wpdb->posts}.post_type = %s", 'docs');
130 + return [
131 + 'where' => $where,
132 + 'join' => ''
133 + ];
134 + }
144 135
145 - global $wpdb;
136 + public function build_status_condition(): string {
137 + if ($this->args['status']) {
138 + return $this->wpdb->prepare(" AND {$this->wpdb->posts}.post_status = %s", $this->args['status']);
139 + }
140 + return " AND {$this->wpdb->posts}.post_status != 'auto-draft'";
141 + }
146 142
147 - $query_clauses = $meta_query->get_sql( 'docs', $wpdb->posts, 'ID' );
143 + public function build_post_in_condition(): string {
144 + $ids = $this->args['post__in'];
145 + $ids_placeholder = implode(', ', array_fill(0, count($ids), '%d'));
146 + return $this->wpdb->prepare(" AND {$this->wpdb->posts}.ID IN ($ids_placeholder)", $ids);
147 + }
148 148
149 - $join .= $query_clauses['join'];
150 - $where .= $query_clauses['where'];
151 - }
149 + public function build_category_query(): array {
150 + $join = "INNER JOIN {$this->wpdb->term_relationships} tr ON ({$this->wpdb->posts}.ID = tr.object_id)
151 + INNER JOIN {$this->wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";
152 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 = [];
153 + $where = " AND tt.taxonomy = 'doc_category'";
156 154
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 );
155 + if ( ! empty( $this->args['category_terms'] ) ) {
156 + $term_ids = [];
157 + foreach ( $this->args['category_terms'] as $term_id ) {
158 + $term = term_exists( $term_id, 'doc_category' );
159 + if ( $term ) {
160 + $term_ids[] = $term['term_taxonomy_id'];
161 + }
162 + }
160 163
161 - if ( $thumbnail_id && ! in_array( $thumbnail_id, $post_ids, true ) ) {
162 - $thumbnail_ids [] = $thumbnail_id;
163 - }
164 + if ( ! empty( $term_ids ) ) {
165 + $term_placeholder = implode( ', ', array_fill( 0, count( $term_ids ), '%d' ) );
166 + $where .= $this->wpdb->prepare( " AND tt.term_taxonomy_id IN ($term_placeholder)", $term_ids );
164 167 }
165 168 }
166 169
167 - $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.xml';
168 170 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 - ]
171 + 'join' => $join,
172 + 'where' => $where
175 173 ];
176 174 }
177 175
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 {
176 + public function build_kb_query(): array {
177 + $join = "INNER JOIN {$this->wpdb->term_relationships} tr ON ({$this->wpdb->posts}.ID = tr.object_id)
178 + INNER JOIN {$this->wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";
186 179
187 - $output = str_repeat( "\t", $columns );
180 + $where = " AND tt.taxonomy = 'knowledge_base'";
188 181
189 - return (string) $output;
190 - }
182 + if ( ! empty( $this->args['kb_terms'] ) ) {
183 + $term_ids = [];
184 + foreach ( $this->args['kb_terms'] as $term_id ) {
185 + $term = term_exists( $term_id, 'knowledge_base' );
186 + if ( $term ) {
187 + $term_ids[] = $term['term_taxonomy_id'];
188 + }
189 + }
191 190
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 );
191 + if ( ! empty( $term_ids ) ) {
192 + $term_placeholder = implode( ', ', array_fill( 0, count( $term_ids ), '%d' ) );
193 + $where .= $this->wpdb->prepare( " AND tt.term_taxonomy_id IN ($term_placeholder)", $term_ids );
194 + }
204 195 }
205 196
206 - $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
207 -
208 - return $str;
197 + return [
198 + 'join' => $join,
199 + 'where' => $where
200 + ];
209 201 }
210 202
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 - }
203 + public function build_additional_filters(): string {
204 + $where = '';
225 205
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 '';
206 + if ( $this->args['author'] ) {
207 + $where .= $this->wpdb->prepare( " AND {$this->wpdb->posts}.post_author = %d", $this->args['author'] );
236 208 }
237 209
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 '';
210 + if ( $this->args['start_date'] ) {
211 + $where .= $this->wpdb->prepare(
212 + " AND {$this->wpdb->posts}.post_date >= %s",
213 + gmdate( 'Y-m-d', strtotime( $this->args['start_date'] ) )
214 + );
251 215 }
252 216
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 '';
217 + if ( $this->args['end_date'] ) {
218 + $where .= $this->wpdb->prepare(
219 + " AND {$this->wpdb->posts}.post_date < %s",
220 + gmdate( 'Y-m-d', strtotime( '+1 month', strtotime( $this->args['end_date'] ) ) )
221 + );
266 222 }
267 223
268 - return $this->indent( 3 ) . '<wp:tag_name>' . $this->wxr_cdata( $tag->name ) . '</wp:tag_name>' . PHP_EOL;
224 + return $where;
269 225 }
270 226
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 - }
227 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
228 + public function get_faq_posts(): array {
229 + return get_posts([
230 + 'numberposts' => -1,
231 + 'post_type' => 'betterdocs_faq',
232 + 'fields' => 'ids',
233 + 'post_status' => 'publish',
234 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- intentional: export the raw, untranslated FAQ set so multilingual filters don't drop or swap rows during export.
235 + 'suppress_filters' => true,
236 + ]);
237 + }
282 238
283 - return $this->indent( 3 ) . '<wp:tag_description>' . $this->wxr_cdata( $tag->description ) . '</wp:tag_description>' . PHP_EOL;
284 - }
239 + public function get_thumbnail_ids(array $post_ids): array {
240 + $thumbnail_ids = [];
285 241
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 ) );
242 + if ( ! empty( $this->args['include_post_featured_image_as_attachment'] ) ) {
243 + foreach ( $post_ids as $post_id ) {
244 + $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true );
245 + if ( $thumbnail_id && ! in_array( $thumbnail_id, $post_ids, true ) ) {
246 + $thumbnail_ids[] = $thumbnail_id;
247 + }
343 248 }
344 249 }
345 250
346 - return $result;
251 + return $thumbnail_ids;
347 252 }
348 253
349 254 /**
350 - * Return list of authors with posts.
255 + * Return tabulation characters, by `$columns`.
351 256 *
352 - * @param int[] $post_ids Optional. Array of post IDs to filter the query by.
257 + * @param int $columns
353 258 *
354 259 * @return string
355 260 */
356 - private function wxr_authors_list( array $post_ids = null ): string {
357 - $result = '';
261 + public function indent( int $columns = 1 ): string {
358 262
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 - }
263 + $output = str_repeat( "\t", $columns );
365 264
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;
265 + return (string) $output;
388 266 }
389 267
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 = '';
268 + /**
269 + * Retrieve terms associated with the specified object IDs and sort them based on term meta.
270 + *
271 + * @param array $post_ids An array of object IDs.
272 + * @return array An array of WP_Term objects sorted based on term meta.
273 + */
274 + public function get_terms(array $post_ids = []) {
275 + // Get the object taxonomies
276 + $taxonomies = get_object_taxonomies($this->args['content']);
399 277
400 - foreach ( $cats as $c ) {
401 - $result .= $this->indent( 2 ) . '<wp:category>' . PHP_EOL;
278 + $terms = [];
402 279
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 );
280 + // Handle glossaries
281 + if ($this->args['content'] == 'glossaries') {
282 + $terms = get_terms([
283 + 'taxonomy' => 'glossaries',
284 + 'include' => $post_ids,
285 + 'hide_empty' => false,
286 + ]);
407 287
408 - $result .= $this->indent( 2 ) . '</wp:category>' . PHP_EOL;
409 - }
288 + if (is_wp_error($terms)) {
289 + return [];
290 + }
410 291
411 - return $result;
412 - }
292 + // Include parent terms for hierarchical glossaries
293 + $parent_terms = [];
294 + foreach ($terms as $term) {
295 + if (!empty($term->parent)) {
296 + $parent = get_term($term->parent, 'glossaries');
297 + if (!is_wp_error($parent)) {
298 + $parent_terms[] = $parent;
299 + }
300 + }
301 + }
413 302
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 = '';
303 + if (!empty($parent_terms)) {
304 + $terms = array_merge($terms, $parent_terms);
305 + $terms = array_unique($terms, SORT_REGULAR);
306 + }
307 + } elseif ($this->args['content'] == 'docs') { // Handle docs
308 + if (isset($this->args['selected_docs']) && !empty($this->args['selected_docs'])) {
309 + if ($this->args['selected_docs'][0] == 'all') {
310 + // Fetch all terms
311 + $terms = get_terms([
312 + 'taxonomy' => $taxonomies,
313 + 'hide_empty' => false,
314 + ]);
315 + } else {
316 + // Fetch terms for specific post IDs
317 + $terms = wp_get_object_terms($this->args['selected_docs'], $taxonomies);
318 + }
319 + }
320 + } elseif (in_array($this->args['content'], ['knowledge_base', 'doc_category'])) {
321 + // Handle both knowledge_base and doc_category cases
322 + $terms_key = $this->args['content'] == 'doc_category' ? 'category_terms' : 'kb_terms';
423 323
424 - foreach ( $tags as $t ) {
425 - $result .= $this->indent( 2 ) . '<wp:tag>' . PHP_EOL;
324 + if (isset($this->args[$terms_key]) && !empty($this->args[$terms_key])) {
325 + $terms_arg = [
326 + 'taxonomy' => $taxonomies,
327 + 'hide_empty' => false,
328 + ];
426 329
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 );
330 + if ($this->args[$terms_key][0] !== 'all') {
331 + $terms_arg['slug'] = $this->args[$terms_key];
332 + }
430 333
431 - $result .= $this->indent( 2 ) . '</wp:tag>' . PHP_EOL;
432 - }
334 + $terms = get_terms($terms_arg);
335 + }
336 + }
433 337
434 - return $result;
435 - }
338 + // Include both parent and child terms for hierarchical structures
339 + if (!empty($terms) && is_array($terms)) {
340 + $additional_terms = [];
436 341
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 = '';
342 + foreach ($terms as $term) {
343 + // Get parent terms
344 + if (!empty($term->parent)) {
345 + $ancestors = get_ancestors($term->term_id, $term->taxonomy, 'taxonomy');
346 + foreach ($ancestors as $ancestor_id) {
347 + $parent = get_term($ancestor_id, $term->taxonomy);
348 + if (!is_wp_error($parent)) {
349 + $additional_terms[] = $parent;
350 + }
351 + }
352 + }
446 353
447 - foreach ( $terms as $t ) {
448 - $result .= $this->indent( 2 ) . '<wp:term>' . PHP_EOL;
354 + // Get child terms
355 + $children = get_term_children($term->term_id, $term->taxonomy);
356 + if (!is_wp_error($children)) {
357 + foreach ($children as $child_id) {
358 + $child = get_term($child_id, $term->taxonomy);
359 + if (!is_wp_error($child)) {
360 + $additional_terms[] = $child;
361 + }
362 + }
363 + }
364 + }
449 365
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 );
366 + if (!empty($additional_terms)) {
367 + $terms = array_merge($terms, $additional_terms);
368 + $terms = array_unique($terms, SORT_REGULAR);
369 + }
370 + }
455 371
456 - $result .= $this->indent( 2 ) . '</wp:term>' . PHP_EOL;
457 - }
372 + // Sort terms if not empty
373 + if (!empty($terms) && is_array($terms)) {
374 + usort($terms, array($this, 'compare_terms_by_meta'));
375 + }
458 376
459 - return $result;
460 - }
377 + return $terms;
378 + }
461 379
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 380 /**
467 - * Return list of posts, by requested `$post_ids`.
381 + * Compare terms based on their associated term meta values.
468 382 *
469 - * @param array $post_ids
470 - *
471 - * @return string
383 + * @param WP_Term $a The first term object.
384 + * @param WP_Term $b The second term object.
385 + * @return int Returns a negative value if $a is less than $b,
386 + * a positive value if $a is greater than $b, or 0 if they are equal.
387 + * Additionally, prioritize sorting terms by taxonomy order,
388 + * with 'doc_category' terms appearing before other taxonomy terms.
472 389 */
473 - private function wxr_posts_list( array $post_ids ): string {
474 - $result = '';
390 + public function compare_terms_by_meta( $a, $b ) {
391 + // Define the order of taxonomies
392 + $taxonomy_order = array(
393 + 'doc_category' => 0,
394 + 'knowledge_base' => 1,
395 + 'doc_tag' => 2,
396 + );
475 397
476 - if ( $post_ids ) {
477 - global $wp_query;
398 + // Get the taxonomy order for terms $a and $b
399 + $order_a = isset( $taxonomy_order[ $a->taxonomy ] ) ? $taxonomy_order[ $a->taxonomy ] : PHP_INT_MAX;
400 + $order_b = isset( $taxonomy_order[ $b->taxonomy ] ) ? $taxonomy_order[ $b->taxonomy ] : PHP_INT_MAX;
478 401
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 - }
402 + // If the taxonomies have different order, sort by order
403 + if ( $order_a !== $order_b ) {
404 + return $order_a - $order_b;
621 405 }
622 406
623 - return $result;
624 - }
407 + // If the taxonomies have the same order, sort by meta value
408 + $taxonomy_order_meta = array(
409 + 'doc_category' => 'doc_category_order',
410 + 'knowledge_base' => 'kb_order'
411 + );
625 412
626 - /**
627 - * Return all navigation menu terms
628 - *
629 - * @return string
630 - */
631 - private function wxr_nav_menu_terms(): string {
632 - $args = [];
413 + if ( isset( $taxonomy_order_meta[ $a->taxonomy ] ) && isset( $taxonomy_order_meta[ $b->taxonomy ] ) ) {
414 + $meta_a = intval( get_term_meta( $a->term_id, $taxonomy_order_meta[ $a->taxonomy ], true ) );
415 + $meta_b = intval( get_term_meta( $b->term_id, $taxonomy_order_meta[ $b->taxonomy ], true ) );
633 416
634 - if( ! empty( $this->nav_menu_terms ) ) {
635 - $args['include'] = $this->nav_menu_terms;
417 + return $meta_a - $meta_b;
636 418 }
637 419
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 - }
420 + return 0; // Default to no sorting if meta keys are not defined
421 + }
768 422 }