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

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