PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.4.7
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.4.7
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 / models / class-wpsc-thread.php

class-wpsc-thread.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.4.7, at includes/models/class-wpsc-thread.php

708 lines 17.5 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_Thread' ) ) :
7
8 final class WPSC_Thread {
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 = array();
30
31 /**
32 * Prevent fields to modify
33 *
34 * @var array
35 */
36 public static $prevent_modify = array();
37
38 /**
39 * Initialize this class
40 *
41 * @return void
42 */
43 public static function init() {
44
45 // Apply schema for this model.
46 add_action( 'init', array( __CLASS__, 'apply_schema' ), 2 );
47
48 // Get object of this class.
49 add_filter( 'wpsc_load_ref_classes', array( __CLASS__, 'load_ref_class' ) );
50
51 // Ticket model.
52 add_filter( 'wpsc_ticket_joins', array( __CLASS__, 'ticket_join' ), 10, 2 );
53 add_filter( 'wpsc_ticket_where', array( __CLASS__, 'ticket_where' ), 9, 3 );
54 add_filter( 'wpsc_ticket_search', array( __CLASS__, 'ticket_search' ), 10, 5 );
55 }
56
57 /**
58 * Apply schema for this model
59 *
60 * @return void
61 */
62 public static function apply_schema() {
63
64 $schema = array(
65 'id' => array(
66 'has_ref' => false,
67 'ref_class' => '',
68 'has_multiple_val' => false,
69 ),
70 'ticket' => array(
71 'has_ref' => true,
72 'ref_class' => 'wpsc_ticket',
73 'has_multiple_val' => false,
74 ),
75 'is_active' => array(
76 'has_ref' => false,
77 'ref_class' => '',
78 'has_multiple_val' => false,
79 ),
80 'customer' => array(
81 'has_ref' => true,
82 'ref_class' => 'wpsc_customer',
83 'has_multiple_val' => false,
84 ),
85 'type' => array(
86 'has_ref' => false,
87 'ref_class' => '',
88 'has_multiple_val' => false,
89 ),
90 'body' => array(
91 'has_ref' => false,
92 'ref_class' => '',
93 'has_multiple_val' => false,
94 ),
95 'attachments' => array(
96 'has_ref' => true,
97 'ref_class' => 'wpsc_attachment',
98 'has_multiple_val' => true,
99 ),
100 'log' => array(
101 'has_ref' => false,
102 'ref_class' => '',
103 'has_multiple_val' => false,
104 ),
105 'ip_address' => array(
106 'has_ref' => false,
107 'ref_class' => '',
108 'has_multiple_val' => false,
109 ),
110 'source' => array(
111 'has_ref' => false,
112 'ref_class' => '',
113 'has_multiple_val' => false,
114 ),
115 'os' => array(
116 'has_ref' => false,
117 'ref_class' => '',
118 'has_multiple_val' => false,
119 ),
120 'browser' => array(
121 'has_ref' => false,
122 'ref_class' => '',
123 'has_multiple_val' => false,
124 ),
125 'seen' => array(
126 'has_ref' => true,
127 'ref_class' => 'datetime',
128 'has_multiple_val' => false,
129 ),
130 'date_created' => array(
131 'has_ref' => true,
132 'ref_class' => 'datetime',
133 'has_multiple_val' => false,
134 ),
135 'date_updated' => array(
136 'has_ref' => true,
137 'ref_class' => 'datetime',
138 'has_multiple_val' => false,
139 ),
140 );
141 self::$schema = apply_filters( 'wpsc_thread_schema', $schema );
142
143 // Prevent modify.
144 $prevent_modify = array( 'id', 'ticket', 'date_created', 'ip_address', 'os', 'browser', 'type', 'source' );
145 self::$prevent_modify = apply_filters( 'wpsc_thread_prevent_modify', $prevent_modify );
146 }
147
148 /**
149 * Model constructor
150 *
151 * @param int $id - Optional. Data record id to retrive object for.
152 */
153 public function __construct( $id = 0 ) {
154
155 global $wpdb;
156
157 $id = intval( $id );
158
159 if ( $id > 0 ) {
160
161 $thread = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}psmsc_threads WHERE id = " . $id, ARRAY_A );
162 if ( ! is_array( $thread ) ) {
163 return;
164 }
165
166 foreach ( $thread as $key => $val ) {
167 $this->data[ $key ] = $val !== null ? $val : '';
168 }
169 }
170 }
171
172 /**
173 * Convert object into an array
174 *
175 * @return array
176 */
177 public function to_array() {
178
179 return $this->data;
180 }
181
182 /**
183 * Magic get function to use with object arrow function
184 *
185 * @param string $var_name - variable name.
186 * @return mixed
187 */
188 public function __get( $var_name ) {
189
190 if ( ! isset( $this->data[ $var_name ] ) ||
191 $this->data[ $var_name ] == null ||
192 $this->data[ $var_name ] == ''
193 ) {
194 return self::$schema[ $var_name ]['has_multiple_val'] ? array() : '';
195 }
196
197 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
198
199 $response = array();
200 $values = $this->data[ $var_name ] ? explode( '|', $this->data[ $var_name ] ) : array();
201 foreach ( $values as $val ) {
202 $response[] = self::$schema[ $var_name ]['has_ref'] ?
203 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $val ) :
204 $val;
205 }
206 return $response;
207
208 } else {
209
210 return self::$schema[ $var_name ]['has_ref'] && $this->data[ $var_name ] ?
211 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $this->data[ $var_name ] ) :
212 $this->data[ $var_name ];
213 }
214 }
215
216 /**
217 * Magic function to use setting object field with arrow function
218 *
219 * @param string $var_name - (Required) property slug.
220 * @param mixed $value - (Required) value to set for a property.
221 * @return void
222 */
223 public function __set( $var_name, $value ) {
224
225 if (
226 ! isset( $this->data[ $var_name ] ) ||
227 in_array( $var_name, self::$prevent_modify )
228 ) {
229 return;
230 }
231
232 $data_val = '';
233 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
234
235 $data_vals = array_map(
236 fn( $val ) => is_object( $val ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $val ) : $val,
237 $value
238 );
239
240 $data_val = $data_vals ? implode( '|', $data_vals ) : '';
241
242 } else {
243
244 $data_val = is_object( $value ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $value ) : $value;
245 }
246
247 if ( $this->data[ $var_name ] == $data_val ) {
248 return;
249 }
250
251 $this->data[ $var_name ] = $data_val;
252 $this->is_modified = true;
253 }
254
255 /**
256 * Save changes made
257 *
258 * @return boolean
259 */
260 public function save() {
261
262 global $wpdb;
263
264 if ( ! $this->is_modified ) {
265 return true;
266 }
267
268 $data = $this->data;
269 $success = true;
270
271 if ( ! isset( $data['id'] ) ) {
272
273 $thd = self::insert( $data );
274 if ( $thd ) {
275 $this->data = $thd->data;
276 $success = true;
277 } else {
278 $success = false;
279 }
280 } else {
281
282 unset( $data['id'] );
283 $success = $wpdb->update(
284 $wpdb->prefix . 'psmsc_threads',
285 $data,
286 array( 'id' => $this->data['id'] )
287 );
288 }
289 $this->is_modified = false;
290 return $success ? true : false;
291 }
292
293 /**
294 * Insert new record
295 *
296 * @param array $data - insert data.
297 * @return WPSC_Thread
298 */
299 public static function insert( $data ) {
300
301 global $wpdb;
302 $current_date = ( new DateTime() )->format( 'Y-m-d H:i:s' );
303
304 // Set date_created and date_updated.
305 if ( ! isset( $data['date_created'] ) ) {
306 $data['date_created'] = $current_date;
307 $data['date_updated'] = $current_date;
308 }
309
310 $success = $wpdb->insert(
311 $wpdb->prefix . 'psmsc_threads',
312 $data
313 );
314
315 if ( ! $success ) {
316 return false;
317 }
318
319 $thread = new WPSC_Thread( $wpdb->insert_id );
320 return $thread;
321 }
322
323 /**
324 * Delete record from database
325 *
326 * @param WPSC_Thread $thread - thread object.
327 * @return boolean
328 */
329 public static function destroy( $thread ) {
330
331 global $wpdb;
332
333 // Delete thread logs.
334 $success = $wpdb->delete(
335 $wpdb->prefix . 'psmsc_logs',
336 array(
337 'type' => 'thread',
338 'ref_id' => $thread->id,
339 )
340 );
341 if ( ! $success ) {
342 return false;
343 }
344
345 // Mark attachments for removal.
346 $wpdb->query( "UPDATE {$wpdb->prefix}psmsc_attachments SET is_active=0 WHERE source IN ('img_editor', '" . $thread->type . "') AND source_id=" . $thread->id );
347
348 $success = $wpdb->delete(
349 $wpdb->prefix . 'psmsc_threads',
350 array( 'id' => $thread->id )
351 );
352 if ( ! $success ) {
353 return false;
354 }
355 return true;
356 }
357
358 /**
359 * Set data to create new object using direct data. Used in find method
360 *
361 * @param array $data - data to set for object.
362 * @return void
363 */
364 private function set_data( $data ) {
365
366 foreach ( $data as $var_name => $val ) {
367 $this->data[ $var_name ] = $val !== null ? $val : '';
368 }
369 }
370
371 /**
372 * Find records based on given filters
373 *
374 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
375 * @param boolean $is_object - return data as array or object. Default object.
376 * @return mixed
377 */
378 public static function find( $filter = array(), $is_object = true ) {
379
380 global $wpdb;
381
382 $sql = 'SELECT * FROM ' . $wpdb->prefix . 'psmsc_threads ';
383 $where = self::get_where( $filter );
384
385 $filter['items_per_page'] = isset( $filter['items_per_page'] ) ? $filter['items_per_page'] : 20;
386 $filter['page_no'] = isset( $filter['page_no'] ) ? $filter['page_no'] : 1;
387 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'id';
388 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'DESC';
389
390 $order = WPSC_Functions::parse_order( $filter );
391 $order = apply_filters( 'wpsc_thread_order_by', $order, $filter );
392 $limit = WPSC_Functions::parse_limit( $filter );
393
394 $sql = $sql . $where . $order . $limit;
395 $results = $wpdb->get_results( $sql, ARRAY_A );
396
397 // total results.
398 $sql = 'SELECT count(id) FROM ' . $wpdb->prefix . 'psmsc_threads ';
399 $total_items = $wpdb->get_var( $sql . $where );
400
401 $response = WPSC_Functions::parse_response( $results, $total_items, $filter );
402
403 // Return array.
404 if ( ! $is_object ) {
405 return $response;
406 }
407
408 // create and return array of objects.
409 $temp_results = array();
410 foreach ( $response['results'] as $thread ) {
411
412 $ob = new WPSC_Thread();
413 $data = array();
414 foreach ( $thread as $key => $val ) {
415 $data[ $key ] = $val;
416 }
417 $ob->set_data( $data );
418 $temp_results[] = $ob;
419 }
420 $response['results'] = $temp_results;
421
422 return $response;
423 }
424
425 /**
426 * Get where for find method
427 *
428 * @param array $filter - user filter.
429 * @return array
430 */
431 private static function get_where( $filter ) {
432
433 $where = '';
434
435 // Set user defined filters.
436 $meta_query = isset( $filter['meta_query'] ) && $filter['meta_query'] ? $filter['meta_query'] : array();
437 if ( $meta_query ) {
438
439 $meta_query = WPSC_Functions::parse_user_filters( __CLASS__, $meta_query );
440 $where = $meta_query . ' ';
441 }
442
443 return $where ? 'WHERE ' . $where : '';
444 }
445
446 /**
447 * Load current class to reference classes
448 *
449 * @param array $classes - Associative array of class names indexed by its slug.
450 * @return array
451 */
452 public static function load_ref_class( $classes ) {
453
454 $classes['wpsc_thread'] = array(
455 'class' => __CLASS__,
456 'save-key' => 'id',
457 );
458 return $classes;
459 }
460
461 /**
462 * Add SQL joins to ticket model for this field type
463 *
464 * @param array $joins - array of join string that can be imploded later.
465 * @param array $filter - user filter.
466 * @return array
467 */
468 public static function ticket_join( $joins, $filter ) {
469
470 global $wpdb;
471 $gs = get_option( 'wpsc-gs-general' );
472 $search = WPSC_Functions::get_filter_search_str( $filter );
473 if ( ( $search && in_array( 'threads', $gs['allowed-search-fields'] ) ) || WPSC_Ticket::is_filter( 'description', $filter ) ) {
474 $joins[] = 'LEFT JOIN ' . $wpdb->prefix . 'psmsc_threads th ON t.id = th.ticket';
475 }
476
477 return $joins;
478 }
479
480 /**
481 * Set thread type limit based on user meta query and search string
482 *
483 * @param array $where - where array.
484 * @param array $filter - user filter.
485 * @param array $custom_fields - custom fields array.
486 * @return array
487 */
488 public static function ticket_where( $where, $filter, $custom_fields ) {
489
490 global $wpdb;
491 $current_user = WPSC_Current_User::$current_user;
492 $search = esc_sql( $wpdb->esc_like( WPSC_Functions::get_filter_search_str( $filter ) ) );
493 $gs = get_option( 'wpsc-gs-general' );
494
495 if ( $search && in_array( 'threads', $gs['allowed-search-fields'] ) ) {
496 if ( $current_user->is_agent ) {
497 $where[] = 'th.type IN(\'report\', \'reply\', \'note\')';
498 } else {
499 $where[] = 'th.type IN(\'report\', \'reply\')';
500 }
501 }
502
503 if ( ! $search && WPSC_Ticket::is_filter( 'description', $filter ) ) {
504 $where[] = 'th.type = \'report\'';
505 }
506
507 return $where;
508 }
509
510 /**
511 * Add ticket search compatibility for fields of this custom field type.
512 *
513 * @param array $sql - Array of sql peices that can be joined later.
514 * @param array $filter - User filter.
515 * @param array $custom_fields - Custom fields array applicable for search.
516 * @param string $search - search string.
517 * @param array $allowed_search_fields - Allowed search fields.
518 * @return array
519 */
520 public static function ticket_search( $sql, $filter, $custom_fields, $search, $allowed_search_fields ) {
521
522 if ( in_array( 'threads', $allowed_search_fields ) ) {
523 $sql[] = 'CONVERT(th.body USING utf8) LIKE \'%' . $search . '%\'';
524 $search_items = WPSC_Attachment::get_tl_search_string( $search );
525 if ( $search_items ) {
526 $sql[] = 'th.attachments RLIKE \'(^|[|])(' . implode( '|', $search_items ) . ')($|[|])\'';
527 }
528 }
529 return $sql;
530 }
531
532 /**
533 * Get logs for current thread
534 *
535 * @return array
536 */
537 public function get_logs() {
538
539 $logs = WPSC_Log::find(
540 array(
541 'meta_query' => array(
542 'relation' => 'AND',
543 array(
544 'slug' => 'type',
545 'compare' => '=',
546 'val' => 'thread',
547 ),
548 array(
549 'slug' => 'ref_id',
550 'compare' => '=',
551 'val' => $this->id,
552 ),
553 ),
554 )
555 );
556
557 return $logs['results'];
558 }
559
560 /**
561 * Delete logs for current thread
562 */
563 public function delete_logs() {
564
565 global $wpdb;
566 $sql = "DELETE FROM {$wpdb->prefix}psmsc_logs WHERE type='thread' AND ref_id={$this->id}";
567 $wpdb->query( $sql );
568 }
569
570 /**
571 * Return printable body string for thread. Can be used for replace macros.
572 *
573 * @return string
574 */
575 public function get_printable_string() {
576
577 ob_start();
578 echo wp_kses_post( $this->body );
579
580 // Thread attachments.
581 $attachments = $this->attachments;
582 if ( $attachments ) {
583 ?>
584 <div>
585 <strong><?php esc_attr_e( 'Attachments', 'supportcandy' ); ?>: </strong>
586 <?php
587 $en = get_option( 'wpsc-en-general' );
588 if ( $en['attachments-in-notification'] == 'file-links' ) {
589
590 $names = array();
591 foreach ( $attachments as $attachment ) {
592 $link = site_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&auth_code=' . $this->ticket->auth_code;
593 $names[] = '<a href= ' . $link . ' >' . $attachment->name . '</a>';
594 }
595 echo wp_kses_post( implode( ', ', $names ) );
596 } else {
597 $names = array_map( fn( $attachment ) => $attachment->name, $attachments );
598 echo esc_attr( implode( ', ', $names ) );
599 }
600 ?>
601 </div>
602 <?php
603 }
604
605 return ob_get_clean();
606 }
607
608 /**
609 * Prints thread for history macro types.
610 *
611 * @return string
612 */
613 public function get_history_macro() {
614
615 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
616 $date = wp_date( $advanced['thread-date-format'], $this->date_created->setTimezone( wp_timezone() )->getTimestamp() );
617
618 ob_start();
619 if ( $this->type != 'log' ) {
620
621 ?>
622 <strong>
623 <?php echo esc_attr( $this->customer->name ) . ' '; ?>
624 <small>
625 <i>
626 <?php
627 switch ( $this->type ) {
628 case 'report':
629 esc_attr_e( 'reported', 'supportcandy' );
630 break;
631
632 case 'reply':
633 esc_attr_e( 'replied', 'supportcandy' );
634 break;
635
636 case 'note':
637 esc_attr_e( 'added a note', 'supportcandy' );
638 break;
639 }
640 ?>
641 </i>
642 </small>
643 </strong>
644 <div style="font-size:10px;"><?php echo esc_attr( $date ); ?></div>
645 <?php
646 echo wp_kses_post( $this->get_printable_string() );
647
648 } else {
649
650 $body = json_decode( $this->body );
651 $is_json = ( json_last_error() == JSON_ERROR_NONE ) ? true : false;
652
653 if ( $is_json ) {
654
655 $cf = WPSC_Custom_Field::get_cf_by_slug( $body->slug );
656 if ( ! $cf ) {
657 return;
658 }
659 ?>
660 <div>
661 <?php
662 if ( $this->customer ) {
663
664 printf(
665 /* translators: %1$s: User Name, %2$s: Field Name */
666 esc_attr__( '%1$s changed the %2$s', 'supportcandy' ),
667 '<strong>' . esc_attr( $this->customer->name ) . '</strong>',
668 '<strong>' . esc_attr( $cf->name ) . '</strong>'
669 );
670
671 } else {
672
673 printf(
674 /* translators: %1$s: Field Name */
675 esc_attr__( 'The %1$s has been changed', 'supportcandy' ),
676 '<strong>' . esc_attr( $cf->name ) . '</strong>'
677 );
678 }
679 ?>
680 </div>
681 <div style="font-size:10px;"><?php echo esc_attr( $date ); ?></div>
682 <table>
683 <tbody>
684 <tr>
685 <td><?php echo wp_kses_post( $cf->type::get_history_log_val( $cf, $body->prev ) ); ?></td>
686 <td>-></td>
687 <td><?php echo wp_kses_post( $cf->type::get_history_log_val( $cf, $body->new ) ); ?></td>
688 </tr>
689 </tbody>
690 </table>
691 <?php
692
693 } else {
694
695 ?>
696 <div><?php echo wp_kses_post( $thread->body ); ?></div>
697 <div style="font-size:10px;"><?php echo esc_attr( $date ); ?></div>
698 <?php
699 }
700 }
701
702 return ob_get_clean();
703 }
704 }
705 endif;
706
707 WPSC_Thread::init();
708