PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.4.4
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.4.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 / tickets / class-wpsc-individual-archive-ticket.php

class-wpsc-individual-archive-ticket.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.4.4, at includes/admin/tickets/class-wpsc-individual-archive-ticket.php

1,131 lines 34.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_Individual_Archive_Ticket' ) ) :
7
8 final class WPSC_Individual_Archive_Ticket {
9
10 /**
11 * Current ticket object (ticket model object)
12 *
13 * @var WPSC_Archive_Ticket
14 */
15 public static $ticket;
16
17 /**
18 * Viewing profile of current user of this ticket
19 *
20 * @var string
21 */
22 public static $view_profile;
23
24 /**
25 * Reply profile of current user of this ticket
26 *
27 * @var string
28 */
29 public static $reply_profile;
30
31 /**
32 * Actions for the individual ticket
33 *
34 * @var array
35 */
36 private static $actions;
37
38 /**
39 * Submit actions
40 *
41 * @var array
42 */
43 private static $submit_actions;
44
45 /**
46 * Thread actions
47 *
48 * @var array
49 */
50 private static $thread_actions = array();
51
52 /**
53 * Widget HTML displayed in two places, sidebar and body to maintain responsive behaviour.
54 * By loading their HTML only once, we have optmized load time here.
55 *
56 * @var string
57 */
58 private static $widget_html;
59
60 /**
61 * Set whether ticket url is authenticated or not.
62 *
63 * @var boolean
64 */
65 public static $url_auth = false;
66
67 /**
68 * Archive ticket view
69 *
70 * @var array
71 */
72 public static $readonly = true;
73
74 /**
75 * Initialize the class
76 *
77 * @return void
78 */
79 public static function init() {
80
81 add_action( 'wp_ajax_wpsc_get_individual_archive_ticket', array( __CLASS__, 'layout' ) );
82 add_action( 'wp_ajax_nopriv_wpsc_get_individual_archive_ticket', array( __CLASS__, 'layout' ) );
83
84 // Load older threads.
85 add_action( 'wp_ajax_wpsc_load_older_archive_threads', array( __CLASS__, 'load_older_threads' ) );
86 add_action( 'wp_ajax_nopriv_wpsc_load_older_archive_threads', array( __CLASS__, 'load_older_threads' ) );
87
88 // Restore archive ticket.
89 add_action( 'wp_ajax_wpsc_it_restore_archived', array( __CLASS__, 'set_restore_archived' ) );
90
91 // Thread actions.
92 add_action( 'wp_ajax_wpsc_it_archive_thread_info', array( __CLASS__, 'it_archive_thread_info' ) );
93 add_action( 'wp_ajax_nopriv_wpsc_it_archive_thread_info', array( __CLASS__, 'it_archive_thread_info' ) );
94
95 // Permanently delete archive ticket.
96 add_action( 'wp_ajax_wpsc_iat_delete_permanently', array( __CLASS__, 'iat_delete_permanently' ) );
97 add_action( 'wp_ajax_nopriv_wpsc_iat_delete_permanently', array( __CLASS__, 'iat_delete_permanently' ) );
98 }
99
100 /**
101 * Ajax callback function for individual ticket
102 *
103 * @return void
104 */
105 public static function layout() {
106
107 $current_user = WPSC_Current_User::$current_user;
108
109 $gs = get_option( 'wpsc-gs-general' );
110 self::load_current_ticket();
111 self::load_actions();
112 self::load_widget_html();?>
113 <div class="wpsc-it-container">
114 <div class="wpsc-it-body">
115 <?php
116 self::get_actions();
117 self::get_subject();
118 self::get_mobile_widgets();
119 self::get_thread_section();
120 ?>
121 </div>
122 <div class="wpsc-it-sidebar-widget-container wpsc-hidden-xs wpsc-hidden-sm">
123 <?php echo self::$widget_html; // phpcs:ignore?>
124 </div>
125 </div>
126 <div style="display:none" id="wpsc-ticket-url"><?php echo esc_url( self::$ticket->get_url() ); ?></div>
127 <input type="hidden" id="wpsc-current-ticket" value="<?php echo intval( self::$ticket->id ); ?>">
128 <input type="hidden" id="wpsc-current-agent" value="<?php echo $current_user->is_agent ? intval( $current_user->agent->id ) : 0; ?>">
129 <script>
130
131 var arrow_up = '<?php WPSC_Icons::get( 'chevron-up' ); ?>';
132 var arrow_down = '<?php WPSC_Icons::get( 'chevron-down' ); ?>';
133
134 jQuery(document).ready(function() {
135
136 var widgets_hidden = JSON.parse(localStorage.getItem('wpsc_itw_hidden')) || [];
137 widgets_hidden.forEach(function(widget){
138
139 jQuery('.' + widget).find(".wpsc-widget-body").hide();
140 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_down);
141 });
142
143 jQuery('.wpsc-itw-toggle').on('click',function() {
144
145 var widgets_hidden = JSON.parse(localStorage.getItem('wpsc_itw_hidden')) || [];
146 var widget = jQuery(this).data('widget');
147
148 if(widgets_hidden.includes(widget)){
149
150 jQuery('.' + widget).find(".wpsc-widget-body").slideDown();
151 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_up);
152
153 widgets_hidden = widgets_hidden.filter(function(element) {
154 return element !== widget;
155 });
156 } else {
157
158 jQuery('.' + widget).find(".wpsc-widget-body").slideUp();
159 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_down);
160 widgets_hidden.push(widget);
161 }
162
163 localStorage.setItem('wpsc_itw_hidden', JSON.stringify(widgets_hidden));
164 });
165 });
166 </script>
167 <?php
168 do_action( 'wpsc_it_layout_section', self::$ticket );
169 wp_die();
170 }
171
172 /**
173 * Load current ticket using id we got from ajax request
174 * Ignore phpcs nonce issue as we already checked where it is called from.
175 *
176 * @return void
177 */
178 public static function load_current_ticket() {
179
180 $current_user = WPSC_Current_User::$current_user;
181
182 $id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0; // phpcs:ignore
183 if ( ! $id ) {
184 wp_send_json_error( new WP_Error( '001', 'Unauthorized!' ), 401 );
185 }
186
187 $ticket = new WPSC_Archive_Ticket( $id );
188 if ( ! $ticket->id ) {
189 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
190 }
191 self::$ticket = $ticket;
192
193 // url authentication.
194 $auth_code = isset( $_REQUEST['auth-code'] ) ? sanitize_text_field( $_REQUEST['auth-code'] ) : ''; // phpcs:ignore
195 if ( ! $auth_code ) {
196 $auth_code = isset( $_REQUEST['auth_code'] ) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
197 }
198 if ( $auth_code && $ticket->auth_code == $auth_code ) {
199 self::$url_auth = true;
200 }
201
202 // Set view profile as 'customer' if the user is either a valid logged-in customer or a guest with a valid auth code.
203 if ( self::is_customer() || ( $current_user->is_guest && ! $current_user->is_customer && self::$url_auth ) ) {
204 self::$view_profile = 'customer';
205 }
206
207 // Check whether view profile is an agent.
208 if ( $current_user->is_agent && self::has_ticket_cap( 'at' ) ) {
209 self::$view_profile = 'agent';
210 }
211
212 if ( ! self::$view_profile ) :
213 ?>
214 <div style="align-item:center;" ><h6><?php esc_attr_e( 'Unauthorized access!', 'supportcandy' ); ?></h6></div>
215 <?php
216 wp_die();
217 endif;
218
219 // Check if ticket is deleted and whether current user has access to deleted tickets.
220 if ( ! ( self::$view_profile == 'agent' ) ) {
221 wp_send_json_error( new WP_Error( '003', 'Unauthorized!' ), 401 );
222 }
223 }
224
225 /**
226 * Load actions of ticket which will be used in action bar
227 *
228 * @return void
229 */
230 public static function load_actions() {
231
232 $current_user = WPSC_Current_User::$current_user;
233
234 // Refresh ticket.
235 $actions = array(
236 'refresh' => array(
237 'label' => esc_attr__( 'Refresh', 'supportcandy' ),
238 'callback' => 'wpsc_it_archive_ab_refresh(' . self::$ticket->id . ');',
239 ),
240 );
241
242 // Restore-archived.
243 if ( $current_user->is_agent && self::has_ticket_cap( 'at' ) ) {
244 $actions['restore-archived'] = array(
245 'label' => esc_attr__( 'Restore', 'supportcandy' ),
246 'callback' => 'wpsc_it_restore_archived(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_restore_archived' ) ) . '\');',
247 );
248 }
249
250 // Permanently delete.
251 if ( $current_user->is_agent && $current_user->agent->has_cap( 'at-delete-access' ) ) {
252 $actions['delete-permanently'] = array(
253 'label' => esc_attr__( 'Delete Permanently', 'supportcandy' ),
254 'callback' => 'wpsc_iat_delete_permanently(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_iat_delete_permanently' ) ) . '\');',
255 );
256 }
257 self::$actions = apply_filters( 'wpsc_individual_archive_ticket_actions', $actions, self::$ticket );
258 if ( self::$view_profile == 'agent' ) {
259
260 // Thread actions.
261 $thread_actions = array(
262 'info' => array(
263 'icon' => 'info-circle',
264 'label' => esc_attr__( 'Info', 'supportcandy' ),
265 'callback' => 'wpsc_it_thread_info',
266 ),
267 );
268 self::$thread_actions = apply_filters( 'wpsc_it_archive_thread_actions', $thread_actions );
269 }
270 }
271
272 /**
273 * Load widget at once and save html to $widget_html
274 *
275 * @return void
276 */
277 private static function load_widget_html() {
278
279 $current_user = WPSC_Current_User::$current_user;
280 $widgets = get_option( 'wpsc-ticket-widget' );
281 ob_start();
282
283 do_action( 'wpsc_before_ticket_widget', self::$ticket );
284
285 foreach ( $widgets as $slug => $widget ) :
286 if ( ! $widget['is_enable'] ) {
287 continue;
288 }
289 if ( ! class_exists( $widget['class'] ) || ! method_exists( $widget['class'], 'print_archive_widget' ) ) {
290 continue;
291 }
292 $widget['class']::print_archive_widget( self::$ticket, $widget );
293 endforeach;
294
295 do_action( 'wpsc_after_ticket_widget', self::$ticket );
296
297 self::$widget_html = ob_get_clean();
298 }
299
300 /**
301 * Action bar where verious actions like refresh, close, duplicate, etc. are available
302 *
303 * @return void
304 */
305 public static function get_actions() {
306
307 $actions_arr = array();
308 foreach ( self::$actions as $key => $action ) :
309 ob_start();
310 ?>
311 <span
312 class="wpsc-link wpsc-it-<?php echo esc_attr( $key ); ?>"
313 onclick="<?php echo esc_attr( $action['callback'] ); ?>">
314 <?php echo esc_attr( $action['label'] ); ?>
315 </span>
316 <?php
317 $actions_arr[] = ob_get_clean();
318 endforeach;
319 ?>
320
321 <div class="wpsc-it-action-container">
322 <div class="wpsc-filter-actions">
323 <?php echo implode( '<div class="action-devider"></div>', $actions_arr ); // phpcs:ignore?>
324 </div>
325 </div>
326 <?php
327 }
328
329 /**
330 * Subject bar of the ticket
331 *
332 * @return void
333 */
334 public static function get_subject() {
335 $gs = get_option( 'wpsc-gs-general' );
336 ?>
337 <div class="wpsc-it-body-item wpsc-it-subject-container">
338 <h2><?php echo '[' . esc_attr( $gs['ticket-alice'] ) . esc_attr( self::$ticket->id ) . '] ' . esc_attr( self::$ticket->subject ); ?></h2>
339 </div>
340 <?php
341 }
342
343 /**
344 * Displayed only on sm and xs screens
345 *
346 * @return void
347 */
348 public static function get_mobile_widgets() {
349 ?>
350 <div class="wpsc-it-body-item wpsc-it-mobile-widget-container wpsc-visible-xs wpsc-visible-sm">
351 <div class="wpsc-it-mob-widget-trigger-btn" onclick="wpsc_toggle_mob_it_widgets();">
352 <h2><?php esc_attr_e( 'Ticket Details', 'supportcandy' ); ?></h2>
353 <span class="down"><?php WPSC_Icons::get( 'chevron-down' ); ?></span>
354 <span class="up" style="display:none;"><?php WPSC_Icons::get( 'chevron-up' ); ?></span>
355 </div>
356 <div class="wpsc-it-mob-widgets-inner-container" data-status="0" style="display: none;">
357 <?php echo self::$widget_html; // phpcs:ignore?>
358 </div>
359 </div>
360 <?php
361 }
362
363 /**
364 * Get threads section of individual ticket
365 *
366 * @return void
367 */
368 public static function get_thread_section() {
369
370 $gs = get_option( 'wpsc-gs-general' );
371 $filters = array(
372 'meta_query' => array(
373 'relation' => 'AND',
374 array(
375 'slug' => 'ticket',
376 'compare' => '=',
377 'val' => self::$ticket->id,
378 ),
379 ),
380 'orderby' => 'id',
381 'order' => 'DESC',
382 );
383
384 $thread_types = array(
385 'slug' => 'type',
386 'compare' => 'IN',
387 'val' => array( 'report', 'reply' ),
388 );
389
390 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
391 $thread_types['val'][] = 'note';
392 }
393 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
394 $thread_types['val'][] = 'log';
395 }
396
397 $filters['meta_query'][] = $thread_types;
398
399 $filters = apply_filters( 'wpsc_filter_ticket_threads', $filters );
400
401 $response = WPSC_Archive_Thread::find( $filters );
402 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
403
404 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'bottom' ) :
405 ?>
406 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
407 <button class="wpsc-button small secondary" onclick="wpsc_load_older_archive_threads(this, <?php echo esc_attr( self::$ticket->id ); ?>);"><?php esc_attr_e( 'Load older communications', 'supportcandy' ); ?></button>
408 </div>
409 <?php
410 endif;
411 ?>
412
413 <div class="wpsc-it-body-item wpsc-it-thread-section-container">
414 <?php
415 if ( $gs['reply-form-position'] == 'top' ) {
416 foreach ( $response['results'] as $thread ) {
417 if ( $thread->type == 'log' ) {
418 self::print_log( $thread );
419 } else {
420 self::print_thread( $thread );
421 }
422 }
423 } else {
424 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
425 $thread = $response['results'][ $i ];
426 if ( $thread->type == 'log' ) {
427 self::print_log( $thread );
428 } else {
429 self::print_thread( $thread );
430 }
431 }
432 }
433 ?>
434 </div>
435 <script>
436 jQuery(document).find('.thread-text').each(function(){
437 var height = parseInt(jQuery(this).height());
438 <?php
439 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
440 if ( $advanced['view-more'] ) {
441 ?>
442 if( height > 100){
443 jQuery(this).height(100);
444 jQuery(this).parent().find('.wpsc-ticket-thread-expander').text(supportcandy.translations.view_more);
445 jQuery(this).parent().find('.wpsc-ticket-thread-expander').show();
446 }
447 <?php
448 } else {
449 ?>
450 jQuery(this).parent().find('.thread-text').height('auto');
451 <?php
452 }
453 ?>
454 });
455 supportcandy.threads = {last_thread: <?php echo esc_attr( $last_id ); ?>}
456 </script>
457 <?php
458 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'top' ) :
459 ?>
460 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
461 <button class="wpsc-button small secondary" onclick="wpsc_load_older_archive_threads(this, <?php echo esc_attr( self::$ticket->id ); ?>);"><?php esc_attr_e( 'Load older communications', 'supportcandy' ); ?></button>
462 </div>
463 <?php
464 endif;
465 }
466
467 /**
468 * Print thread
469 *
470 * @param WPSC_Archive_Thread $thread - thread object.
471 * @return void
472 */
473 public static function print_thread( $thread ) {
474
475 // If thread is of type "Private Note", return if current user does not have permission to view logs.
476 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
477 return;
478 }
479
480 // If thread is deleted, show only if current user has view log permission. It will show deleteted log with view content link.
481 if ( ! $thread->is_active && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
482 return;
483 }
484
485 $settings = get_option( 'wpsc-gs-general' );
486 $current_user = WPSC_Current_User::$current_user;
487 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
488
489 $now = new DateTime();
490 $date = $thread->date_created->setTimezone( wp_timezone() );
491 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
492 $time_title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
493 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
494 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
495
496 if (
497 ! is_object( $thread->seen ) &&
498 $current_user->customer == $thread->ticket->customer &&
499 in_array( $thread->type, array( 'report', 'reply' ) )
500 ) {
501
502 $today = new DateTime();
503 $thread->seen = $today->format( 'Y-m-d H:i:s' );
504 $thread->save();
505 }
506
507 $classes = $thread->type == 'note' ? 'note' : 'reply';
508 if ( in_array( $thread->type, array( 'report', 'reply' ) ) ) {
509 $thread_user = get_user_by( 'email', $thread->customer->email );
510 $user_class = $thread_user && $thread_user->has_cap( 'wpsc_agent' ) ? 'agent' : 'customer';
511 $classes = $classes . ' ' . $user_class;
512 }
513 $classes = apply_filters( 'wpsc_it_thread_add_classes', $classes, $thread );
514 self::$thread_actions = apply_filters( 'wpsc_it_filter_thread_actions', self::$thread_actions, $thread );
515 ?>
516
517 <div class="wpsc-thread <?php echo esc_attr( $classes ); ?> <?php echo esc_attr( $thread->id ); ?>">
518
519 <div class="thread-avatar">
520 <?php echo get_avatar( $thread->customer->email, 32 ); ?>
521 </div>
522
523 <div class="thread-body">
524
525 <div class="thread-header">
526
527 <div class="user-info">
528 <div style="display: flex;">
529 <h2 class="user-name"><?php echo esc_attr( $thread->customer->name ) . ' '; ?></h2>
530 <h2>
531 <small class="thread-type">
532 <i>
533 <?php
534 switch ( $thread->type ) {
535
536 case 'report':
537 esc_attr_e( 'reported', 'supportcandy' );
538 break;
539
540 case 'reply':
541 esc_attr_e( 'replied', 'supportcandy' );
542 break;
543
544 case 'note':
545 esc_attr_e( 'added a note', 'supportcandy' );
546 break;
547 }
548 ?>
549 </i>
550 </small>
551 </h2>
552 </div>
553 <span class="thread-time" title="<?php echo esc_attr( $time_title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
554 </div>
555 <?php
556 do_action( 'wpsc_it_thread_header', $thread );
557 if ( $thread->is_active ) {
558 ?>
559 <div class="actions">
560 <?php
561 foreach ( self::$thread_actions as $action ) :
562 ?>
563 <span title="<?php echo esc_attr( $action['label'] ); ?>" onclick="<?php echo esc_attr( $action['callback'] ) . '(this, ' . esc_attr( self::$ticket->id ) . ',' . esc_attr( $thread->id ) . ', \'' . esc_attr( wp_create_nonce( $action['callback'] ) ) . '\', \'archive_ticket\')'; ?>">
564 <?php WPSC_Icons::get( $action['icon'] ); ?>
565 </span>
566 <?php
567 endforeach;
568 ?>
569 </div>
570 <?php
571 }
572 ?>
573
574 </div>
575
576 <div class="thread-text">
577 <?php
578 if ( $thread->is_active ) {
579 echo wp_kses_post( $thread->body );
580 } else {
581 $logs = $thread->get_logs();
582 ?>
583 <i>
584 <?php
585 printf(
586 /* translators: %1$s: customer name, %2$s: datetime */
587 esc_attr__( 'This thread was deleted by %1$s on %2$s.', 'supportcandy' ),
588 '<strong>' . esc_attr( $logs[0]->modified_by->name ) . '</strong>',
589 esc_attr( $logs[0]->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
590 );
591 ?>
592 <a href="javascript:wpsc_it_view_deleted_thread(<?php echo intval( self::$ticket->id ); ?>, <?php echo intval( $thread->id ); ?>, '<?php echo esc_attr( wp_create_nonce( 'wpsc_it_view_deleted_thread' ) ); ?>')">
593 <?php esc_attr_e( 'View thread!', 'supportcandy' ); ?>
594 </a>
595 </i>
596 <?php
597 }
598 ?>
599 </div>
600
601 <?php
602 if ( $advanced['view-more'] ) {
603 ?>
604 <div class="wpsc-ticket-thread-expander" onclick="wpsc_ticket_thread_expander_toggle(this);" style="display: none;">
605 <?php esc_attr_e( 'View More ...', 'supportcandy' ); ?>
606 </div>
607 <?php
608 }
609
610 // Thread attachments.
611 if ( $thread->is_active && $thread->attachments ) {
612 ?>
613 <div class="wpsc-thread-attachments">
614 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
615 <?php
616 foreach ( $thread->attachments as $attachment ) {
617 ?>
618 <div class="wpsc-attachment-item">
619 <?php
620 $download_url = site_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&auth_code=' . $thread->ticket->auth_code;
621 ?>
622 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
623 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
624 </div>
625 <?php
626 }
627 ?>
628 </div>
629 <?php
630 }
631
632 // Thread logs.
633 $logs = $thread->get_logs();
634 if ( $thread->is_active && self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && $logs ) {
635 ?>
636 <div class="wpsc-thread-logs">
637 <?php
638 foreach ( $logs as $log ) {
639
640 $log_body = json_decode( $log->body );
641 ?>
642 <div class="wpsc-thread-log-item">
643 <?php
644 switch ( $log_body->type ) {
645
646 case 'modify':
647 printf(
648 /* translators: %1$s: customer name, %2$s: date time */
649 esc_attr__( 'Modified by %1$s on %2$s.', 'supportcandy' ),
650 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
651 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
652 );
653 ?>
654 <a href="javascript:wpsc_it_view_thread_log(<?php echo intval( self::$ticket->id ); ?>, <?php echo intval( $thread->id ); ?>, <?php echo intval( $log->id ); ?>, '<?php echo esc_attr( wp_create_nonce( 'wpsc_it_view_thread_log' ) ); ?>');" class="wpsc-link">
655 <?php esc_attr_e( 'View change', 'supportcandy' ); ?>
656 </a>
657 <?php
658 break;
659
660 case 'delete':
661 printf(
662 /* translators: %1$s: customer name, %2$s: date time */
663 esc_attr__( 'Deleted by %1$s on %2$s', 'supportcandy' ),
664 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
665 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
666 );
667 break;
668
669 case 'restore':
670 printf(
671 /* translators: %1$s: customer name, %2$s: date time */
672 esc_attr__( 'Restored by %1$s on %2$s', 'supportcandy' ),
673 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
674 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
675 );
676 break;
677 }
678 ?>
679 </div>
680 <?php
681 }
682 ?>
683 </div>
684 <?php
685 }
686 ?>
687
688 </div>
689
690 </div>
691 <?php
692 }
693
694 /**
695 * Print thread log
696 *
697 * @param WPSC_Archive_Thread $thread - thread object.
698 * @return void
699 */
700 public static function print_log( $thread ) {
701
702 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
703 return;
704 }
705
706 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
707 $now = new DateTime();
708 $date = $thread->date_created->setTimezone( wp_timezone() );
709 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
710 $title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
711 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
712 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
713
714 $body = json_decode( $thread->body );
715 $is_json = ( json_last_error() == JSON_ERROR_NONE ) ? true : false;
716
717 if ( $is_json ) {
718
719 $cf = WPSC_Custom_Field::get_cf_by_slug( $body->slug );
720 if ( ! $cf ) {
721 return;
722 }
723 ?>
724 <div class="wpsc-thread log">
725 <div class="thread-avatar">
726 <?php
727 if ( $thread->customer ) {
728 echo get_avatar( $thread->customer->email, 32 );
729 } else {
730 WPSC_Icons::get( 'system' );
731 }
732 ?>
733 </div>
734 <div class="thread-body">
735 <div class="thread-header">
736 <div class="user-info">
737 <div>
738 <?php
739 if ( $thread->customer ) {
740
741 printf(
742 /* translators: %1$s: User Name, %2$s: Field Name */
743 esc_attr__( '%1$s changed the %2$s', 'supportcandy' ),
744 '<strong>' . esc_attr( $thread->customer->name ) . '</strong>',
745 '<strong>' . esc_attr( $cf->name ) . '</strong>'
746 );
747
748 } else {
749
750 printf(
751 /* translators: %1$s: Field Name */
752 esc_attr__( 'The %1$s has been changed', 'supportcandy' ),
753 '<strong>' . esc_attr( $cf->name ) . '</strong>'
754 );
755 }
756 ?>
757 </div>
758 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
759 </div>
760 </div>
761 <div class="wpsc-log-diff">
762 <div class="lhs"><?php $cf->type::print_val( $cf, $body->prev ); ?></div>
763 <div class="transform-icon">
764 <?php
765 if ( is_rtl() ) {
766 WPSC_Icons::get( 'arrow-left' );
767 } else {
768 WPSC_Icons::get( 'arrow-right' );
769 }
770 ?>
771 </div>
772 <div class="rhs"><?php $cf->type::print_val( $cf, $body->new ); ?></div>
773 </div>
774 </div>
775 </div>
776 <?php
777
778 } else {
779
780 ?>
781 <div class="wpsc-thread log">
782 <div class="thread-avatar">
783 <?php
784 WPSC_Icons::get( 'system' );
785 ?>
786 </div>
787 <div class="thread-body">
788 <div><?php echo wp_kses_post( $thread->body ); ?></div>
789 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
790 </div>
791 </div>
792 <?php
793 }
794 }
795
796 /**
797 * Check whether current user is customer or not
798 *
799 * @return boolean
800 */
801 public static function is_customer() {
802
803 $current_user = WPSC_Current_User::$current_user;
804 if ( ! $current_user->is_customer ) {
805 return false;
806 }
807
808 $adv_setting = get_option( 'wpsc-ms-advanced-settings' );
809 if ( $adv_setting['public-mode'] && ! $current_user->is_agent ) {
810 return true;
811 }
812
813 $allowed_customers = apply_filters( 'wpsc_non_agent_ticket_customers_allowed', array( self::$ticket->customer->id ), self::$ticket );
814 if ( in_array( $current_user->customer->id, $allowed_customers ) ) {
815 return true;
816 }
817
818 return false;
819 }
820
821 /**
822 * Agent ticket access for cap
823 *
824 * @param string $cap - capability name.
825 * @return boolean
826 */
827 public static function has_ticket_cap( $cap ) {
828
829 $current_user = WPSC_Current_User::$current_user;
830
831 $assigned_agents = array_map(
832 fn ( $agent ) => $agent->id,
833 self::$ticket->assigned_agent
834 );
835
836 $flag = false;
837 if (
838 (
839 ! self::$ticket->assigned_agent &&
840 $current_user->agent->has_cap( $cap . '-unassigned' )
841 ) ||
842 (
843 in_array( $current_user->agent->id, $assigned_agents ) &&
844 $current_user->agent->has_cap( $cap . '-assigned-me' )
845 ) ||
846 (
847 self::$ticket->assigned_agent &&
848 ! in_array( $current_user->agent->id, $assigned_agents ) &&
849 $current_user->agent->has_cap( $cap . '-assigned-others' )
850 )
851 ) {
852 $flag = true;
853 }
854
855 return apply_filters( 'wpsc_it_has_ticket_cap', $flag, self::$ticket, $cap );
856 }
857
858 /**
859 * Load older threads
860 *
861 * @return void
862 */
863 public static function load_older_threads() {
864
865 self::load_current_ticket();
866 self::load_actions();
867
868 $last_thread = isset( $_POST['last_thread'] ) ? intval( $_POST['last_thread'] ) : 0; // phpcs:ignore
869 if ( ! $last_thread ) {
870 wp_send_json_error( new WP_Error( '004', 'Bad request!' ), 401 );
871 }
872
873 $gs = get_option( 'wpsc-gs-general' );
874 $filters = array(
875 'meta_query' => array(
876 'relation' => 'AND',
877 array(
878 'slug' => 'ticket',
879 'compare' => '=',
880 'val' => self::$ticket->id,
881 ),
882 array(
883 'slug' => 'id',
884 'compare' => '<',
885 'val' => $last_thread,
886 ),
887 ),
888 'orderby' => 'id',
889 'order' => 'DESC',
890 );
891
892 $thread_types = array(
893 'slug' => 'type',
894 'compare' => 'IN',
895 'val' => array( 'report', 'reply' ),
896 );
897
898 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
899 $thread_types['val'][] = 'note';
900 }
901 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
902 $thread_types['val'][] = 'log';
903 }
904
905 $filters['meta_query'][] = $thread_types;
906
907 $response = WPSC_Archive_Thread::find( $filters );
908 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
909
910 ob_start();
911 if ( $gs['reply-form-position'] == 'top' ) {
912 foreach ( $response['results'] as $thread ) {
913 if ( $thread->type == 'log' ) {
914 self::print_log( $thread );
915 } else {
916 self::print_thread( $thread );
917 }
918 }
919 } else {
920 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
921 $thread = $response['results'][ $i ];
922 if ( $thread->type == 'log' ) {
923 self::print_log( $thread );
924 } else {
925 self::print_thread( $thread );
926 }
927 }
928 }
929 $threads = ob_get_clean();
930
931 $html_response = array(
932 'last_thread' => $last_id,
933 'has_next_page' => $response['has_next_page'],
934 'threads' => $threads,
935 );
936
937 wp_send_json( $html_response );
938 wp_die();
939 }
940
941 /**
942 * Restore the given ticket by moving it and its threads to the ticket/thread tables.
943 *
944 * @return void
945 */
946 public static function set_restore_archived() {
947
948 if ( check_ajax_referer( 'wpsc_it_restore_archived', '_ajax_nonce', false ) != 1 ) {
949 wp_send_json_error( 'Unauthorized request!', 401 );
950 }
951
952 $ticket_id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0;
953 if ( ! $ticket_id ) {
954 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
955 }
956
957 $ticket = new WPSC_Archive_Ticket( $ticket_id );
958 if ( ! $ticket->id ) {
959 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
960 }
961
962 $success = WPSC_Archive_Ticket::restore_archive_ticket( $ticket );
963 if ( $success ) {
964 $ticket = new WPSC_Ticket( $ticket->id );
965 do_action( 'wpsc_ticket_restore', $ticket );
966 }
967 }
968
969 /**
970 * Get thread info
971 *
972 * @return void
973 */
974 public static function it_archive_thread_info() {
975
976 if ( check_ajax_referer( 'wpsc_it_thread_info', '_ajax_nonce', false ) != 1 ) {
977 wp_send_json_error( 'Unauthorized request!', 401 );
978 }
979
980 self::load_current_ticket();
981
982 $current_user = WPSC_Current_User::$current_user;
983
984 if ( ! ( self::$view_profile == 'agent' ) ) {
985 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
986 }
987
988 $title = esc_attr__( 'Thread info', 'supportcandy' );
989
990 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
991 if ( ! $thread_id ) {
992 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
993 }
994
995 $thread = new WPSC_Archive_Thread( $thread_id );
996 if ( ! $thread->id ) {
997 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
998 }
999
1000 if ( self::$ticket != $thread->ticket ) {
1001 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1002 }
1003
1004 $settings = get_option( 'wpsc-gs-general', array() );
1005 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1006 $ip_address = $thread->ip_address ? $thread->ip_address : esc_attr__( 'Not Applicable', 'supportcandy' );
1007 $os = $thread->os ? $thread->os : esc_attr__( 'Not Applicable', 'supportcandy' );
1008 $browser = $thread->browser ? $thread->browser : esc_attr__( 'Not Applicable', 'supportcandy' );
1009 $source = $thread->source && isset( WPSC_DF_Source::$sources[ $thread->source ] ) ? WPSC_DF_Source::$sources[ $thread->source ] : esc_attr__( 'Not Applicable', 'supportcandy' );
1010
1011 ob_start();
1012 ?>
1013 <div class="wpsc-thread-info">
1014
1015 <div class="info-list-item">
1016 <div class="info-label"><?php esc_attr_e( 'Name', 'supportcandy' ); ?>:</div>
1017 <div class="info-val"><?php echo esc_attr( $thread->customer->name ); ?></div>
1018 </div>
1019 <?php
1020
1021 if ( $current_user->is_agent && in_array( $current_user->agent->role, $settings['allow-ar-thread-email'] ) ) {
1022 ?>
1023 <div class="info-list-item">
1024 <div class="info-label"><?php esc_attr_e( 'Email Address', 'supportcandy' ); ?>:</div>
1025 <div class="info-val"><?php echo esc_attr( $thread->customer->email ); ?></div>
1026 </div>
1027 <?php
1028 }
1029 ?>
1030
1031 <div class="info-list-item">
1032 <div class="info-label"><?php esc_attr_e( 'Source', 'supportcandy' ); ?>:</div>
1033 <div class="info-val"><?php echo esc_attr( $source ); ?></div>
1034 </div>
1035
1036 <div class="info-list-item">
1037 <div class="info-label"><?php esc_attr_e( 'IP Address', 'supportcandy' ); ?>:</div>
1038 <div class="info-val"><?php echo esc_attr( $ip_address ); ?></div>
1039 </div>
1040
1041 <div class="info-list-item">
1042 <div class="info-label"><?php esc_attr_e( 'Browser', 'supportcandy' ); ?>:</div>
1043 <div class="info-val"><?php echo esc_attr( $browser ); ?></div>
1044 </div>
1045
1046 <div class="info-list-item">
1047 <div class="info-label"><?php esc_attr_e( 'Operating System', 'supportcandy' ); ?>:</div>
1048 <div class="info-val"><?php echo esc_attr( $os ); ?></div>
1049 </div>
1050
1051 <?php
1052 if ( $thread->type == 'reply' && $thread->customer != $thread->ticket->customer ) {
1053 ?>
1054 <div class="info-list-item">
1055 <div class="info-label"><?php esc_attr_e( 'Seen', 'supportcandy' ); ?>:</div>
1056 <div class="info-val">
1057 <?php echo is_object( $thread->seen ) ? esc_attr( $thread->seen->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) ) : esc_attr_e( 'No', 'supportcandy' ); ?>
1058 </div>
1059 </div>
1060 <?php
1061 }
1062 do_action( 'wpsc_thread_info_body', $thread );
1063 ?>
1064 </div>
1065 <?php
1066 $body = ob_get_clean();
1067
1068 ob_start();
1069 ?>
1070 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
1071 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
1072 </button>
1073 <?php
1074 $footer = ob_get_clean();
1075
1076 $response = array(
1077 'title' => $title,
1078 'body' => $body,
1079 'footer' => $footer,
1080 );
1081 wp_send_json( $response );
1082 }
1083
1084 /**
1085 * Permanently delete ticket ajax request
1086 *
1087 * @return void
1088 */
1089 public static function iat_delete_permanently() {
1090
1091 if ( check_ajax_referer( 'wpsc_iat_delete_permanently', '_ajax_nonce', false ) != 1 ) {
1092 wp_send_json_error( 'Unauthorized request!', 401 );
1093 }
1094
1095 $current_user = WPSC_Current_User::$current_user;
1096 if ( ! $current_user->is_agent ) {
1097 wp_send_json_error( 'Unauthorized request!', 401 );
1098 }
1099
1100 $ticket_id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0;
1101 if ( ! $ticket_id ) {
1102 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1103 }
1104
1105 $ticket = new WPSC_Archive_Ticket( $ticket_id );
1106 if ( ! $ticket->id ) {
1107 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
1108 }
1109
1110 if ( ! $current_user->agent->has_cap( 'at-delete-access' ) ) {
1111 wp_send_json_error( 'Unauthorized request!', 401 );
1112 }
1113
1114 self::delete_archive_ticket( $ticket );
1115 }
1116
1117 /**
1118 * Delete ticket permanently.
1119 *
1120 * @param WPSC_Ticket $ticket - ticket object.
1121 * @return void
1122 */
1123 public static function delete_archive_ticket( $ticket ) {
1124
1125 WPSC_Archive_Ticket::destroy( $ticket );
1126 do_action( 'wpsc_ticket_delete_permanently', $ticket );
1127 }
1128 }
1129 endif;
1130 WPSC_Individual_Archive_Ticket::init();
1131