PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / trunk
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent vtrunk
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-archive-ticket.php

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

1,150 lines 28.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_Archive_Ticket' ) ) :
7
8 final class WPSC_Archive_Ticket {
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' ), 3 );
47
48 // Get object of this class.
49 add_filter( 'wpsc_load_ref_classes', array( __CLASS__, 'load_ref_class' ) );
50 }
51
52 /**
53 * Apply schema for this model
54 *
55 * @return void
56 */
57 public static function apply_schema() {
58
59 // start with schema item not present as custom field type.
60 $schema = array(
61 'is_active' => array(
62 'has_ref' => false,
63 'ref_class' => '',
64 'has_multiple_val' => false,
65 ),
66 'auth_code' => array(
67 'has_ref' => false,
68 'ref_class' => '',
69 'has_multiple_val' => false,
70 ),
71 'live_agents' => array(
72 'has_ref' => false,
73 'ref_class' => '',
74 'has_multiple_val' => false,
75 ),
76 'misc' => array(
77 'has_ref' => false,
78 'ref_class' => '',
79 'has_multiple_val' => false,
80 ),
81 );
82
83 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
84
85 if ( in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
86 $schema[ $cf->slug ] = array(
87 'has_ref' => $cf->type::$has_ref,
88 'ref_class' => $cf->type::$ref_class,
89 'has_multiple_val' => $cf->type::$has_multiple_val,
90 );
91 }
92 }
93
94 self::$schema = apply_filters( 'wpsc_ticket_schema', $schema );
95
96 // Prevent modify.
97 $prevent_modify = array( 'id', 'description', 'agent_created', 'date_created' );
98 self::$prevent_modify = apply_filters( 'wpsc_ticket_prevent_modify', $prevent_modify );
99 }
100
101 /**
102 * Model constructor
103 *
104 * @param int $id - Optional. Data record id to retrive object for.
105 */
106 public function __construct( $id = 0 ) {
107
108 global $wpdb;
109
110 $id = intval( $id );
111
112 if ( $id > 0 ) {
113
114 $ticket = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}psmsc_archived_tickets WHERE id = " . $id, ARRAY_A );
115 if ( ! is_array( $ticket ) ) {
116 return;
117 }
118
119 foreach ( $ticket as $key => $val ) {
120 $this->data[ $key ] = $val !== null ? $val : '';
121 }
122 }
123 }
124
125 /**
126 * Convert object into an array
127 *
128 * @return array
129 */
130 public function to_array() {
131
132 return $this->data;
133 }
134
135 /**
136 * Magic get function to use with object arrow function
137 *
138 * @param string $var_name - variable name.
139 * @return mixed
140 */
141 public function __get( $var_name ) {
142
143 if ( $var_name == 'misc' ) {
144
145 $misc_data = array();
146 if ( ( isset( $this->data['misc'] ) && ! empty( isset( $this->data['misc'] ) ) ) ) {
147 $misc_data = json_decode( $this->data['misc'], true );
148 }
149 return $misc_data;
150 }
151
152 if ( ! isset( $this->data[ $var_name ] ) ||
153 $this->data[ $var_name ] == null ||
154 $this->data[ $var_name ] == ''
155 ) {
156
157 if ( $var_name == 'customer_name' ) {
158 return $this->customer->name;
159 }
160 if ( $var_name == 'customer_email' ) {
161 return $this->customer->email_address;
162 }
163 return self::$schema[ $var_name ]['has_multiple_val'] ? array() : '';
164 }
165
166 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
167
168 $response = array();
169 $values = $this->data[ $var_name ] ? explode( '|', $this->data[ $var_name ] ) : array();
170 foreach ( $values as $val ) {
171 $response[] = self::$schema[ $var_name ]['has_ref'] ?
172 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $val ) :
173 $val;
174 }
175 return $response;
176
177 } elseif ( ( self::$schema[ $var_name ]['has_ref'] && $this->data[ $var_name ] ) || ( self::$schema[ $var_name ]['has_ref'] && self::$schema[ $var_name ]['ref_class'] == 'wpsc_customer' ) ) {
178 return WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $this->data[ $var_name ] );
179 } else {
180 return $this->data[ $var_name ];
181 }
182 }
183
184 /**
185 * Magic function to use setting object field with arrow function
186 *
187 * @param string $var_name - (Required) property slug.
188 * @param mixed $value - (Required) value to set for a property.
189 * @return void
190 */
191 public function __set( $var_name, $value ) {
192
193 if (
194 ! isset( $this->data[ $var_name ] ) ||
195 in_array( $var_name, self::$prevent_modify )
196 ) {
197 return;
198 }
199
200 $data_val = '';
201 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
202
203 $data_vals = array_map(
204 fn( $val ) => is_object( $val ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $val ) : $val,
205 $value
206 );
207
208 $data_val = $data_vals ? implode( '|', $data_vals ) : '';
209 } else {
210 if ( $var_name == 'misc' ) {
211 $value = wp_json_encode( $value );
212 }
213 $data_val = is_object( $value ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $value ) : $value;
214 }
215
216 if ( $this->data[ $var_name ] == $data_val ) {
217 return;
218 }
219
220 $this->data[ $var_name ] = $data_val;
221 $this->is_modified = true;
222 }
223
224 /**
225 * Save changes made
226 *
227 * @return boolean
228 */
229 public function save() {
230
231 global $wpdb;
232
233 if ( ! $this->is_modified ) {
234 return true;
235 }
236
237 $data = $this->data;
238 $success = true;
239
240 if ( ! isset( $data['id'] ) ) {
241
242 $tic = self::insert( $data );
243 if ( $tic ) {
244 $this->data = $tic->data;
245 $success = true;
246 } else {
247 $success = false;
248 }
249 } else {
250
251 unset( $data['id'] );
252 $success = $wpdb->update(
253 $wpdb->prefix . 'psmsc_archived_tickets',
254 $data,
255 array( 'id' => $this->data['id'] )
256 );
257 }
258 $this->is_modified = false;
259 return $success ? true : false;
260 }
261
262 /**
263 * Insert new record
264 *
265 * @param array $data - insert data.
266 * @return WPSC_Custom_Field
267 */
268 public static function insert( $data ) {
269
270 global $wpdb;
271
272 $ad_settings = get_option( 'wpsc-ms-advanced-settings' );
273 if ( ! isset( $data['id'] ) && $ad_settings['ticket-id-format'] === 'random' ) {
274
275 $range = self::get_ticket_id_range();
276 do {
277 $id = wp_rand( $range['start_range'], $range['end_range'] );
278 $sql = "select id from {$wpdb->prefix}psmsc_archived_tickets where id=" . $id;
279 $result = $wpdb->get_var( $sql );
280 } while ( $result );
281 $data['id'] = $id;
282 }
283
284 // Insert ticket to DB.
285 $success = $wpdb->insert(
286 $wpdb->prefix . 'psmsc_archived_tickets',
287 $data
288 );
289
290 if ( ! $success ) {
291 return false;
292 }
293 return new WPSC_Archive_Ticket( $wpdb->insert_id );
294 }
295
296 /**
297 * Delete record from database
298 *
299 * @param WPSC_Archive_Ticket $ticket - ticket object.
300 * @return boolean
301 */
302 public static function destroy( $ticket ) {
303
304 global $wpdb;
305
306 // Delete attachments of the ticket.
307 $success = $wpdb->update(
308 $wpdb->prefix . 'psmsc_attachments',
309 array( 'is_active' => '0' ),
310 array( 'ticket_id' => $ticket->id )
311 );
312
313 // Delete threads for the ticket.
314 $deleted_threads = $wpdb->delete(
315 $wpdb->prefix . 'psmsc_archived_threads',
316 array( 'ticket' => $ticket->id )
317 );
318 if ( false === $deleted_threads ) {
319 return false;
320 }
321
322 // Finally delete ticket.
323 $deleted_ticket = $wpdb->delete(
324 $wpdb->prefix . 'psmsc_archived_tickets',
325 array( 'id' => $ticket->id )
326 );
327 if ( false === $deleted_ticket ) {
328 return false;
329 }
330
331 return true;
332 }
333
334 /**
335 * Set data to create new object using direct data. Used in find method
336 *
337 * @param array $data - data to set for object.
338 * @return void
339 */
340 private function set_data( $data ) {
341
342 foreach ( $data as $var_name => $val ) {
343 $this->data[ $var_name ] = $val !== null ? $val : '';
344 }
345 }
346
347 /**
348 * Find records based on given filters
349 *
350 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
351 * @param boolean $is_object - return data as array or object. Default object.
352 * @return mixed
353 */
354 public static function find( $filter = array(), $is_object = true ) {
355
356 global $wpdb;
357
358 $filter['items_per_page'] = isset( $filter['items_per_page'] ) ? $filter['items_per_page'] : 20;
359 $filter['page_no'] = isset( $filter['page_no'] ) ? $filter['page_no'] : 1;
360 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'date_updated';
361 $filter['orderby_slug'] = $filter['orderby'];
362
363 // orderby.
364 $cf = WPSC_Custom_Field::get_cf_by_slug( $filter['orderby'] );
365 if ( ! $cf ) {
366 $cf = WPSC_Custom_Field::get_cf_by_slug( 'date_updated' );
367 }
368 $filter['orderby'] = $cf->type::get_orderby_string( $cf );
369
370 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'DESC';
371
372 $sql = 'SELECT t.* FROM ' . $wpdb->prefix . 'psmsc_archived_tickets t ';
373 $joins = self::get_joins( $filter );
374 $where = self::get_where( $filter );
375
376 $order = WPSC_Functions::parse_order( $filter );
377 $order = apply_filters( 'wpsc_archive_ticket_order_by', $order, $filter );
378
379 $limit = WPSC_Functions::parse_limit( $filter );
380 $group_by = 'GROUP BY t.id ';
381
382 $sql = $sql . $joins . $where . $group_by . $order . $limit;
383 $results = $wpdb->get_results( $sql, ARRAY_A );
384
385 // total results.
386 $sql = 'SELECT count(DISTINCT t.id) FROM ' . $wpdb->prefix . 'psmsc_archived_tickets t ';
387 $total_items = $wpdb->get_var( $sql . $joins . $where );
388
389 $response = WPSC_Functions::parse_response( $results, $total_items, $filter );
390
391 // Return array.
392 if ( ! $is_object ) {
393 return $response;
394 }
395
396 // create and return array of objects.
397 $temp_results = array();
398 foreach ( $response['results'] as $ticket ) {
399
400 $ob = new WPSC_Archive_Ticket();
401 $data = array();
402 foreach ( $ticket as $key => $val ) {
403 $data[ $key ] = $val;
404 }
405 $ob->set_data( $data );
406 $temp_results[] = $ob;
407 }
408 $response['results'] = $temp_results;
409
410 return $response;
411 }
412
413 /**
414 * Return join string
415 *
416 * @param array $filter - user filter.
417 * @return string
418 */
419 private static function get_joins( $filter ) {
420
421 $joins = apply_filters( 'wpsc_archive_ticket_joins', array(), $filter );
422 return implode( ' ', $joins ) . ' ';
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 global $wpdb;
434
435 // Custom fields by type.
436 $cfs = array();
437 $custom_fields = WPSC_Custom_Field::$custom_fields;
438 foreach ( $custom_fields as $cf ) {
439 $cfs[ $cf->type::$slug ][] = $cf;
440 }
441
442 $where = array( '1=1' );
443
444 // Load system filters.
445 $system_query = isset( $filter['system_query'] ) ? $filter['system_query'] : array();
446 if ( $system_query ) {
447 $where[] = self::parse_filters( $system_query );
448 }
449
450 // Load meta filters.
451 $meta_query = isset( $filter['meta_query'] ) ? $filter['meta_query'] : array();
452 if ( $meta_query ) {
453 $where[] = self::parse_filters( $meta_query );
454 }
455
456 // Load search query.
457 $search = esc_sql( $wpdb->esc_like( WPSC_Functions::get_filter_search_str( $filter ) ) );
458 if ( $search ) {
459
460 $gs = get_option( 'wpsc-gs-general' );
461 $search_query = apply_filters( 'wpsc_archive_ticket_search', array(), $filter, $cfs, $search, $gs['allowed-search-fields'] );
462 if ( $search_query ) {
463 $where[] = implode( ' OR ', $search_query );
464 }
465 }
466
467 $where = apply_filters( 'wpsc_archive_ticket_where', $where, $filter, $cfs );
468
469 return 'WHERE (' . implode( ') AND (', $where ) . ') ';
470 }
471
472 /**
473 * Check whether slug is present in either system filters or meta query filters
474 *
475 * @param string $slug - custom field slug.
476 * @param array $filter - filter array.
477 * @return boolean
478 */
479 public static function is_filter( $slug, $filter ) {
480
481 $flag = false;
482 $search_term = '"slug":"' . $slug . '"';
483
484 if ( isset( $filter['meta_query'] ) ) {
485
486 $meta_query = wp_json_encode( $filter['meta_query'] );
487 if ( is_numeric( strpos( $meta_query, $search_term ) ) ) {
488 $flag = true;
489 }
490 }
491
492 return $flag;
493 }
494
495 /**
496 * Parse user filters for this model
497 *
498 * @param array $filters - filter array.
499 * @return string
500 */
501 private static function parse_filters( $filters ) {
502
503 // Invalid filter.
504 if ( ! isset( $filters['relation'] ) || count( $filters ) < 2 ) {
505 return '1=1';
506 }
507
508 $relation = $filters['relation'];
509 $filter_str = array();
510
511 foreach ( $filters as $key => $filter ) {
512
513 // Skip if current element is relation indicator.
514 if ( $key === 'relation' ) {
515 continue;
516 }
517
518 // Invalid filter if it is not an array.
519 if ( ! is_array( $filter ) ) {
520 return '1=1';
521 }
522
523 // Call recursively if there is multi-layer filter detected.
524 if ( isset( $filter['relation'] ) ) {
525 $filter_str[] = self::parse_filters( $filter );
526 continue;
527 }
528
529 // Invalid filter if it does not contain slug, compare and val indexes.
530 $slug = isset( $filter['slug'] ) ? WPSC_Functions::sanitize_sql_key( $filter['slug'] ) : false;
531 $compare = isset( $filter['compare'] ) ? $filter['compare'] : false;
532 $val = isset( $filter['val'] ) ? $filter['val'] : false;
533 if ( ! $slug || ! $compare || $val === false ) {
534 $filter_str[] = '1=1';
535 }
536
537 // custom filter.
538 if ( $slug === 'custom_query' ) {
539
540 $filter_str[] = $val;
541
542 } else {
543
544 // Get custom field object for the slug.
545 $cf = WPSC_Custom_Field::get_cf_by_slug( $slug );
546 if ( $cf ) {
547 $filter_str[] = $cf->type::parse_filter( $cf, $compare, $val );
548 }
549 }
550 }
551
552 return count( $filter_str ) > 1 ?
553 '(' . implode( ' ' . $relation . ' ', $filter_str ) . ')' :
554 $filter_str[0];
555 }
556
557 /**
558 * Load current class to reference classes
559 *
560 * @param array $classes - Associative array of class names indexed by its slug.
561 * @return array
562 */
563 public static function load_ref_class( $classes ) {
564
565 $classes['wpsc_archive_ticket'] = array(
566 'class' => __CLASS__,
567 'save-key' => 'id',
568 );
569 return $classes;
570 }
571
572 /**
573 * Return array of thread objects for current ticket
574 *
575 * @param integer $page_no - current page.
576 * @param integer $items_per_page - number of records per page.
577 * @param array $types - thread types: reply, log, etc.
578 * @param string $orderby - orderby slug.
579 * @param string $order - order flag.
580 * @return array
581 */
582 public function get_threads( $page_no = 1, $items_per_page = 0, $types = array(), $orderby = 'date_created', $order = 'DESC' ) {
583
584 $args = array(
585 'meta_query' => array(
586 'relation' => 'AND',
587 array(
588 'slug' => 'ticket',
589 'compare' => '=',
590 'val' => $this->id,
591 ),
592 ),
593 'items_per_page' => $items_per_page,
594 'page_no' => $page_no,
595 'orderby' => $orderby,
596 'order' => $order,
597 );
598
599 if ( $types ) {
600 $args['meta_query'][] = array(
601 'slug' => 'type',
602 'compare' => 'IN',
603 'val' => $types,
604 );
605 }
606
607 return WPSC_Archive_Thread::find( $args )['results'];
608 }
609
610 /**
611 * Return last reply of the ticket.
612 *
613 * @return WPSC_Archive_Thread|boolean
614 */
615 public function get_last_reply() {
616
617 $filters = array(
618 'meta_query' => array(
619 'relation' => 'AND',
620 array(
621 'slug' => 'ticket',
622 'compare' => '=',
623 'val' => $this->id,
624 ),
625 array(
626 'slug' => 'type',
627 'compare' => 'IN',
628 'val' => array( 'reply', 'report' ),
629 ),
630 array(
631 'slug' => 'is_active',
632 'compare' => '=',
633 'val' => '1',
634 ),
635 ),
636 'orderby' => 'id',
637 'order' => 'DESC',
638 'items_per_page' => 1,
639 );
640 $threads = WPSC_Archive_Thread::find( $filters );
641
642 return isset( $threads['results'][0] ) ? $threads['results'][0] : false;
643 }
644
645 /**
646 * Return last note of the ticket.
647 *
648 * @return WPSC_Archive_Thread|boolean
649 */
650 public function get_last_note() {
651
652 $filters = array(
653 'meta_query' => array(
654 'relation' => 'AND',
655 array(
656 'slug' => 'ticket',
657 'compare' => '=',
658 'val' => $this->id,
659 ),
660 array(
661 'slug' => 'type',
662 'compare' => '=',
663 'val' => 'note',
664 ),
665 array(
666 'slug' => 'is_active',
667 'compare' => '=',
668 'val' => '1',
669 ),
670 ),
671 'orderby' => 'id',
672 'order' => 'DESC',
673 'items_per_page' => 1,
674 );
675 $threads = WPSC_Archive_Thread::find( $filters );
676
677 return isset( $threads['results'][0] ) ? $threads['results'][0] : false;
678 }
679
680 /**
681 * Return array of agents allowed to view this ticket
682 */
683 public function get_current_read_permission_agents() {
684
685 $agents = array();
686 $agent_roles = get_option( 'wpsc-agent-roles' );
687
688 // check customer whether he is an agent.
689 if ( $this->customer->user ) {
690 $agent = WPSC_Agent::get_by_user_id( $this->customer->user->ID );
691 if ( $agent->id && $agent->is_active ) {
692 $agents[] = $agent;
693 }
694 }
695
696 // unassigned.
697 if ( ! $this->assigned_agent ) {
698
699 $applicable_roles = array();
700 foreach ( $agent_roles as $key => $role ) {
701 if ( $role['caps']['view-unassigned'] ) {
702 $applicable_roles[] = $key;
703 }
704 }
705 $temp_agents = WPSC_Agent::find(
706 array(
707 'items_per_page' => 0,
708 'meta_query' => array(
709 'relation' => 'AND',
710 array(
711 'slug' => 'role',
712 'compare' => 'IN',
713 'val' => $applicable_roles,
714 ),
715 array(
716 'slug' => 'is_active',
717 'compare' => '=',
718 'val' => 1,
719 ),
720 array(
721 'slug' => 'is_agentgroup',
722 'compare' => '=',
723 'val' => 0,
724 ),
725 ),
726 )
727 )['results'];
728 if ( $temp_agents ) {
729 $agents = array_merge( $agents, $temp_agents );
730 }
731 } else { // assigned to agents.
732
733 // assign to me.
734 foreach ( $this->assigned_agent as $agent ) {
735
736 if ( $agent->is_agentgroup ) {
737 continue;
738 }
739 if ( $agent_roles[ $agent->role ]['caps']['view-assigned-me'] ) {
740 $agents[] = $agent;
741 }
742 }
743
744 // assign to other.
745 $applicable_roles = array();
746 foreach ( $agent_roles as $key => $role ) {
747 if ( $role['caps']['view-assigned-others'] ) {
748 $applicable_roles[] = $key;
749 }
750 }
751 $temp_agents = WPSC_Agent::find(
752 array(
753 'items_per_page' => 0,
754 'meta_query' => array(
755 'relation' => 'AND',
756 array(
757 'slug' => 'role',
758 'compare' => 'IN',
759 'val' => $applicable_roles,
760 ),
761 array(
762 'slug' => 'is_active',
763 'compare' => '=',
764 'val' => 1,
765 ),
766 array(
767 'slug' => 'is_agentgroup',
768 'compare' => '=',
769 'val' => 0,
770 ),
771 ),
772 )
773 )['results'];
774 if ( $temp_agents ) {
775 $agents = array_merge( $agents, $temp_agents );
776 }
777
778 // filter for agentgroups.
779 $agents = apply_filters( 'wpsc_get_current_read_permission_agents', $agents, $this );
780 }
781
782 $temp_agents = array();
783 foreach ( $agents as $agent ) {
784 if ( ! isset( $temp_agents[ $agent->id ] ) ) {
785 $temp_agents[ $agent->id ] = $agent;
786 }
787 }
788
789 return $temp_agents;
790 }
791
792 /**
793 * Return agents associated to this ticket previously with read permission
794 *
795 * @param array $prev - array of agent objects.
796 * @return array
797 */
798 public function get_prev_read_permission_agents( $prev ) {
799
800 $agents = array();
801 $agent_roles = get_option( 'wpsc-agent-roles' );
802
803 // unassigned.
804 if ( ! $prev ) {
805
806 $applicable_roles = array();
807 foreach ( $agent_roles as $key => $role ) {
808 if ( $role['caps']['view-unassigned'] ) {
809 $applicable_roles[] = $key;
810 }
811 }
812 $temp_agents = WPSC_Agent::find(
813 array(
814 'items_per_page' => 0,
815 'meta_query' => array(
816 'relation' => 'AND',
817 array(
818 'slug' => 'role',
819 'compare' => 'IN',
820 'val' => $applicable_roles,
821 ),
822 array(
823 'slug' => 'is_active',
824 'compare' => '=',
825 'val' => 1,
826 ),
827 array(
828 'slug' => 'is_agentgroup',
829 'compare' => '=',
830 'val' => 0,
831 ),
832 ),
833 )
834 )['results'];
835 if ( $temp_agents ) {
836 $agents = array_merge( $agents, $temp_agents );
837 }
838 } else {
839
840 // assign to me.
841 foreach ( $prev as $agent ) {
842 if ( $agent->is_agentgroup ) {
843 continue;
844 }
845 if ( $agent_roles[ $agent->role ]['caps']['view-assigned-me'] ) {
846 $agents[] = $agent;
847 }
848 }
849
850 // assign to other.
851 $applicable_roles = array();
852 foreach ( $agent_roles as $key => $role ) {
853 if ( $role['caps']['view-assigned-others'] ) {
854 $applicable_roles[] = $key;
855 }
856 }
857 $temp_agents = WPSC_Agent::find(
858 array(
859 'items_per_page' => 0,
860 'meta_query' => array(
861 'relation' => 'AND',
862 array(
863 'slug' => 'role',
864 'compare' => 'IN',
865 'val' => $applicable_roles,
866 ),
867 array(
868 'slug' => 'is_active',
869 'compare' => '=',
870 'val' => 1,
871 ),
872 array(
873 'slug' => 'is_agentgroup',
874 'compare' => '=',
875 'val' => 0,
876 ),
877 ),
878 )
879 )['results'];
880 if ( $temp_agents ) {
881 $agents = array_merge( $agents, $temp_agents );
882 }
883
884 // filter for agentgroups.
885 $agents = apply_filters( 'wpsc_get_prev_read_permission_agents', $agents, $this );
886 }
887
888 $temp_agents = array();
889 foreach ( $agents as $agent ) {
890 if ( ! isset( $temp_agents[ $agent->id ] ) ) {
891 $temp_agents[ $agent->id ] = $agent;
892 }
893 }
894 return $temp_agents;
895 }
896
897 /**
898 * Get ticket id range
899 *
900 * @return array start id and end id of random number
901 */
902 public static function get_ticket_id_range() {
903
904 $ad_settings = get_option( 'wpsc-ms-advanced-settings' );
905 $length = $ad_settings['random-id-length'];
906
907 $start = '1';
908 for ( $i = 1; $i < $length; $i++ ) {
909 $start .= '0';
910 }
911
912 $end = '9';
913 for ( $i = 1; $i < $length; $i++ ) {
914 $end .= '9';
915 }
916 return array(
917 'start_range' => intval( $start ),
918 'end_range' => intval( $end ),
919 );
920 }
921
922 /**
923 * Get ticket url
924 */
925 public function get_url() {
926 return apply_filters( 'wpsc_get_ticket_url', '', $this );
927 }
928
929 /**
930 * Get description thread model object
931 *
932 * @return WPSC_Archive_Thread
933 */
934 public function get_description_thread() {
935
936 $results = WPSC_Archive_Thread::find(
937 array(
938 'meta_query' => array(
939 'relation' => 'AND',
940 array(
941 'slug' => 'ticket',
942 'compare' => '=',
943 'val' => $this->id,
944 ),
945 array(
946 'slug' => 'type',
947 'compare' => '=',
948 'val' => 'report',
949 ),
950 ),
951 )
952 )['results'];
953
954 return $results ? $results[0] : false;
955 }
956
957 /**
958 * Archive the given ticket by moving it and its threads to the archived tables.
959 *
960 * @param WPSC_Archive_Ticket $ticket - Ticket object to be archived.
961 * @return boolean
962 * @throws Exception If archiving fails.
963 */
964 public static function set_archive_ticket( $ticket ) {
965
966 global $wpdb;
967 $flag = true;
968 try {
969 // Start transaction safely.
970 $wpdb->query( 'START TRANSACTION' );
971
972 // Ensure the ticket exists before proceeding.
973 $ticket_exists = $wpdb->get_var(
974 $wpdb->prepare(
975 "SELECT COUNT(*) FROM {$wpdb->prefix}psmsc_tickets WHERE id = %d",
976 $ticket->id
977 )
978 );
979
980 if ( ! $ticket_exists ) {
981 $flag = false;
982 throw new Exception( 'Ticket not found in main table.' );
983 }
984
985 // Move the ticket.
986 $insert_ticket = $wpdb->query(
987 $wpdb->prepare(
988 "INSERT INTO {$wpdb->prefix}psmsc_archived_tickets
989 SELECT * FROM {$wpdb->prefix}psmsc_tickets WHERE id = %d",
990 $ticket->id
991 )
992 );
993
994 if ( false === $insert_ticket ) {
995 $flag = false;
996 throw new Exception( 'Failed to move ticket: ' . $wpdb->last_error );
997 }
998
999 // Move all related threads.
1000 $insert_threads = $wpdb->query(
1001 $wpdb->prepare(
1002 "INSERT INTO {$wpdb->prefix}psmsc_archived_threads
1003 SELECT * FROM {$wpdb->prefix}psmsc_threads WHERE ticket = %d",
1004 $ticket->id
1005 )
1006 );
1007
1008 if ( false === $insert_threads && $wpdb->last_error ) {
1009 $flag = false;
1010 throw new Exception( 'Failed to move threads: ' . $wpdb->last_error );
1011 }
1012
1013 // Delete original records only after successful inserts.
1014 $delete_threads = $wpdb->query(
1015 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}psmsc_threads WHERE ticket = %d", $ticket->id )
1016 );
1017
1018 $delete_ticket = $wpdb->query(
1019 $wpdb->prepare( "DELETE FROM {$wpdb->prefix}psmsc_tickets WHERE id = %d", $ticket->id )
1020 );
1021
1022 if ( false === $delete_ticket || false === $delete_threads ) {
1023 $flag = false;
1024 throw new Exception( 'Failed to delete old ticket or threads: ' . $wpdb->last_error );
1025 }
1026
1027 // Commit only when all above succeed.
1028 $wpdb->query( 'COMMIT' );
1029
1030 } catch ( Exception $e ) {
1031 // Roll back in case of any failure.
1032 $wpdb->query( 'ROLLBACK' );
1033 $flag = false;
1034 wp_send_json_error(
1035 array(
1036 'message' => 'Archiving failed!',
1037 'error' => $e->getMessage(),
1038 'success_flag' => $flag,
1039 ),
1040 400
1041 );
1042 }
1043 return $flag;
1044 }
1045
1046 /**
1047 * Restore archived tickets
1048 *
1049 * @param WPSC_Archive_Ticket $ticket - Archive Ticket object.
1050 * @return boolean
1051 * @throws Exception If archiving fails.
1052 */
1053 public static function restore_archive_ticket( $ticket ) {
1054
1055 global $wpdb;
1056 $flag = true;
1057 try {
1058 // Start transaction.
1059 $wpdb->query( 'START TRANSACTION' );
1060
1061 // Ensure the ticket exists in the archived table.
1062 $ticket_exists = $wpdb->get_var(
1063 $wpdb->prepare(
1064 "SELECT COUNT(*) FROM {$wpdb->prefix}psmsc_archived_tickets WHERE id = %d",
1065 $ticket->id
1066 )
1067 );
1068
1069 if ( ! $ticket_exists ) {
1070 $flag = false;
1071 throw new Exception( 'Archived ticket not found.' );
1072 }
1073
1074 // Move the ticket from archive → main table.
1075 $insert_ticket = $wpdb->query(
1076 $wpdb->prepare(
1077 "INSERT INTO {$wpdb->prefix}psmsc_tickets
1078 SELECT * FROM {$wpdb->prefix}psmsc_archived_tickets WHERE id = %d",
1079 $ticket->id
1080 )
1081 );
1082
1083 if ( false === $insert_ticket ) {
1084 $flag = false;
1085 throw new Exception( 'Failed to restore ticket: ' . $wpdb->last_error );
1086 }
1087
1088 // Move all threads for that ticket.
1089 $insert_threads = $wpdb->query(
1090 $wpdb->prepare(
1091 "INSERT INTO {$wpdb->prefix}psmsc_threads
1092 SELECT * FROM {$wpdb->prefix}psmsc_archived_threads WHERE ticket = %d",
1093 $ticket->id
1094 )
1095 );
1096
1097 if ( false === $insert_threads && $wpdb->last_error ) {
1098 $flag = false;
1099 throw new Exception( 'Failed to restore threads: ' . $wpdb->last_error );
1100 }
1101
1102 // Delete the archived records only if above inserts succeeded.
1103 $delete_threads = $wpdb->query(
1104 $wpdb->prepare(
1105 "DELETE FROM {$wpdb->prefix}psmsc_archived_threads WHERE ticket = %d",
1106 $ticket->id
1107 )
1108 );
1109
1110 if ( false === $delete_threads ) {
1111 $flag = false;
1112 throw new Exception( 'Failed to delete threads from archive tables: ' . $wpdb->last_error );
1113 }
1114
1115 $delete_ticket = $wpdb->query(
1116 $wpdb->prepare(
1117 "DELETE FROM {$wpdb->prefix}psmsc_archived_tickets WHERE id = %d",
1118 $ticket->id
1119 )
1120 );
1121
1122 if ( false === $delete_ticket ) {
1123 $flag = false;
1124 throw new Exception( 'Failed to delete ticket from archive tables: ' . $wpdb->last_error );
1125 }
1126
1127 // All good — commit transaction.
1128 $wpdb->query( 'COMMIT' );
1129
1130 } catch ( Exception $e ) {
1131 // Roll back on any failure.
1132 $wpdb->query( 'ROLLBACK' );
1133 $flag = false;
1134 wp_send_json_error(
1135 array(
1136 'message' => 'Restoring ticket failed!',
1137 'error' => $e->getMessage(),
1138 'success_flag' => $flag,
1139 ),
1140 400
1141 );
1142 }
1143 // Optionally return or use $flag here if needed.
1144 return $flag;
1145 }
1146 }
1147 endif;
1148
1149 WPSC_Archive_Ticket::init();
1150