PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.3.7
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.3.7
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 / rest-api / class-wpsc-rest-individual-ticket.php

class-wpsc-rest-individual-ticket.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.3.7, at includes/rest-api/class-wpsc-rest-individual-ticket.php

664 lines 19.8 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_REST_Individual_Ticket' ) ) :
7
8 final class WPSC_REST_Individual_Ticket {
9
10 /**
11 * Array of ticket slugs which we do not want to expose to REST API
12 *
13 * @var array
14 */
15 public static $prevent_data = array();
16
17 /**
18 * Initialize this class
19 *
20 * @return void
21 */
22 public static function init() {
23
24 add_action( 'wpsc_rest_load_ticket_prevent_data', array( __CLASS__, 'load_prevent_data' ) );
25 }
26
27 /**
28 * Load prevent data
29 *
30 * @return void
31 */
32 public static function load_prevent_data() {
33
34 $current_user = WPSC_Current_User::$current_user;
35 $data = ! $current_user->is_agent ? array( 'ip_address', 'source', 'browser', 'os', 'user_type' ) : array();
36 self::$prevent_data = apply_filters( 'wpsc_rest_prevent_ticket_data', $data );
37 }
38
39 /**
40 * Create new ticket
41 *
42 * @param WP_REST_Request $request - request object.
43 * @return WP_Error|WP_REST_Response
44 */
45 public static function create( $request ) {
46
47 $current_user = WPSC_Current_User::$current_user;
48 $gs = get_option( 'wpsc-gs-general' );
49 $advanced = get_option( 'wpsc-ms-advanced-settings' );
50
51 if ( ! (
52 ( ! $current_user->user->ID && in_array( 'guest', $gs['allow-create-ticket'] ) ) ||
53 ( $current_user->is_agent && in_array( $current_user->agent->role, $gs['allow-create-ticket'] ) ) ||
54 ( ! $current_user->is_agent && $current_user->user->ID && in_array( 'registered-user', $gs['allow-create-ticket'] ) )
55 ) ) {
56 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized to create ticket!' ), 401 );
57 }
58
59 // group by custom field type.
60 $cfs = array();
61 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
62 $cfs[ $cf->type::$slug ][] = $cf;
63 }
64
65 // collect all data to create ticket.
66 $data = apply_filters(
67 'wpsc_rest_create_ticket',
68 array( 'errors' => new WP_Error() ),
69 $request,
70 $cfs
71 );
72
73 // check whether there are any errors.
74 if ( $data['errors']->has_errors() ) {
75 return new WP_REST_Response( $data['errors'], 400 );
76 } else {
77 unset( $data['errors'] );
78 }
79
80 // Seperate description from $data.
81 $description = $data['description'];
82 unset( $data['description'] );
83
84 // Seperate description attachments from $data.
85 $description_attachments = $data['description_attachments'];
86 unset( $data['description_attachments'] );
87
88 $data['last_reply_on'] = ( new DateTime() )->format( 'Y-m-d H:i:s' );
89
90 // insert ticket data.
91 $ticket = WPSC_Ticket::insert( $data );
92
93 if ( ! $ticket ) {
94 return new WP_REST_Response( new WP_Error( 'something_wrong', 'Something went wrong!' ), 400 );
95 }
96
97 $thread_customer = $current_user->is_agent && $current_user->customer->id != $ticket->customer->id && $advanced['raised-by-user'] == 'agent' ? $current_user->customer : $ticket->customer;
98 $ticket->last_reply_by = $thread_customer->id;
99 $ticket->last_reply_source = 'rest-api';
100 $ticket->save();
101
102 // replace macros only when current user is an agent.
103 $description = $current_user->is_agent ? WPSC_Macros::replace( $description, $ticket ) : $description;
104
105 // set signature if agent.
106 $signature = $current_user->is_agent && $current_user->customer->email == $ticket->customer->email ? $current_user->agent->get_signature() : '';
107 if ( $signature ) {
108 $description .= $signature;
109 }
110
111 // Create report thread.
112 $thread = WPSC_Thread::insert(
113 array(
114 'ticket' => $ticket->id,
115 'customer' => $thread_customer->id,
116 'type' => 'report',
117 'body' => $description,
118 'attachments' => $description_attachments,
119 'ip_address' => $ticket->ip_address,
120 'source' => $ticket->source,
121 'os' => $ticket->os,
122 'browser' => $ticket->browser,
123 )
124 );
125
126 do_action( 'wpsc_create_new_ticket', $ticket );
127
128 $request = new WP_REST_Request( 'GET', '/supportcandy/v2/tickets/' . $ticket->id );
129 return rest_do_request( $request );
130 }
131
132 /**
133 * Get individual ticket
134 *
135 * @param WP_REST_Request $request - request object.
136 * @return WP_Error|WP_REST_Response
137 */
138 public static function get( $request ) {
139
140 $current_user = WPSC_Current_User::$current_user;
141
142 // triggers loading of prevent ticket data property.
143 do_action( 'wpsc_rest_load_ticket_prevent_data' );
144
145 $ticket = new WPSC_Ticket( $request->get_param( 'id' ) );
146 if ( ! $ticket->id ) {
147 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
148 }
149
150 WPSC_Individual_Ticket::$ticket = $ticket;
151 if ( ! (
152 ( $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'view' ) ) ||
153 WPSC_Individual_Ticket::is_customer()
154 ) ) {
155 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
156 }
157
158 $data = self::modify_response( $ticket->to_array() );
159 return new WP_REST_Response( $data, 200 );
160 }
161
162 /**
163 * Update/modify the ticket
164 *
165 * @param WP_REST_Request $request - request object.
166 * @return WP_Error|WP_REST_Response
167 */
168 public static function update( $request ) {
169
170 $current_user = WPSC_Current_User::$current_user;
171
172 $ticket = new WPSC_Ticket( $request->get_param( 'id' ) );
173 if ( ! $ticket->id || ! $ticket->is_active ) {
174 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
175 }
176
177 // ticket object clone to compare.
178 $ticket_clone = clone $ticket;
179
180 WPSC_Individual_Ticket::$ticket = $ticket;
181
182 foreach ( $request->get_params() as $slug => $val ) {
183
184 if ( in_array( $slug, array( 'id', 'description', 'description_attachments' ) ) ) {
185 continue;
186 }
187
188 $cf = WPSC_Custom_Field::get_cf_by_slug( $slug );
189 if ( ! $cf || ! in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
190 continue;
191 }
192
193 // ticket fields.
194 if ( $cf->field == 'ticket' ) {
195
196 // subject.
197 if ( $slug == 'subject' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'ctf' ) ) {
198
199 $prev = $ticket->subject;
200 $new = sanitize_text_field( $val );
201 if ( ! $new || $prev == $new ) {
202 continue;
203 }
204
205 $ticket->subject = $new;
206 do_action( 'wpsc_change_ticket_subject', $ticket, $prev, $new, $current_user->customer->id );
207 }
208
209 // status.
210 if ( $slug == 'status' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'cs' ) ) {
211
212 $status = new WPSC_Status( intval( $val ) );
213 if ( ! $status->id || $ticket->status == $status ) {
214 continue;
215 }
216
217 WPSC_Individual_Ticket::change_status( $ticket->status->id, $status->id, $current_user->customer->id );
218 }
219
220 // priority.
221 if ( $slug == 'priority' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'cs' ) ) {
222
223 $priority = new WPSC_Priority( intval( $val ) );
224 if ( ! $priority->id || $ticket->priority == $priority ) {
225 continue;
226 }
227
228 WPSC_Individual_Ticket::change_priority( $ticket->priority->id, $priority->id, $current_user->customer->id );
229 }
230
231 // category.
232 if ( $slug == 'category' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'cs' ) ) {
233
234 $category = new WPSC_Category( intval( $val ) );
235 if ( ! $category->id || $ticket->category == $category ) {
236 continue;
237 }
238
239 WPSC_Individual_Ticket::change_category( $ticket->category->id, $category->id, $current_user->customer->id );
240 }
241
242 // customer.
243 if ( $slug == 'customer' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'crb' ) ) {
244
245 $customer = new WPSC_Customer( intval( $val ) );
246 if ( ! $customer->id || $ticket->customer == $customer ) {
247 continue;
248 }
249
250 WPSC_Individual_Ticket::change_raised_by( $ticket->customer, $customer, $current_user->customer->id );
251 }
252
253 // assignee.
254 if ( $slug == 'assigned_agent' && $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'aa' ) ) {
255
256 $new = array_unique(
257 array_filter(
258 array_map(
259 function ( $id ) {
260 $agent = new WPSC_Agent( intval( $id ) );
261 return $agent->id ? $agent->id : false;
262 },
263 explode( ',', sanitize_text_field( $val ) )
264 )
265 )
266 );
267
268 $prev = array_unique(
269 array_filter(
270 array_map(
271 fn( $agent ) => $agent->id,
272 $ticket->assigned_agent
273 )
274 )
275 );
276
277 if ( ! $new || ! ( array_diff( $prev, $new ) || array_diff( $new, $prev ) ) ) {
278 continue;
279 }
280
281 $new = array_map(
282 fn( $id ) => new WPSC_Agent( $id ),
283 $new
284 );
285
286 WPSC_Individual_Ticket::change_assignee( $ticket->assigned_agent, $new, $current_user->customer->id );
287 }
288
289 // additional recipients.
290 if (
291 $slug == 'add_recipients' &&
292 (
293 ( $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'ar' ) ) ||
294 ( ! $current_user->is_agent && $ticket->customer->id == $current_user->customer->id )
295 )
296 ) {
297
298 $recipients = array_unique( array_filter( array_map( 'sanitize_email', explode( ',', $val ) ) ) );
299 $prev = $ticket->add_recipients;
300 if ( ! $recipients || ! ( array_diff( $prev, $recipients ) || array_diff( $recipients, $prev ) ) ) {
301 continue;
302 }
303
304 $ticket->add_recipients = $recipients;
305 do_action( 'wpsc_change_ticket_add_recipients', $ticket, $prev, $recipients, $current_user->customer->id );
306 }
307
308 // ticket fields.
309 if (
310 class_exists( $cf->type ) &&
311 $current_user->is_agent &&
312 WPSC_Individual_Ticket::has_ticket_cap( 'ctf' ) &&
313 ! $cf->type::$is_default &&
314 ! in_array( $cf->type::$slug, WPSC_ITW_Ticket_Fields::$ignore_cft )
315 ) {
316
317 $cf->type::set_rest_edit_ticket_cf( $ticket, $cf, $val );
318 do_action( 'wpsc_change_ticket_fields', $ticket_clone, $ticket );
319 }
320
321 // handle add-on ticket fields.
322 do_action( 'wpsc_rest_update_ticket_fields', $ticket, $cf, $val );
323
324 } elseif (
325 // agentonly fields.
326 class_exists( $cf->type ) &&
327 $current_user->is_agent &&
328 WPSC_Individual_Ticket::has_ticket_cap( 'caof' ) &&
329 ! $cf->type::$is_default &&
330 ! in_array( $cf->type::$slug, WPSC_ITW_Agentonly_Fields::$ignore_cft )
331 ) {
332
333 $cf->type::set_rest_edit_ticket_cf( $ticket, $cf, $val );
334 do_action( 'wpsc_change_agentonly_fields', $ticket_clone, $ticket );
335 }
336 }
337
338 // save the changes.
339 if ( $ticket != $ticket_clone ) {
340
341 $ticket->date_updated = new DateTime();
342 $ticket->save();
343 }
344
345 $request = new WP_REST_Request( 'GET', '/supportcandy/v2/tickets/' . $ticket->id );
346 return rest_do_request( $request );
347 }
348
349 /**
350 * Delete the ticket
351 *
352 * @param WP_REST_Request $request - request object.
353 * @return WP_Error|WP_REST_Response
354 */
355 public static function delete( $request ) {
356
357 $current_user = WPSC_Current_User::$current_user;
358 if ( ! $current_user->is_agent ) {
359 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized to delete ticket!' ), 401 );
360 }
361
362 $ticket = new WPSC_Ticket( $request->get_param( 'id' ) );
363 if ( ! $ticket->id ) {
364 return new WP_REST_Response( new WP_Error( 'bad_request', 'Bad request!' ), 400 );
365 }
366
367 WPSC_Individual_Ticket::$ticket = $ticket;
368 if ( ! WPSC_Individual_Ticket::$ticket->is_active || ! WPSC_Individual_Ticket::has_ticket_cap( 'dtt' ) ) {
369 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized to delete this ticket!' ), 401 );
370 }
371
372 WPSC_Individual_Ticket::delete_ticket();
373 return new WP_REST_Response( array( 'success' => true ), 200 );
374 }
375
376 /**
377 * Get ticket threads
378 *
379 * @param WP_REST_Request $request - request object.
380 * @return WP_Error|WP_REST_Response
381 */
382 public static function get_threads( $request ) {
383
384 $current_user = WPSC_Current_User::$current_user;
385
386 $ticket = new WPSC_Ticket( $request->get_param( 'id' ) );
387 if ( ! $ticket->id || ! $ticket->is_active ) {
388 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
389 }
390
391 WPSC_Individual_Ticket::$ticket = $ticket;
392 if ( ! (
393 ( $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'view' ) ) ||
394 ( WPSC_Individual_Ticket::is_customer() )
395 ) ) {
396 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
397 }
398
399 $filters = array(
400 'page_no' => $request->get_param( 'page' ),
401 'items_per_page' => $request->get_param( 'per_page' ),
402 'order' => $request->get_param( 'order' ),
403 'meta_query' => array(
404 'relation' => 'AND',
405 array(
406 'slug' => 'ticket',
407 'compare' => '=',
408 'val' => $ticket->id,
409 ),
410 array(
411 'slug' => 'is_active',
412 'compare' => '=',
413 'val' => 1,
414 ),
415 ),
416 );
417
418 $type = array(
419 'slug' => 'type',
420 'compare' => 'IN',
421 'val' => array( 'report', 'reply' ),
422 );
423
424 // private note.
425 if ( $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'pn' ) ) {
426 $type['val'][] = 'note';
427 }
428
429 // logs.
430 if ( $current_user->is_agent && WPSC_Individual_Ticket::has_ticket_cap( 'vl' ) ) {
431 $type['val'][] = 'log';
432 }
433
434 $filters['meta_query'][] = $type;
435 $response = WPSC_Thread::find( $filters, false );
436
437 return new WP_REST_Response( self::modify_thread_response( $response, $ticket, $current_user ), 200 );
438 }
439
440 /**
441 * Add new thread
442 *
443 * @param WP_REST_Request $request - request object.
444 * @return WP_Error|WP_REST_Response
445 */
446 public static function add_new_thread( $request ) {
447
448 $current_user = WPSC_Current_User::$current_user;
449
450 $ticket = new WPSC_Ticket( $request->get_param( 'id' ) );
451 if ( ! $ticket->id || ! $ticket->is_active ) {
452 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
453 }
454
455 WPSC_Individual_Ticket::$ticket = $ticket;
456 $type = $request->get_param( 'type' );
457 $body = $request->get_param( 'body' );
458 $source = $request->get_param( 'source' );
459 $attachments = $request->get_param( 'attachments' );
460
461 // Set reply profile.
462 WPSC_Individual_Ticket::$reply_profile = 'agent';
463 if ( WPSC_Individual_Ticket::is_customer() ) {
464 WPSC_Individual_Ticket::$reply_profile = 'customer';
465 }
466
467 if ( ! (
468 ( $current_user->is_agent && $type == 'reply' && WPSC_Individual_Ticket::has_ticket_cap( 'reply' ) ) ||
469 ( $current_user->is_agent && $type == 'note' && WPSC_Individual_Ticket::has_ticket_cap( 'pn' ) ) ||
470 ( WPSC_Individual_Ticket::is_customer() && $type == 'reply' )
471 ) ) {
472 return new WP_REST_Response( new WP_Error( 'unauthorized', 'You are not authorized!' ), 401 );
473 }
474
475 // submit thread.
476 $thread = WPSC_Thread::insert(
477 array(
478 'ticket' => $ticket->id,
479 'customer' => $current_user->customer->id,
480 'type' => $type,
481 'body' => $body,
482 'attachments' => implode( '|', $attachments ),
483 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
484 'source' => $source,
485 )
486 );
487
488 // save attachments.
489 foreach ( $attachments as $id ) {
490
491 $attachment = new WPSC_Attachment( $id );
492 $attachment->is_active = 1;
493 $attachment->source = $type;
494 $attachment->source_id = $thread->id;
495 $attachment->ticket_id = $ticket->id;
496 $attachment->save();
497 }
498
499 $ticket->date_updated = new DateTime();
500 $ticket->last_reply_on = new DateTime();
501 $ticket->last_reply_by = $current_user->customer->id;
502 $ticket->last_reply_source = $source;
503 $ticket->save();
504
505 if ( $type == 'reply' ) {
506 do_action( 'wpsc_post_reply', $thread );
507 } else {
508 do_action( 'wpsc_submit_note', $thread );
509 }
510
511 $response = self::modify_thread_response( array( 'results' => array( $thread->to_array() ) ), $ticket, $current_user );
512 return new WP_REST_Response( $response['results'][0], 200 );
513 }
514
515 /**
516 * Modify ticket response data appropreate for client side
517 *
518 * @param array $ticket - ticket response array.
519 * @return array
520 */
521 public static function modify_response( $ticket ) {
522
523 $current_user = WPSC_Current_User::$current_user;
524
525 foreach ( $ticket as $slug => $value ) {
526
527 // remove prevent ticket data.
528 if ( in_array( $slug, self::$prevent_data ) ) {
529 unset( $ticket[ $slug ] );
530 continue;
531 }
532
533 $cf = WPSC_Custom_Field::get_cf_by_slug( $slug );
534
535 // ignore if no custom field type class is loaded.
536 if ( ! $cf ) {
537 unset( $ticket[ $slug ] );
538 continue;
539 }
540
541 // ignore agentonly fields for non-agent.
542 if ( ! $current_user->is_agent && $cf->field == 'agentonly' ) {
543 unset( $ticket[ $slug ] );
544 continue;
545 }
546
547 // convert has multiple values to array.
548 if ( WPSC_Ticket::$schema[ $slug ]['has_multiple_val'] ) {
549 if ( $value ) {
550 $ticket[ $slug ] = array_filter(
551 array_map(
552 fn( $val ) => is_numeric( $val ) ? intval( $val ) : $val,
553 explode( '|', $value )
554 )
555 );
556 } else {
557 $ticket[ $slug ] = array();
558 }
559 continue;
560 }
561
562 // empty date value.
563 if ( $value == '0000-00-00 00:00:00' ) {
564 $ticket[ $slug ] = '';
565 }
566
567 // cast numeric fields into integer.
568 if ( is_numeric( $value ) ) {
569 $ticket[ $slug ] = intval( $value );
570 }
571 }
572
573 return apply_filters( 'wpsc_rest_modify_ticket_response', $ticket );
574 }
575
576 /**
577 * Modify thread response
578 *
579 * @param array $response - response array.
580 * @param WPSC_Ticket $ticket - ticket object.
581 * @param WPSC_Current_User $current_user - current user object.
582 * @return array
583 */
584 public static function modify_thread_response( $response, $ticket, $current_user ) {
585
586 if ( $response['results'] ) {
587
588 $log_items = apply_filters(
589 'wpsc_rest_allowed_thread_log_items',
590 array( 'id', 'customer', 'type', 'body', 'date_created' )
591 );
592
593 $reply_items = apply_filters(
594 'wpsc_rest_allowed_thread_reply_items',
595 array( 'id', 'customer', 'type', 'body', 'attachments', 'seen', 'date_created' )
596 );
597
598 $response['results'] = array_map(
599 function ( $thread ) use ( $log_items, $reply_items ) {
600 $items = $thread['type'] == 'log' ? $log_items : $reply_items;
601 $temp = array();
602 foreach ( $items as $item ) {
603 switch ( $item ) {
604 case 'body':
605 if ( $thread['type'] == 'log' ) {
606 $body = json_decode( $thread[ $item ], true );
607 // Handle 'prev' field.
608 if ( isset( $body['prev'] ) ) {
609 if ( is_array( $body['prev'] ) ) {
610 $body['prev'] = implode( ',', $body['prev'] );
611 }
612 if ( is_string( $body['prev'] ) && strpos( $body['prev'], '|' ) !== false ) {
613 $body['prev'] = str_replace( '|', ',', $body['prev'] );
614 }
615 }
616 // Handle 'new' field.
617 if ( isset( $body['new'] ) ) {
618 if ( is_array( $body['new'] ) ) {
619 $body['new'] = implode( ',', $body['new'] );
620 }
621 if ( is_string( $body['new'] ) && strpos( $body['new'], '|' ) !== false ) {
622 $body['new'] = str_replace( '|', ',', $body['new'] );
623 }
624 }
625 $temp[ $item ] = $body;
626 } else {
627 $temp[ $item ] = $thread[ $item ];
628 }
629 break;
630 case 'attachments':
631 $temp[ $item ] = str_replace( '|', ',', $thread[ $item ] );
632 break;
633 default:
634 $temp[ $item ] = $thread[ $item ];
635 }
636 }
637 return $temp;
638 },
639 $response['results']
640 );
641 }
642
643 return $response;
644 }
645
646 /**
647 * Validate id
648 *
649 * @param string $param - parameter value.
650 * @param WP_REST_Request $request - request object.
651 * @param string $key - filter key.
652 * @return boolean
653 */
654 public static function validate_id( $param, $request, $key ) {
655
656 $error = new WP_Error( 'invalid_id', 'Invalid ticket id', array( 'status' => 400 ) );
657 $ticket = new WPSC_Ticket( $param );
658 return $ticket->id ? true : $error;
659 }
660 }
661 endif;
662
663 WPSC_REST_Individual_Ticket::init();
664