PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.9.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.9.1
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 3.9.1, at includes/Admin/CSVExporter.php

626 lines 16.9 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 // Combine posts data
212 for ( $i = 1; $i < count( $csv_data_posts ); $i++ ) {
213 $combined_row = array_merge(
214 $csv_data_posts[ $i ],
215 array_fill( 0, count( $headers_combined ) - count( $csv_data_posts[ $i ] ), '' )
216 );
217 $csv_data_combined[] = $combined_row;
218 }
219
220 // Combine author data
221 $author_start_index = count( $csv_data_posts[0] );
222 for ( $i = 1; $i < count( $csv_data_author ); $i++ ) {
223 $combined_row = array_merge(
224 [ $csv_data_author[ $i ][0] ],
225 array_fill( 1, $author_start_index - 1, '' ),
226 array_slice( $csv_data_author[ $i ], 1 )
227 );
228 $csv_data_combined[] = $combined_row;
229 }
230
231 // Combine terms data
232 $terms_start_index = $author_start_index + count( $csv_data_author[0] ) - 1;
233 for ( $i = 1; $i < count( $csv_data_terms ); $i++ ) {
234 $combined_row = array_merge(
235 [ $csv_data_terms[ $i ][0] ],
236 array_fill( 1, $terms_start_index - 1, '' ),
237 array_slice( $csv_data_terms[ $i ], 1 )
238 );
239 $csv_data_combined[] = $combined_row;
240 }
241
242 $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.csv';
243 $csv_content = $this->generate_csv( $csv_data_combined );
244
245 return [
246 'success' => true,
247 'data' => [
248 'filename' => $filename,
249 'filetype' => 'text/csv',
250 'download' => $csv_content,
251 ]
252 ];
253 }
254
255 /**
256 * Retrieve terms associated with the specified object IDs and sort them based on term meta.
257 *
258 * @param array $post_ids An array of object IDs.
259 * @return array An array of WP_Term objects sorted based on term meta.
260 */
261 private function get_terms( array $post_ids, $include_faq = false ) {
262 $post_types = [
263 'docs'
264 ];
265
266 if ( $include_faq ) {
267 array_push( $post_types, 'betterdocs_faq' );
268 }
269
270 // Get the object taxonomies
271 $taxonomies = get_object_taxonomies( $post_types );
272
273 // Get the object terms with parent terms coming before their child terms
274 $terms = wp_get_object_terms( $post_ids, $taxonomies );
275
276 usort( $terms, array( $this, 'compare_terms_by_meta' ) );
277
278 return $terms;
279 }
280
281 /**
282 * Compare terms based on their associated term meta values.
283 *
284 * @param WP_Term $a The first term object.
285 * @param WP_Term $b The second term object.
286 * @return int Returns a negative value if $a is less than $b,
287 * a positive value if $a is greater than $b, or 0 if they are equal.
288 * Additionally, prioritize sorting terms by taxonomy order,
289 * with 'doc_category' terms appearing before other taxonomy terms.
290 */
291 public function compare_terms_by_meta( $a, $b ) {
292 // Define the order of taxonomies
293 $taxonomy_order = array(
294 'doc_category' => 0,
295 'knowledge_base' => 1,
296 'doc_tag' => 2,
297 );
298
299 // Get the taxonomy order for terms $a and $b
300 $order_a = isset( $taxonomy_order[ $a->taxonomy ] ) ? $taxonomy_order[ $a->taxonomy ] : PHP_INT_MAX;
301 $order_b = isset( $taxonomy_order[ $b->taxonomy ] ) ? $taxonomy_order[ $b->taxonomy ] : PHP_INT_MAX;
302
303 // If the taxonomies have different order, sort by order
304 if ( $order_a !== $order_b ) {
305 return $order_a - $order_b;
306 }
307
308 // If the taxonomies have the same order, sort by meta value
309 $taxonomy_order_meta = array(
310 'doc_category' => 'doc_category_order',
311 'knowledge_base' => 'kb_order'
312 );
313
314 if ( isset( $taxonomy_order_meta[ $a->taxonomy ] ) && isset( $taxonomy_order_meta[ $b->taxonomy ] ) ) {
315 $meta_a = intval( get_term_meta( $a->term_id, $taxonomy_order_meta[ $a->taxonomy ], true ) );
316 $meta_b = intval( get_term_meta( $b->term_id, $taxonomy_order_meta[ $b->taxonomy ], true ) );
317
318 return $meta_a - $meta_b;
319 }
320
321 return 0; // Default to no sorting if meta keys are not defined
322 }
323
324 /**
325 * Sorting function for sorting term data.
326 *
327 * @param array $a The first array to compare.
328 * @param array $b The second array to compare.
329 *
330 * @return int Returns an integer less than, equal to, or greater than zero if the first array is considered
331 * to be respectively less than, equal to, or greater than the second.
332 */
333 public function sort_terms( $a, $b ) {
334 // Compare parent values
335 $parentComparison = strcmp( $a[7], $b[7] );
336
337 // If parent values are equal, compare Term ID values
338 return ( $parentComparison === 0 ) ? strcmp( $a[2], $b[2] ) : $parentComparison;
339 }
340
341 private function get_terms_csv_data( $post_ids ) {
342 $csv_data_terms = [];
343
344 // Add CSV headers for terms
345 $csv_data_terms[] = [
346 'Type',
347 'Taxonomy',
348 'Term ID',
349 'Term name',
350 'Term slug',
351 'Term group',
352 'Term description',
353 'Term parent',
354 'Assigned Docs',
355 'Assigned KBs',
356 'Doc Category order',
357 'KB order', // Add additional term meta headers here
358 ];
359
360 if ( $this->args['content'] == 'glossaries' ) {
361 $terms = get_terms(
362 [
363 'taxonomy' => 'glossaries',
364 'hide_empty' => false,
365 ]
366 );
367 } else {
368 $terms = $this->get_terms( $post_ids, $this->args['include_faq'] );
369 }
370 foreach ( $terms as $term ) {
371 $term_meta = '';
372
373 // Add term meta based on taxonomy
374 switch ( $term->taxonomy ) {
375 case 'doc_category':
376 $doc_category_knowledge_base = maybe_unserialize( get_term_meta( $term->term_id, 'doc_category_knowledge_base', true ) );
377 if ( is_array( $doc_category_knowledge_base ) && $doc_category_knowledge_base !== false ) {
378 $doc_category_knowledge_base = implode( ', ', array_filter( $doc_category_knowledge_base ) );
379 } else {
380 $doc_category_knowledge_base = '';
381 }
382
383 $term_meta = [
384 '_docs_order' => get_term_meta( $term->term_id, '_docs_order', true ),
385 'doc_category_knowledge_base' => $doc_category_knowledge_base,
386 'doc_category_order' => get_term_meta( $term->term_id, 'doc_category_order', true ),
387 ];
388 break;
389
390 case 'knowledge_base':
391 $term_meta = [
392 'kb_order' => get_term_meta( $term->term_id, 'kb_order', true ),
393 ];
394 break;
395 }
396
397 $parent = $term->parent ? get_term_by( 'id', $term->parent, $term->taxonomy ) : '';
398 // Add CSV row for term
399 $csv_data_terms[] = [
400 'Term',
401 $term->taxonomy,
402 $term->term_id,
403 $term->name,
404 $term->slug,
405 $term->term_group,
406 $term->description,
407 $parent ? $parent->slug : '',
408 isset( $term_meta['_docs_order'] ) ? $term_meta['_docs_order'] : '',
409 isset( $term_meta['doc_category_knowledge_base'] ) ? $term_meta['doc_category_knowledge_base'] : '',
410 isset( $term_meta['doc_category_order'] ) ? $term_meta['doc_category_order'] : '',
411 isset( $term_meta['kb_order'] ) ? $term_meta['kb_order'] : '',
412 ];
413 }
414
415 return $csv_data_terms;
416 }
417
418 private function handle_glossaries_export(): array {
419 if ( isset( $this->args['glossary_terms'] ) && ( count( $this->args['glossary_terms'] ) > 0 ) ) {
420 $glossary_term_ids = [];
421 foreach ( $this->args['glossary_terms'] as $glossary_slug ) {
422 $term_object = get_term_by( 'slug', $glossary_slug, 'glossaries' );
423 if ( isset( $term_object->term_id ) && ! empty( $term_object->term_id ) ) {
424 array_push( $glossary_term_ids, $term_object->term_id );
425 }
426 }
427 } else {
428 $glossary_term_ids = $this->wpdb->get_col( "SELECT term_id from {$this->wpdb->term_taxonomy} where taxonomy='{$this->args['content']}';" );
429 }
430
431 $filename = 'betterdocs.' . date( 'Y-m-d' ) . '.csv';
432 $csv_data_combined = $this->get_glossaries_csv_data( $glossary_term_ids );
433 $csv_content = $this->generate_csv( $csv_data_combined );
434
435 return [
436 'success' => true,
437 'data' => [
438 'filename' => $filename,
439 'filetype' => 'text/csv',
440 'download' => $csv_content,
441 ],
442 ];
443 }
444
445 private function get_glossaries_csv_data( $post_ids ) {
446 $csv_data_terms = [];
447
448 if ( $this->args['content'] == 'glossaries' ) {
449 $terms = get_terms(
450 [
451 'taxonomy' => 'glossaries',
452 'hide_empty' => false,
453 ]
454 );
455 } else {
456 $terms = $this->get_terms( $post_ids, $this->args['include_faq'] );
457 }
458
459 if ( is_wp_error( $terms ) ) {
460 return $csv_data_terms;
461 }
462
463 // Add CSV headers for terms
464 $csv_data_terms[] = [
465 'Taxonomy',
466 'Term ID',
467 'Term name',
468 'Term slug',
469 'Term group',
470 'Term description'
471 ];
472
473 foreach ( $terms as $term ) {
474 // Add CSV row for term
475 $csv_data_terms[] = [
476 $term->taxonomy,
477 $term->term_id,
478 $term->name,
479 $term->slug,
480 $term->term_group,
481 get_term_meta( $term->term_id, 'glossary_term_description', true )
482 ];
483 }
484
485 return $csv_data_terms;
486 }
487
488 /**
489 * Return list of authors with posts.
490 *
491 * @param int[] $post_ids Optional. Array of post IDs to filter the query by.
492 *
493 * @return string
494 */
495 private function authors_list( $post_ids ) {
496 $authors = [];
497
498 // Add CSV headers for terms
499 $authors[] = [
500 'Type',
501 'Author id',
502 'Author login',
503 'Author email',
504 'Author display name',
505 'Author first name',
506 'Author last name'
507 ];
508
509 if ( ! empty( $post_ids ) ) {
510 $post_ids = array_map( 'absint', $post_ids );
511 $and = 'AND ID IN ( ' . implode( ', ', $post_ids ) . ')';
512 } else {
513 $and = '';
514 }
515 $authors_data = [];
516 $results = $this->wpdb->get_results( "SELECT DISTINCT post_author FROM {$this->wpdb->posts} WHERE post_status != 'auto-draft' $and" );// phpcs:ignore
517 foreach ( (array) $results as $r ) {
518 $authors_data[] = get_userdata( $r->post_author );
519 }
520
521 $authors_data = array_filter( $authors_data );
522
523 foreach ( $authors_data as $author ) {
524 $authors[] = [
525 'Author',
526 $author->ID,
527 $author->user_login,
528 $author->user_email,
529 $author->display_name,
530 $author->first_name,
531 $author->last_name
532 ];
533 }
534
535 return $authors;
536 }
537
538 private function get_posts_csv_data( $posts ) {
539 $csv_data_posts = [];
540
541 // Add CSV headers for posts
542 $csv_data_posts[] = [
543 'Type',
544 'Docs ID',
545 'Docs author',
546 'Docs date',
547 'Docs date gmt',
548 'Docs title',
549 'Docs content',
550 'Docs excerpt',
551 'Docs status',
552 'Docs password',
553 'Docs slug',
554 'Docs modified date',
555 'Docs modified date gmt',
556 'Docs parent',
557 'Docs menu order',
558 'Docs mime type',
559 'Comment count',
560 'Doc Categories',
561 'Doc Tags',
562 'Knowledge Bases',
563 'Docs attachement url',
564 'Docs attachement ID'
565 ];
566
567 foreach ( $posts as $post ) {
568 $attachment_id = get_post_thumbnail_id( $post->ID );
569 $attachment_url = get_the_post_thumbnail_url( $post->ID );
570 // Add CSV row for post
571 $csv_data_posts[] = [
572 $post->post_type == 'betterdocs_faq' ? 'FAQ' : 'Docs',
573 $post->ID,
574 $post->post_author,
575 $post->post_date,
576 $post->post_date_gmt,
577 $post->post_title,
578 $post->post_content,
579 $post->post_excerpt,
580 $post->post_status,
581 $post->post_password,
582 $post->post_name,
583 $post->post_modified,
584 $post->post_modified_gmt,
585 $post->post_parent,
586 $post->menu_order,
587 $post->post_mime_type,
588 $post->comment_count,
589 $this->get_term_ids( $post->ID, 'doc_category' ),
590 $this->get_term_ids( $post->ID, 'doc_tag' ),
591 $this->get_term_ids( $post->ID, 'knowledge_base' ),
592 $attachment_url ? $attachment_url : '',
593 $attachment_id ? $attachment_id : ''
594 ];
595 }
596
597 return $csv_data_posts;
598 }
599
600 public function get_term_ids( $post_id, $taxonomy ) {
601 $terms = get_the_terms( $post_id, $taxonomy );
602
603 if ( $terms && ! is_wp_error( $terms ) ) {
604 $term_ids = wp_list_pluck( $terms, 'term_id' );
605 return implode( ', ', $term_ids );
606 }
607
608 return '';
609 }
610
611 private function generate_csv( array $data ): string {
612 ob_start();
613
614 $output = fopen( 'php://output', 'w' );
615
616 // Add CSV rows
617 foreach ( $data as $row ) {
618 fputcsv( $output, $row );
619 }
620
621 fclose( $output );
622
623 return ob_get_clean();
624 }
625 }
626