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

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

642 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPDeveloper\BetterDocs\Admin;
3
4 use Error;
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10 #[\AllowDynamicProperties]
11 class CSVExporter {
12 private static $default_args = [
13 'content' => 'docs',
14 'author' => false,
15 'category' => false,
16 'start_date' => false,
17 'end_date' => false,
18 'status' => false,
19 'offset' => 0,
20 'limit' => -1,
21 'meta_query' => [], // If specified `meta_key` then will include all post(s) that have this meta_key.
22 'query_args' => []
23 ];
24
25 /**
26 * @var array
27 */
28 private $args;
29
30 /**
31 * @var wpdb
32 */
33 private $wpdb;
34
35 public function __construct( array $args = [] ) {
36 global $wpdb;
37
38 $this->args = wp_parse_args( $args, self::$default_args );
39
40 $this->wpdb = $wpdb;
41 }
42
43 public function combine_csv_data( $csv_data_array ) {
44 // Combine headers
45 $headers_combined = $csv_data_array[0][0];
46
47 foreach ( $csv_data_array as $csv_data ) {
48 $headers_combined = array_merge( $headers_combined, array_slice( $csv_data[0], 1 ) );
49 }
50
51 $csv_data_combined = [ $headers_combined ];
52
53 // Combine data
54 for ( $i = 1; $i < count( $csv_data_array[0] ); $i++ ) {
55 $combined_row = [];
56 foreach ( $csv_data_array as $csv_data ) {
57 $combined_row = array_merge( $combined_row, array_fill( 0, count( $csv_data_combined[0] ) - count( $combined_row ) ), [ $csv_data[ $i ][0] ], array_slice( $csv_data[ $i ], 1 ) );
58 }
59 $csv_data_combined[] = $combined_row;
60 }
61
62 return $csv_data_combined;
63 }
64
65
66 public function run(): array {
67 $allowed_post_types = [ 'docs', 'betterdocs_faq' ];
68
69 if ( $this->args['content'] === 'glossaries' ) {
70 return $this->handle_glossaries_export();
71 }
72
73 if ( ! in_array( $this->args['content'], $allowed_post_types ) ) {
74 return [];
75 }
76
77 $where = $this->wpdb->prepare(
78 "{$this->wpdb->posts}.post_type = %s",
79 $this->args['content']
80 );
81
82 if ( $this->args['status'] ) {
83 $where .= $this->wpdb->prepare(
84 " AND {$this->wpdb->posts}.post_status = %s",
85 $this->args['status']
86 );
87 } else {
88 $where .= " AND {$this->wpdb->posts}.post_status != 'auto-draft'";
89 }
90
91 if ( ! empty( $this->args['post__in'] ) ) {
92 $post_in = $this->args['post__in'];
93 $ids_placeholder = implode( ', ', array_fill( 0, count( $post_in ), '%d' ) );
94 $where .= $this->wpdb->prepare(
95 " AND {$this->wpdb->posts}.ID IN ($ids_placeholder)",
96 $post_in
97 );
98 }
99
100 $join = '';
101
102 if ( isset( $this->args['category_terms'] ) ) {
103 $join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)";
104 $tax_terms = [];
105
106 // Handle doc categories
107 foreach ( $this->args['category_terms'] as $term_slug ) {
108 $term = get_term_by( 'slug', $term_slug, 'doc_category' );
109 if ( $term ) {
110 $tax_terms[] = $term->term_taxonomy_id;
111 }
112 }
113
114 if ( ! empty( $tax_terms ) ) {
115 $tax_placeholder = implode( ', ', array_fill( 0, count( $tax_terms ), '%d' ) );
116 $where .= $this->wpdb->prepare(
117 " AND {$this->wpdb->term_relationships}.term_taxonomy_id IN ($tax_placeholder)",
118 $tax_terms
119 );
120 }
121 } elseif ( isset( $this->args['kb_terms'] ) ) {
122 $join = "INNER JOIN {$this->wpdb->term_relationships} ON ({$this->wpdb->posts}.ID = {$this->wpdb->term_relationships}.object_id)";
123 $kb_terms = [];
124
125 foreach ( $this->args['kb_terms'] as $term_slug ) {
126 $term = get_term_by( 'slug', $term_slug, 'knowledge_base' );
127 if ( $term ) {
128 $kb_terms[] = $term->term_taxonomy_id;
129 }
130 }
131
132 if ( ! empty( $kb_terms ) ) {
133 $term_placeholder = implode( ', ', array_fill( 0, count( $kb_terms ), '%d' ) );
134 $where .= $this->wpdb->prepare(
135 " AND {$this->wpdb->term_relationships}.term_taxonomy_id IN ($term_placeholder)",
136 $kb_terms
137 );
138 }
139 }
140
141 if ( $this->args['author'] ) {
142 $where .= $this->wpdb->prepare(
143 " AND {$this->wpdb->posts}.post_author = %d",
144 $this->args['author']
145 );
146 }
147
148 if ( $this->args['start_date'] ) {
149 $where .= $this->wpdb->prepare(
150 " AND {$this->wpdb->posts}.post_date >= %s",
151 gmdate( 'Y-m-d', strtotime( $this->args['start_date'] ) )
152 );
153 }
154
155 if ( $this->args['end_date'] ) {
156 $where .= $this->wpdb->prepare(
157 " AND {$this->wpdb->posts}.post_date < %s",
158 gmdate( 'Y-m-d', strtotime( '+1 month', strtotime( $this->args['end_date'] ) ) )
159 );
160 }
161
162 if ( ! empty( $this->args['meta_query'] ) ) {
163 $meta_query = new \WP_Meta_Query( $this->args['meta_query'] );
164 $query_clauses = $meta_query->get_sql( 'post', $this->wpdb->posts, 'ID' );
165
166 $join .= ' ' . $query_clauses['join'];
167 $where .= ' ' . $query_clauses['where'];
168 }
169
170 // Get post IDs
171 $post_ids = $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->posts} $join WHERE $where" );
172
173 // Add FAQ post IDs if include_faq is true
174 if ( ! empty( $this->args['include_faq'] ) ) {
175 $faq_ids = get_posts(
176 [
177 'post_type' => 'betterdocs_faq',
178 'posts_per_page' => -1,
179 'fields' => 'ids',
180 'post_status' => 'publish'
181 ]
182 );
183 $post_ids = array_merge( $post_ids, $faq_ids );
184 }
185
186 if ( empty( $post_ids ) ) {
187 return [
188 'success' => false,
189 'message' => 'No posts found matching the criteria.'
190 ];
191 }
192
193 // Get posts data
194 $posts = array_map( 'get_post', $post_ids );
195
196 // Prepare CSV data
197 $csv_data_terms = $this->get_terms_csv_data( $post_ids );
198 $csv_data_author = $this->authors_list( $post_ids );
199 $csv_data_posts = $this->get_posts_csv_data( $posts );
200
201 // Combine headers
202 $headers_combined = array_merge(
203 $csv_data_posts[0],
204 array_slice( $csv_data_author[0], 1 ),
205 array_slice( $csv_data_terms[0], 1 )
206 );
207
208 // Initialize the combined array with headers
209 $csv_data_combined = [ $headers_combined ];
210
211 $author_start_index = count( $csv_data_posts[0] );
212 $terms_start_index = $author_start_index + count( $csv_data_author[0] ) - 1;
213
214 // Author rows
215 for ( $i = 1; $i < count( $csv_data_author ); $i++ ) {
216 $combined_row = array_merge(
217 [ $csv_data_author[ $i ][0] ],
218 array_fill( 1, $author_start_index - 1, '' ),
219 array_slice( $csv_data_author[ $i ], 1 )
220 );
221 $csv_data_combined[] = $combined_row;
222 }
223
224 // Docs post rows
225 for ( $i = 1; $i < count( $csv_data_posts ); $i++ ) {
226 if ( $csv_data_posts[ $i ][0] === 'FAQ' ) {
227 continue;
228 }
229 $combined_row = array_merge(
230 $csv_data_posts[ $i ],
231 array_fill( 0, count( $headers_combined ) - count( $csv_data_posts[ $i ] ), '' )
232 );
233 $csv_data_combined[] = $combined_row;
234 }
235
236 // Term rows just before FAQ rows so category IDs can be resolved on import
237 for ( $i = 1; $i < count( $csv_data_terms ); $i++ ) {
238 $combined_row = array_merge(
239 [ $csv_data_terms[ $i ][0] ],
240 array_fill( 1, $terms_start_index - 1, '' ),
241 array_slice( $csv_data_terms[ $i ], 1 )
242 );
243 $csv_data_combined[] = $combined_row;
244 }
245
246 // FAQ post rows last
247 for ( $i = 1; $i < count( $csv_data_posts ); $i++ ) {
248 if ( $csv_data_posts[ $i ][0] !== 'FAQ' ) {
249 continue;
250 }
251 $combined_row = array_merge(
252 $csv_data_posts[ $i ],
253 array_fill( 0, count( $headers_combined ) - count( $csv_data_posts[ $i ] ), '' )
254 );
255 $csv_data_combined[] = $combined_row;
256 }
257
258 $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.csv';
259 $csv_content = $this->generate_csv( $csv_data_combined );
260
261 return [
262 'success' => true,
263 'data' => [
264 'filename' => $filename,
265 'filetype' => 'text/csv',
266 'download' => $csv_content,
267 ]
268 ];
269 }
270
271 /**
272 * Retrieve terms associated with the specified object IDs and sort them based on term meta.
273 *
274 * @param array $post_ids An array of object IDs.
275 * @return array An array of WP_Term objects sorted based on term meta.
276 */
277 private function get_terms( array $post_ids, $include_faq = false ) {
278 $post_types = [
279 'docs'
280 ];
281
282 if ( $include_faq ) {
283 array_push( $post_types, 'betterdocs_faq' );
284 }
285
286 // Get the object taxonomies
287 $taxonomies = get_object_taxonomies( $post_types );
288
289 // Get the object terms with parent terms coming before their child terms
290 $terms = wp_get_object_terms( $post_ids, $taxonomies );
291
292 usort( $terms, array( $this, 'compare_terms_by_meta' ) );
293
294 return $terms;
295 }
296
297 /**
298 * Compare terms based on their associated term meta values.
299 *
300 * @param WP_Term $a The first term object.
301 * @param WP_Term $b The second term object.
302 * @return int Returns a negative value if $a is less than $b,
303 * a positive value if $a is greater than $b, or 0 if they are equal.
304 * Additionally, prioritize sorting terms by taxonomy order,
305 * with 'doc_category' terms appearing before other taxonomy terms.
306 */
307 public function compare_terms_by_meta( $a, $b ) {
308 // Define the order of taxonomies
309 $taxonomy_order = array(
310 'doc_category' => 0,
311 'knowledge_base' => 1,
312 'doc_tag' => 2,
313 );
314
315 // Get the taxonomy order for terms $a and $b
316 $order_a = isset( $taxonomy_order[ $a->taxonomy ] ) ? $taxonomy_order[ $a->taxonomy ] : PHP_INT_MAX;
317 $order_b = isset( $taxonomy_order[ $b->taxonomy ] ) ? $taxonomy_order[ $b->taxonomy ] : PHP_INT_MAX;
318
319 // If the taxonomies have different order, sort by order
320 if ( $order_a !== $order_b ) {
321 return $order_a - $order_b;
322 }
323
324 // If the taxonomies have the same order, sort by meta value
325 $taxonomy_order_meta = array(
326 'doc_category' => 'doc_category_order',
327 'knowledge_base' => 'kb_order'
328 );
329
330 if ( isset( $taxonomy_order_meta[ $a->taxonomy ] ) && isset( $taxonomy_order_meta[ $b->taxonomy ] ) ) {
331 $meta_a = intval( get_term_meta( $a->term_id, $taxonomy_order_meta[ $a->taxonomy ], true ) );
332 $meta_b = intval( get_term_meta( $b->term_id, $taxonomy_order_meta[ $b->taxonomy ], true ) );
333
334 return $meta_a - $meta_b;
335 }
336
337 return 0; // Default to no sorting if meta keys are not defined
338 }
339
340 /**
341 * Sorting function for sorting term data.
342 *
343 * @param array $a The first array to compare.
344 * @param array $b The second array to compare.
345 *
346 * @return int Returns an integer less than, equal to, or greater than zero if the first array is considered
347 * to be respectively less than, equal to, or greater than the second.
348 */
349 public function sort_terms( $a, $b ) {
350 // Compare parent values
351 $parentComparison = strcmp( $a[7], $b[7] );
352
353 // If parent values are equal, compare Term ID values
354 return ( $parentComparison === 0 ) ? strcmp( $a[2], $b[2] ) : $parentComparison;
355 }
356
357 private function get_terms_csv_data( $post_ids ) {
358 $csv_data_terms = [];
359
360 // Add CSV headers for terms
361 $csv_data_terms[] = [
362 'Type',
363 'Taxonomy',
364 'Term ID',
365 'Term name',
366 'Term slug',
367 'Term group',
368 'Term description',
369 'Term parent',
370 'Assigned Docs',
371 'Assigned KBs',
372 'Doc Category order',
373 'KB order', // Add additional term meta headers here
374 ];
375
376 if ( $this->args['content'] == 'glossaries' ) {
377 $terms = get_terms(
378 [
379 'taxonomy' => 'glossaries',
380 'hide_empty' => false,
381 ]
382 );
383 } else {
384 $terms = $this->get_terms( $post_ids, $this->args['include_faq'] );
385 }
386 foreach ( $terms as $term ) {
387 $term_meta = '';
388
389 // Add term meta based on taxonomy
390 switch ( $term->taxonomy ) {
391 case 'doc_category':
392 $doc_category_knowledge_base = maybe_unserialize( get_term_meta( $term->term_id, 'doc_category_knowledge_base', true ) );
393 if ( is_array( $doc_category_knowledge_base ) && $doc_category_knowledge_base !== false ) {
394 $doc_category_knowledge_base = implode( ', ', array_filter( $doc_category_knowledge_base ) );
395 } else {
396 $doc_category_knowledge_base = '';
397 }
398
399 $term_meta = [
400 '_docs_order' => get_term_meta( $term->term_id, '_docs_order', true ),
401 'doc_category_knowledge_base' => $doc_category_knowledge_base,
402 'doc_category_order' => get_term_meta( $term->term_id, 'doc_category_order', true ),
403 ];
404 break;
405
406 case 'knowledge_base':
407 $term_meta = [
408 'kb_order' => get_term_meta( $term->term_id, 'kb_order', true ),
409 ];
410 break;
411 }
412
413 $parent = $term->parent ? get_term_by( 'id', $term->parent, $term->taxonomy ) : '';
414 // Add CSV row for term
415 $csv_data_terms[] = [
416 'Term',
417 $term->taxonomy,
418 $term->term_id,
419 $term->name,
420 $term->slug,
421 $term->term_group,
422 $term->description,
423 $parent ? $parent->slug : '',
424 isset( $term_meta['_docs_order'] ) ? $term_meta['_docs_order'] : '',
425 isset( $term_meta['doc_category_knowledge_base'] ) ? $term_meta['doc_category_knowledge_base'] : '',
426 isset( $term_meta['doc_category_order'] ) ? $term_meta['doc_category_order'] : '',
427 isset( $term_meta['kb_order'] ) ? $term_meta['kb_order'] : '',
428 ];
429 }
430
431 return $csv_data_terms;
432 }
433
434 private function handle_glossaries_export(): array {
435 if ( isset( $this->args['glossary_terms'] ) && ( count( $this->args['glossary_terms'] ) > 0 ) ) {
436 $glossary_term_ids = [];
437 foreach ( $this->args['glossary_terms'] as $glossary_slug ) {
438 $term_object = get_term_by( 'slug', $glossary_slug, 'glossaries' );
439 if ( isset( $term_object->term_id ) && ! empty( $term_object->term_id ) ) {
440 array_push( $glossary_term_ids, $term_object->term_id );
441 }
442 }
443 } else {
444 $glossary_term_ids = $this->wpdb->get_col( "SELECT term_id from {$this->wpdb->term_taxonomy} where taxonomy='{$this->args['content']}';" );
445 }
446
447 $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.csv';
448 $csv_data_combined = $this->get_glossaries_csv_data( $glossary_term_ids );
449 $csv_content = $this->generate_csv( $csv_data_combined );
450
451 return [
452 'success' => true,
453 'data' => [
454 'filename' => $filename,
455 'filetype' => 'text/csv',
456 'download' => $csv_content,
457 ],
458 ];
459 }
460
461 private function get_glossaries_csv_data( $post_ids ) {
462 $csv_data_terms = [];
463
464 if ( $this->args['content'] == 'glossaries' ) {
465 $terms = get_terms(
466 [
467 'taxonomy' => 'glossaries',
468 'hide_empty' => false,
469 ]
470 );
471 } else {
472 $terms = $this->get_terms( $post_ids, $this->args['include_faq'] );
473 }
474
475 if ( is_wp_error( $terms ) ) {
476 return $csv_data_terms;
477 }
478
479 // Add CSV headers for terms
480 $csv_data_terms[] = [
481 'Taxonomy',
482 'Term ID',
483 'Term name',
484 'Term slug',
485 'Term group',
486 'Term description'
487 ];
488
489 foreach ( $terms as $term ) {
490 // Add CSV row for term
491 $csv_data_terms[] = [
492 $term->taxonomy,
493 $term->term_id,
494 $term->name,
495 $term->slug,
496 $term->term_group,
497 get_term_meta( $term->term_id, 'glossary_term_description', true )
498 ];
499 }
500
501 return $csv_data_terms;
502 }
503
504 /**
505 * Return list of authors with posts.
506 *
507 * @param int[] $post_ids Optional. Array of post IDs to filter the query by.
508 *
509 * @return string
510 */
511 private function authors_list( $post_ids ) {
512 $authors = [];
513
514 // Add CSV headers for terms
515 $authors[] = [
516 'Type',
517 'Author id',
518 'Author login',
519 'Author email',
520 'Author display name',
521 'Author first name',
522 'Author last name'
523 ];
524
525 if ( ! empty( $post_ids ) ) {
526 $post_ids = array_map( 'absint', $post_ids );
527 $and = 'AND ID IN ( ' . implode( ', ', $post_ids ) . ')';
528 } else {
529 $and = '';
530 }
531 $authors_data = [];
532 $results = $this->wpdb->get_results( "SELECT DISTINCT post_author FROM {$this->wpdb->posts} WHERE post_status != 'auto-draft' $and" );// phpcs:ignore
533 foreach ( (array) $results as $r ) {
534 $authors_data[] = get_userdata( $r->post_author );
535 }
536
537 $authors_data = array_filter( $authors_data );
538
539 foreach ( $authors_data as $author ) {
540 $authors[] = [
541 'Author',
542 $author->ID,
543 $author->user_login,
544 $author->user_email,
545 $author->display_name,
546 $author->first_name,
547 $author->last_name
548 ];
549 }
550
551 return $authors;
552 }
553
554 private function get_posts_csv_data( $posts ) {
555 $csv_data_posts = [];
556
557 // Add CSV headers for posts
558 $csv_data_posts[] = [
559 'Type',
560 'Docs ID',
561 'Docs author',
562 'Docs date',
563 'Docs date gmt',
564 'Docs title',
565 'Docs content',
566 'Docs excerpt',
567 'Docs status',
568 'Docs password',
569 'Docs slug',
570 'Docs modified date',
571 'Docs modified date gmt',
572 'Docs parent',
573 'Docs menu order',
574 'Docs mime type',
575 'Comment count',
576 'Doc Categories',
577 'Doc Tags',
578 'Knowledge Bases',
579 'Docs attachement url',
580 'Docs attachement ID'
581 ];
582
583 foreach ( $posts as $post ) {
584 $attachment_id = get_post_thumbnail_id( $post->ID );
585 $attachment_url = get_the_post_thumbnail_url( $post->ID );
586 // Add CSV row for post
587 $csv_data_posts[] = [
588 $post->post_type == 'betterdocs_faq' ? 'FAQ' : 'Docs',
589 $post->ID,
590 $post->post_author,
591 $post->post_date,
592 $post->post_date_gmt,
593 $post->post_title,
594 $post->post_content,
595 $post->post_excerpt,
596 $post->post_status,
597 $post->post_password,
598 $post->post_name,
599 $post->post_modified,
600 $post->post_modified_gmt,
601 $post->post_parent,
602 $post->menu_order,
603 $post->post_mime_type,
604 $post->comment_count,
605 $this->get_term_ids( $post->ID, $post->post_type === 'betterdocs_faq' ? 'betterdocs_faq_category' : 'doc_category' ),
606 $this->get_term_ids( $post->ID, 'doc_tag' ),
607 $this->get_term_ids( $post->ID, 'knowledge_base' ),
608 $attachment_url ? $attachment_url : '',
609 $attachment_id ? $attachment_id : ''
610 ];
611 }
612
613 return $csv_data_posts;
614 }
615
616 public function get_term_ids( $post_id, $taxonomy ) {
617 $terms = get_the_terms( $post_id, $taxonomy );
618
619 if ( $terms && ! is_wp_error( $terms ) ) {
620 $term_ids = wp_list_pluck( $terms, 'term_id' );
621 return implode( ', ', $term_ids );
622 }
623
624 return '';
625 }
626
627 private function generate_csv( array $data ): string {
628 ob_start();
629
630 $output = fopen( 'php://output', 'w' );
631
632 // Add CSV rows
633 foreach ( $data as $row ) {
634 fputcsv( $output, $row );
635 }
636
637 fclose( $output );
638
639 return ob_get_clean();
640 }
641 }
642