PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.5.4
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.5.4
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / includes / admin / ai-assistant / models / class-wpsc-rag-training-file.php

class-wpsc-rag-training-file.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.5.4, at includes/admin/ai-assistant/models/class-wpsc-rag-training-file.php

609 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly!
4 }
5
6 if ( ! class_exists( 'WPSC_RAG_Training_File' ) ) :
7
8 final class WPSC_RAG_Training_File {
9
10 /**
11 * Object data in key => val pair.
12 *
13 * @var array
14 */
15 private $data = array();
16
17 /**
18 * Set whether or not current object properties modified
19 *
20 * @var boolean
21 */
22 private $is_modified = false;
23
24 /**
25 * Schema for this model
26 *
27 * @var array
28 */
29 public static $schema;
30
31 /**
32 * Prevent fields to modify
33 *
34 * @var array
35 */
36 public static $prevent_modify;
37
38 /**
39 * DB object caching
40 *
41 * @var array
42 */
43 private static $cache = array();
44
45 /**
46 * Initialize this class
47 *
48 * @return void
49 */
50 public static function init() {
51
52 // Apply schema for this model.
53 add_action( 'init', array( __CLASS__, 'apply_schema' ), 2 );
54
55 // Get object of this class.
56 add_filter( 'wpsc_load_ref_classes', array( __CLASS__, 'load_ref_class' ) );
57 }
58
59 /**
60 * Apply schema for this model
61 *
62 * @return void
63 */
64 public static function apply_schema() {
65
66 $schema = array(
67 'id' => array(
68 'has_ref' => false,
69 'ref_class' => '',
70 'has_multiple_val' => false,
71 ),
72 'status' => array(
73 'has_ref' => false,
74 'ref_class' => '',
75 'has_multiple_val' => false,
76 ),
77 'provider' => array(
78 'has_ref' => false,
79 'ref_class' => '',
80 'has_multiple_val' => false,
81 ),
82 'source' => array(
83 'has_ref' => false,
84 'ref_class' => '',
85 'has_multiple_val' => false,
86 ),
87 'source_id' => array(
88 'has_ref' => false,
89 'ref_class' => '',
90 'has_multiple_val' => false,
91 ),
92 'doc_source' => array(
93 'has_ref' => false,
94 'ref_class' => '',
95 'has_multiple_val' => false,
96 ),
97 'name' => array(
98 'has_ref' => false,
99 'ref_class' => '',
100 'has_multiple_val' => false,
101 ),
102 'file_path' => array(
103 'has_ref' => false,
104 'ref_class' => '',
105 'has_multiple_val' => false,
106 ),
107 'provider_file_id' => array(
108 'has_ref' => false,
109 'ref_class' => '',
110 'has_multiple_val' => false,
111 ),
112 'meta_data' => array(
113 'has_ref' => false,
114 'ref_class' => '',
115 'has_multiple_val' => false,
116 ),
117 'post_updated_on' => array(
118 'has_ref' => true,
119 'ref_class' => 'datetime',
120 'has_multiple_val' => false,
121 ),
122 'date_updated' => array(
123 'has_ref' => true,
124 'ref_class' => 'datetime',
125 'has_multiple_val' => false,
126 ),
127 'date_created' => array(
128 'has_ref' => true,
129 'ref_class' => 'datetime',
130 'has_multiple_val' => false,
131 ),
132 );
133
134 self::$schema = $schema;
135
136 // Prevent modify.
137 $prevent_modify = array( 'id' );
138 self::$prevent_modify = apply_filters( 'wpsc_ai_training_prevent_modify', $prevent_modify );
139 }
140
141 /**
142 * Constructor function
143 *
144 * @param int $id - optional record id.
145 */
146 public function __construct( $id = 0 ) {
147
148 global $wpdb;
149
150 $id = intval( $id );
151
152 if ( isset( self::$cache[ $id ] ) ) {
153 $this->data = self::$cache[ $id ]->data;
154 return;
155 }
156
157 if ( $id > 0 ) {
158
159 $ai_training = $wpdb->get_row(
160 $wpdb->prepare(
161 "SELECT * FROM {$wpdb->prefix}psmsc_ai_training WHERE id = %d",
162 $id
163 ),
164 ARRAY_A
165 );
166 if ( ! is_array( $ai_training ) ) {
167 return;
168 }
169
170 foreach ( $ai_training as $key => $val ) {
171 $this->data[ $key ] = $val !== null ? $val : '';
172 }
173
174 self::$cache[ $id ] = $this;
175 }
176 }
177
178 /**
179 * Magic get function to use with object arrow function
180 *
181 * @param mixed $var_name - (Required) property slug.
182 * @return mixed
183 */
184 public function __get( $var_name ) {
185
186 if ( ! isset( $this->data[ $var_name ] ) ||
187 $this->data[ $var_name ] == null ||
188 $this->data[ $var_name ] == ''
189 ) {
190 return self::$schema[ $var_name ]['has_multiple_val'] ? array() : '';
191 }
192
193 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
194
195 $response = array();
196 $values = $this->data[ $var_name ] ? explode( '|', $this->data[ $var_name ] ) : array();
197 foreach ( $values as $val ) {
198 $response[] = self::$schema[ $var_name ]['has_ref'] ?
199 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $val ) :
200 $val;
201 }
202 return $response;
203
204 } else {
205
206 return self::$schema[ $var_name ]['has_ref'] && $this->data[ $var_name ] ?
207 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $this->data[ $var_name ] ) :
208 $this->data[ $var_name ];
209 }
210 }
211
212 /**
213 * Magic function to use setting object field with arrow function
214 *
215 * @param mixed $var_name - (Required) property slug.
216 * @param mixed $value - (Required) value to set for a property.
217 * @return void
218 */
219 public function __set( $var_name, $value ) {
220
221 if (
222 ! isset( $this->data[ $var_name ] ) ||
223 in_array( $var_name, self::$prevent_modify )
224 ) {
225 return;
226 }
227
228 $data_val = '';
229 if ( self::$schema[ $var_name ]['has_multiple_val'] && is_array( $value ) ) {
230
231 $data_vals = array_map(
232 fn( $val ) => is_object( $val ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $val ) : $val,
233 $value
234 );
235
236 $data_val = $data_vals ? implode( '|', $data_vals ) : '';
237
238 } else {
239
240 $data_val = is_object( $value ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $value ) : $value;
241 }
242
243 if ( $this->data[ $var_name ] == $data_val ) {
244 return;
245 }
246
247 $this->data[ $var_name ] = $data_val;
248 $this->is_modified = true;
249 }
250
251 /**
252 * Save changes made
253 *
254 * @return boolean
255 */
256 public function save() {
257
258 global $wpdb;
259
260 if ( ! $this->is_modified ) {
261 return true;
262 }
263
264 if ( empty( $this->data['id'] ) ) {
265 return false;
266 }
267
268 // Stamp date_updated on every persisted change so staleness checks
269 // (e.g. reset_stale_processing_files()) measure time since the row was
270 // actually last touched, not just time since it was first inserted.
271 $this->data['date_updated'] = ( new DateTime() )->format( 'Y-m-d H:i:s' );
272
273 $data = $this->data;
274
275 unset( $data['id'] );
276 $success = $wpdb->update(
277 $wpdb->prefix . 'psmsc_ai_training',
278 $data,
279 array( 'id' => $this->data['id'] )
280 );
281
282 $this->is_modified = false;
283 self::$cache[ $this->id ] = $this;
284 return $success ? true : false;
285 }
286
287 /**
288 * Insert new record
289 *
290 * @param array $data - insert data.
291 * @return WPSC_RAG_Training_File
292 */
293 public static function insert( $data ) {
294
295 global $wpdb;
296
297 $success = $wpdb->insert(
298 $wpdb->prefix . 'psmsc_ai_training',
299 $data
300 );
301
302 if ( ! $success ) {
303 return false;
304 }
305
306 $cl = new WPSC_RAG_Training_File( $wpdb->insert_id );
307 return $cl;
308 }
309
310 /**
311 * Look up existing (non-deleted) records for a batch of source ids in one query,
312 * instead of one find() (2 queries each: a SELECT plus a separate COUNT) per post.
313 * Used by process_training_posts() to decide skip/insert/refresh for a whole fetched
314 * REST page at once.
315 *
316 * Deliberately not built on top of find(): find() always pages/counts, neither of
317 * which this needs - just the raw rows for the given ids.
318 *
319 * @param string $doc_source Training source slug.
320 * @param string $post_type Post type slug (the 'source' column).
321 * @param array $source_ids Remote post ids to look up.
322 * @param string $provider AI provider the records must belong to.
323 * @return array Map of source_id => list of { id, post_updated_on }, for ids that have
324 * at least one existing (non-deleted) record. Ids with no record at all
325 * are simply absent from the map.
326 */
327 public static function find_existing_by_source_ids( $doc_source, $post_type, array $source_ids, $provider ) {
328
329 global $wpdb;
330
331 $source_ids = array_values( array_unique( array_filter( array_map( 'absint', $source_ids ) ) ) );
332 if ( empty( $source_ids ) ) {
333 return array();
334 }
335
336 $placeholders = implode( ',', array_fill( 0, count( $source_ids ), '%d' ) );
337 $sql = $wpdb->prepare(
338 "SELECT id, source_id, post_updated_on FROM {$wpdb->prefix}psmsc_ai_training
339 WHERE doc_source = %s AND source = %s AND provider = %s AND status != %s
340 AND source_id IN ({$placeholders})",
341 array_merge( array( $doc_source, $post_type, $provider, WPSC_PS_AIT_Status::DELETE ), $source_ids )
342 );
343
344 $by_source_id = array();
345 foreach ( (array) $wpdb->get_results( $sql, ARRAY_A ) as $row ) {
346 $by_source_id[ (int) $row['source_id'] ][] = array(
347 'id' => (int) $row['id'],
348 'post_updated_on' => $row['post_updated_on'],
349 );
350 }
351
352 return $by_source_id;
353 }
354
355 /**
356 * Insert multiple new records in a single query, for a batch of posts that
357 * process_training_posts() has already determined need inserting/refreshing - avoids
358 * one INSERT per post for a whole REST page's worth of rows at once.
359 *
360 * A multi-row INSERT fails as a whole if any single row is malformed - callers should
361 * fall back to inserting one row at a time (via insert()) when this returns 0 for a
362 * non-empty batch, so one bad row cannot silently drop its page-mates too.
363 *
364 * @param array $rows List of associative arrays, each using the same keys insert() takes.
365 * @return int Number of rows inserted (0 if the batch was empty or the statement failed).
366 */
367 public static function bulk_insert( array $rows ) {
368
369 global $wpdb;
370
371 if ( empty( $rows ) ) {
372 return 0;
373 }
374
375 $columns = array_keys( $rows[0] );
376 $row_placeholder = '(' . implode( ',', array_fill( 0, count( $columns ), '%s' ) ) . ')';
377
378 $row_placeholders = array();
379 $values = array();
380 foreach ( $rows as $row ) {
381 $row_placeholders[] = $row_placeholder;
382 foreach ( $columns as $column ) {
383 $values[] = $row[ $column ] ?? '';
384 }
385 }
386
387 $sql = $wpdb->prepare(
388 'INSERT INTO ' . $wpdb->prefix . 'psmsc_ai_training (`' . implode( '`,`', $columns ) . '`) VALUES ' . implode( ',', $row_placeholders ),
389 $values
390 );
391
392 $success = $wpdb->query( $sql );
393
394 return false === $success ? 0 : (int) $success;
395 }
396
397 /**
398 * Delete record of given ID
399 *
400 * @param WPSC_RAG_Training_File $ai_training - AI training object.
401 * @return boolean
402 */
403 public static function destroy( $ai_training ) {
404
405 global $wpdb;
406
407 $success = $wpdb->delete(
408 $wpdb->prefix . 'psmsc_ai_training',
409 array( 'id' => $ai_training->id )
410 );
411 if ( ! $success ) {
412 return false;
413 }
414
415 unset( self::$cache[ $ai_training->id ] );
416 return true;
417 }
418
419 /**
420 * Set data to create new object using direct data. Used in find method
421 *
422 * @param array $data - object data to set.
423 * @return void
424 */
425 private function set_data( $data ) {
426
427 foreach ( $data as $var_name => $val ) {
428 $this->data[ $var_name ] = $val !== null ? $val : '';
429 }
430 self::$cache[ $this->id ] = $this;
431 }
432
433 /**
434 * Find records based on given filters
435 *
436 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
437 * @param boolean $is_object - return data as array or object. Default object.
438 * @return mixed
439 */
440 public static function find( $filter = array(), $is_object = true ) {
441
442 global $wpdb;
443
444 $filter['items_per_page'] = isset( $filter['items_per_page'] ) ? $filter['items_per_page'] : 0;
445 $filter['page_no'] = isset( $filter['page_no'] ) ? $filter['page_no'] : 1;
446 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'id';
447 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'ASC';
448
449 $sql = 'SELECT * FROM ' . $wpdb->prefix . 'psmsc_ai_training ait ';
450 $where = self::get_where( $filter );
451
452 $order = WPSC_Functions::parse_order( $filter );
453 $limit = WPSC_Functions::parse_limit( $filter );
454
455 $sql = $sql . $where . $order . $limit;
456 $results = $wpdb->get_results( $sql, ARRAY_A );
457
458 // total results.
459 $sql = 'SELECT count(DISTINCT ait.id) FROM ' . $wpdb->prefix . 'psmsc_ai_training ait ';
460 $total_items = $wpdb->get_var( $sql . $where );
461
462 $response = WPSC_Functions::parse_response( $results, $total_items, $filter );
463
464 // Return array.
465 if ( ! $is_object ) {
466 return $response;
467 }
468
469 // create and return array of objects.
470 $temp_results = array();
471 foreach ( $response['results'] as $cl ) {
472
473 $ob = new WPSC_RAG_Training_File();
474 $data = array();
475 foreach ( $cl as $key => $val ) {
476 $data[ $key ] = $val;
477 }
478 $ob->set_data( $data );
479 $temp_results[] = $ob;
480 }
481 $response['results'] = $temp_results;
482
483 return $response;
484 }
485
486 /**
487 * Get where for find method
488 *
489 * @param array $filter - user filter.
490 * @return array
491 */
492 private static function get_where( $filter ) {
493 $where = array( '1=1' );
494
495 // Load meta filters.
496 $meta_query = isset( $filter['meta_query'] ) ? $filter['meta_query'] : array();
497 if ( $meta_query ) {
498 $where[] = WPSC_Functions::parse_user_filters( __CLASS__, $meta_query );
499 }
500
501 global $wpdb;
502 $search = WPSC_Functions::get_filter_search_str( $filter );
503 if ( $search ) {
504 $escaped_search = esc_sql( $wpdb->esc_like( $search ) );
505 $like = '%' . $escaped_search . '%';
506
507 $search_query = array();
508
509 // AI training table cols.
510 foreach ( self::$schema as $slug => $schema ) {
511 if ( ! $schema['has_ref'] && $slug != 'id' ) {
512 $search_query[] = $wpdb->prepare( 'CONVERT(ait.' . $slug . ' USING utf8) LIKE %s', $like );
513 }
514 }
515 $where[] = implode( ' OR ', $search_query );
516 }
517
518 return 'WHERE (' . implode( ') AND (', $where ) . ') ';
519 }
520
521 /**
522 * Load current class to ref classes
523 *
524 * @param array $classes - ref classes array.
525 * @return array
526 */
527 public static function load_ref_class( $classes ) {
528
529 $classes['wpsc_ai_training'] = array(
530 'class' => __CLASS__,
531 'save-key' => 'id',
532 );
533 return $classes;
534 }
535
536 /**
537 * Count total records based on given filters
538 *
539 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
540 * @return int
541 */
542 public static function count( $filter = array() ) {
543
544 global $wpdb;
545
546 $sql = 'SELECT COUNT(DISTINCT ait.id) FROM ' . $wpdb->prefix . 'psmsc_ai_training ait ';
547 $where = self::get_where( $filter );
548
549 $sql = $sql . $where;
550
551 return (int) $wpdb->get_var( $sql );
552 }
553
554 /**
555 * Pluck specific column values based on given filters
556 *
557 * @param string $column - column name to pluck.
558 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
559 * @return array
560 */
561 public static function pluck( $column, $filter = array() ) {
562
563 global $wpdb;
564
565 if ( empty( $column ) ) {
566 return array();
567 }
568
569 // Validate column using schema.
570 if ( ! array_key_exists( $column, self::$schema ) ) {
571 return array();
572 }
573
574 $table = $wpdb->prefix . 'psmsc_ai_training';
575
576 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'id';
577 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'ASC';
578
579 $sql = "SELECT DISTINCT {$column} FROM {$table} ait ";
580 $where = self::get_where( $filter );
581 $order = WPSC_Functions::parse_order( $filter );
582 $limit = WPSC_Functions::parse_limit( $filter );
583
584 $sql = $sql . $where . $order . $limit;
585
586 return $wpdb->get_col( $sql );
587 }
588
589 /**
590 * Safe delete record of given ID. If provider file id is available then update status to delete otherwise hard delete.
591 *
592 * @param WPSC_RAG_Training_File $ai_training_data - AI training object.
593 * @return boolean
594 */
595 public static function safe_delete( $ai_training_data ) {
596
597 if ( ! $ai_training_data->provider_file_id ) {
598 return self::destroy( $ai_training_data );
599 } else {
600 $ai_training_data->status = WPSC_PS_AIT_Status::DELETE;
601 $ai_training_data->save();
602 return true;
603 }
604 return false;
605 }
606 }
607 endif;
608 WPSC_RAG_Training_File::init();
609