PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.5.2
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.5.2
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.5.2, at includes/admin/tickets/class-wpsc-individual-archive-ticket.php

1,160 lines 35.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' ) && ! WPSC_Ticket_Restrictions_Manager::is_restricted( self::$ticket ) ) {
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
418 // Default caller based on type.
419 if ( $thread->type === 'log' ) {
420 $default_caller = array( __CLASS__, 'print_log' );
421 } else {
422 $default_caller = array( __CLASS__, 'print_thread' );
423 }
424
425 // Allow addons or core to override the method.
426 $caller = apply_filters(
427 'wpsc_it_print_thread_caller',
428 $default_caller,
429 $thread->type,
430 $thread
431 );
432
433 // Execute handler if valid, otherwise fallback to default handler.
434 if ( ! empty( $caller ) && is_callable( $caller ) ) {
435 call_user_func( $caller, $thread );
436 }
437 }
438 } else {
439 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
440 $thread = $response['results'][ $i ];
441 // Default caller based on type.
442 if ( $thread->type === 'log' ) {
443 $default_caller = array( __CLASS__, 'print_log' );
444 } else {
445 $default_caller = array( __CLASS__, 'print_thread' );
446 }
447
448 // Allow addons or core to override the method.
449 $caller = apply_filters(
450 'wpsc_it_print_thread_caller',
451 $default_caller,
452 $thread->type,
453 $thread
454 );
455
456 // Execute handler if valid, otherwise fallback to default handler.
457 if ( ! empty( $caller ) && is_callable( $caller ) ) {
458 call_user_func( $caller, $thread );
459 }
460 }
461 }
462 ?>
463 </div>
464 <script>
465 jQuery(document).find('.thread-text').each(function(){
466 var height = parseInt(jQuery(this).height());
467 <?php
468 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
469 if ( $advanced['view-more'] ) {
470 ?>
471 if( height > 100){
472 jQuery(this).height(100);
473 jQuery(this).parent().find('.wpsc-ticket-thread-expander').text(supportcandy.translations.view_more);
474 jQuery(this).parent().find('.wpsc-ticket-thread-expander').show();
475 }
476 <?php
477 } else {
478 ?>
479 jQuery(this).parent().find('.thread-text').height('auto');
480 <?php
481 }
482 ?>
483 });
484 supportcandy.threads = {last_thread: <?php echo esc_attr( $last_id ); ?>}
485 </script>
486 <?php
487 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'top' ) :
488 ?>
489 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
490 <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>
491 </div>
492 <?php
493 endif;
494 }
495
496 /**
497 * Print thread
498 *
499 * @param WPSC_Archive_Thread $thread - thread object.
500 * @return void
501 */
502 public static function print_thread( $thread ) {
503
504 // If thread is of type "Private Note", return if current user does not have permission to view logs.
505 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
506 return;
507 }
508
509 // If thread is deleted, show only if current user has view log permission. It will show deleteted log with view content link.
510 if ( ! $thread->is_active && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
511 return;
512 }
513
514 $settings = get_option( 'wpsc-gs-general' );
515 $current_user = WPSC_Current_User::$current_user;
516 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
517
518 $now = new DateTime();
519 $date = $thread->date_created->setTimezone( wp_timezone() );
520 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
521 $time_title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
522 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
523 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
524
525 if (
526 ! is_object( $thread->seen ) &&
527 $current_user->customer == $thread->ticket->customer &&
528 in_array( $thread->type, array( 'report', 'reply' ) )
529 ) {
530
531 $today = new DateTime();
532 $thread->seen = $today->format( 'Y-m-d H:i:s' );
533 $thread->save();
534 }
535
536 $classes = $thread->type == 'note' ? 'note' : 'reply';
537 if ( in_array( $thread->type, array( 'report', 'reply' ) ) ) {
538 $thread_user = get_user_by( 'email', $thread->customer->email );
539 $user_class = $thread_user && $thread_user->has_cap( 'wpsc_agent' ) ? 'agent' : 'customer';
540 $classes = $classes . ' ' . $user_class;
541 }
542 $classes = apply_filters( 'wpsc_it_thread_add_classes', $classes, $thread );
543 self::$thread_actions = apply_filters( 'wpsc_it_filter_thread_actions', self::$thread_actions, $thread );
544 ?>
545
546 <div class="wpsc-thread <?php echo esc_attr( $classes ); ?> <?php echo esc_attr( $thread->id ); ?>">
547
548 <div class="thread-avatar">
549 <?php echo get_avatar( $thread->customer->email, 32 ); ?>
550 </div>
551
552 <div class="thread-body">
553
554 <div class="thread-header">
555
556 <div class="user-info">
557 <div style="display: flex;">
558 <h2 class="user-name"><?php echo esc_attr( $thread->customer->name ) . ' '; ?></h2>
559 <h2>
560 <small class="thread-type">
561 <i>
562 <?php
563 switch ( $thread->type ) {
564
565 case 'report':
566 esc_attr_e( 'reported', 'supportcandy' );
567 break;
568
569 case 'reply':
570 esc_attr_e( 'replied', 'supportcandy' );
571 break;
572
573 case 'note':
574 esc_attr_e( 'added a note', 'supportcandy' );
575 break;
576 }
577 ?>
578 </i>
579 </small>
580 </h2>
581 </div>
582 <span class="thread-time" title="<?php echo esc_attr( $time_title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
583 </div>
584 <?php
585 do_action( 'wpsc_it_thread_header', $thread );
586 if ( $thread->is_active ) {
587 ?>
588 <div class="actions">
589 <?php
590 foreach ( self::$thread_actions as $action ) :
591 ?>
592 <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\')'; ?>">
593 <?php WPSC_Icons::get( $action['icon'] ); ?>
594 </span>
595 <?php
596 endforeach;
597 ?>
598 </div>
599 <?php
600 }
601 ?>
602
603 </div>
604
605 <div class="thread-text">
606 <?php
607 if ( $thread->is_active ) {
608 echo wp_kses_post( $thread->body );
609 } else {
610 $logs = $thread->get_logs();
611 ?>
612 <i>
613 <?php
614 printf(
615 /* translators: %1$s: customer name, %2$s: datetime */
616 esc_attr__( 'This thread was deleted by %1$s on %2$s.', 'supportcandy' ),
617 '<strong>' . esc_attr( $logs[0]->modified_by->name ) . '</strong>',
618 esc_attr( $logs[0]->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
619 );
620 ?>
621 <span class="wpsc-link" onclick="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' ) ); ?>')">
622 <?php esc_attr_e( 'View thread!', 'supportcandy' ); ?>
623 </span>
624 </i>
625 <?php
626 }
627 ?>
628 </div>
629
630 <?php
631 if ( $advanced['view-more'] ) {
632 ?>
633 <div class="wpsc-ticket-thread-expander" onclick="wpsc_ticket_thread_expander_toggle(this);" style="display: none;">
634 <?php esc_attr_e( 'View More ...', 'supportcandy' ); ?>
635 </div>
636 <?php
637 }
638
639 // Thread attachments.
640 if ( $thread->is_active && $thread->attachments ) {
641 ?>
642 <div class="wpsc-thread-attachments">
643 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
644 <?php
645 foreach ( $thread->attachments as $attachment ) {
646 ?>
647 <div class="wpsc-attachment-item">
648 <?php
649 $download_url = site_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&auth_code=' . $thread->ticket->auth_code;
650 ?>
651 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
652 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
653 </div>
654 <?php
655 }
656 ?>
657 </div>
658 <?php
659 }
660
661 // Thread logs.
662 $logs = $thread->get_logs();
663 if ( $thread->is_active && self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && $logs ) {
664 ?>
665 <div class="wpsc-thread-logs">
666 <?php
667 foreach ( $logs as $log ) {
668
669 $log_body = json_decode( $log->body );
670 ?>
671 <div class="wpsc-thread-log-item">
672 <?php
673 switch ( $log_body->type ) {
674
675 case 'modify':
676 printf(
677 /* translators: %1$s: customer name, %2$s: date time */
678 esc_attr__( 'Modified by %1$s on %2$s.', 'supportcandy' ),
679 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
680 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
681 );
682 ?>
683 <span class="wpsc-link" onclick="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' ) ); ?>');">
684 <?php esc_attr_e( 'View change', 'supportcandy' ); ?>
685 </span>
686 <?php
687 break;
688
689 case 'delete':
690 printf(
691 /* translators: %1$s: customer name, %2$s: date time */
692 esc_attr__( 'Deleted by %1$s on %2$s', 'supportcandy' ),
693 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
694 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
695 );
696 break;
697
698 case 'restore':
699 printf(
700 /* translators: %1$s: customer name, %2$s: date time */
701 esc_attr__( 'Restored by %1$s on %2$s', 'supportcandy' ),
702 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
703 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
704 );
705 break;
706 }
707 ?>
708 </div>
709 <?php
710 }
711 ?>
712 </div>
713 <?php
714 }
715 ?>
716
717 </div>
718
719 </div>
720 <?php
721 }
722
723 /**
724 * Print thread log
725 *
726 * @param WPSC_Archive_Thread $thread - thread object.
727 * @return void
728 */
729 public static function print_log( $thread ) {
730
731 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
732 return;
733 }
734
735 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
736 $now = new DateTime();
737 $date = $thread->date_created->setTimezone( wp_timezone() );
738 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
739 $title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
740 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
741 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
742
743 $body = json_decode( $thread->body );
744 $is_json = ( json_last_error() == JSON_ERROR_NONE ) ? true : false;
745
746 if ( $is_json ) {
747
748 $cf = WPSC_Custom_Field::get_cf_by_slug( $body->slug );
749 if ( ! $cf ) {
750 return;
751 }
752 ?>
753 <div class="wpsc-thread log">
754 <div class="thread-avatar">
755 <?php
756 if ( $thread->customer ) {
757 echo get_avatar( $thread->customer->email, 32 );
758 } else {
759 WPSC_Icons::get( 'system' );
760 }
761 ?>
762 </div>
763 <div class="thread-body">
764 <div class="thread-header">
765 <div class="user-info">
766 <div>
767 <?php
768 if ( $thread->customer ) {
769
770 printf(
771 /* translators: %1$s: User Name, %2$s: Field Name */
772 esc_attr__( '%1$s changed the %2$s', 'supportcandy' ),
773 '<strong>' . esc_attr( $thread->customer->name ) . '</strong>',
774 '<strong>' . esc_attr( $cf->name ) . '</strong>'
775 );
776
777 } else {
778
779 printf(
780 /* translators: %1$s: Field Name */
781 esc_attr__( 'The %1$s has been changed', 'supportcandy' ),
782 '<strong>' . esc_attr( $cf->name ) . '</strong>'
783 );
784 }
785 ?>
786 </div>
787 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
788 </div>
789 </div>
790 <div class="wpsc-log-diff">
791 <div class="lhs"><?php $cf->type::print_val( $cf, $body->prev ); ?></div>
792 <div class="transform-icon">
793 <?php
794 if ( is_rtl() ) {
795 WPSC_Icons::get( 'arrow-left' );
796 } else {
797 WPSC_Icons::get( 'arrow-right' );
798 }
799 ?>
800 </div>
801 <div class="rhs"><?php $cf->type::print_val( $cf, $body->new ); ?></div>
802 </div>
803 </div>
804 </div>
805 <?php
806
807 } else {
808
809 ?>
810 <div class="wpsc-thread log">
811 <div class="thread-avatar">
812 <?php
813 WPSC_Icons::get( 'system' );
814 ?>
815 </div>
816 <div class="thread-body">
817 <div><?php echo wp_kses_post( $thread->body ); ?></div>
818 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
819 </div>
820 </div>
821 <?php
822 }
823 }
824
825 /**
826 * Check whether current user is customer or not
827 *
828 * @return boolean
829 */
830 public static function is_customer() {
831
832 $current_user = WPSC_Current_User::$current_user;
833 if ( ! $current_user->is_customer ) {
834 return false;
835 }
836
837 $adv_setting = get_option( 'wpsc-ms-advanced-settings' );
838 if ( $adv_setting['public-mode'] && ! $current_user->is_agent ) {
839 return true;
840 }
841
842 $allowed_customers = apply_filters( 'wpsc_non_agent_ticket_customers_allowed', array( self::$ticket->customer->id ), self::$ticket );
843 if ( in_array( $current_user->customer->id, $allowed_customers ) ) {
844 return true;
845 }
846
847 return false;
848 }
849
850 /**
851 * Agent ticket access for cap
852 *
853 * @param string $cap - capability name.
854 * @return boolean
855 */
856 public static function has_ticket_cap( $cap ) {
857
858 $current_user = WPSC_Current_User::$current_user;
859
860 $assigned_agents = array_map(
861 fn ( $agent ) => $agent->id,
862 self::$ticket->assigned_agent
863 );
864
865 $flag = false;
866 if (
867 (
868 ! self::$ticket->assigned_agent &&
869 $current_user->agent->has_cap( $cap . '-unassigned' )
870 ) ||
871 (
872 in_array( $current_user->agent->id, $assigned_agents ) &&
873 $current_user->agent->has_cap( $cap . '-assigned-me' )
874 ) ||
875 (
876 self::$ticket->assigned_agent &&
877 ! in_array( $current_user->agent->id, $assigned_agents ) &&
878 $current_user->agent->has_cap( $cap . '-assigned-others' )
879 )
880 ) {
881 $flag = true;
882 }
883
884 return apply_filters( 'wpsc_it_has_ticket_cap', $flag, self::$ticket, $cap );
885 }
886
887 /**
888 * Load older threads
889 *
890 * @return void
891 */
892 public static function load_older_threads() {
893
894 self::load_current_ticket();
895 self::load_actions();
896
897 $last_thread = isset( $_POST['last_thread'] ) ? intval( $_POST['last_thread'] ) : 0; // phpcs:ignore
898 if ( ! $last_thread ) {
899 wp_send_json_error( new WP_Error( '004', 'Bad request!' ), 401 );
900 }
901
902 $gs = get_option( 'wpsc-gs-general' );
903 $filters = array(
904 'meta_query' => array(
905 'relation' => 'AND',
906 array(
907 'slug' => 'ticket',
908 'compare' => '=',
909 'val' => self::$ticket->id,
910 ),
911 array(
912 'slug' => 'id',
913 'compare' => '<',
914 'val' => $last_thread,
915 ),
916 ),
917 'orderby' => 'id',
918 'order' => 'DESC',
919 );
920
921 $thread_types = array(
922 'slug' => 'type',
923 'compare' => 'IN',
924 'val' => array( 'report', 'reply' ),
925 );
926
927 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
928 $thread_types['val'][] = 'note';
929 }
930 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
931 $thread_types['val'][] = 'log';
932 }
933
934 $filters['meta_query'][] = $thread_types;
935
936 $response = WPSC_Archive_Thread::find( $filters );
937 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
938
939 ob_start();
940 if ( $gs['reply-form-position'] == 'top' ) {
941 foreach ( $response['results'] as $thread ) {
942 if ( $thread->type == 'log' ) {
943 self::print_log( $thread );
944 } else {
945 self::print_thread( $thread );
946 }
947 }
948 } else {
949 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
950 $thread = $response['results'][ $i ];
951 if ( $thread->type == 'log' ) {
952 self::print_log( $thread );
953 } else {
954 self::print_thread( $thread );
955 }
956 }
957 }
958 $threads = ob_get_clean();
959
960 $html_response = array(
961 'last_thread' => $last_id,
962 'has_next_page' => $response['has_next_page'],
963 'threads' => $threads,
964 );
965
966 wp_send_json( $html_response );
967 wp_die();
968 }
969
970 /**
971 * Restore the given ticket by moving it and its threads to the ticket/thread tables.
972 *
973 * @return void
974 */
975 public static function set_restore_archived() {
976
977 if ( check_ajax_referer( 'wpsc_it_restore_archived', '_ajax_nonce', false ) != 1 ) {
978 wp_send_json_error( 'Unauthorized request!', 401 );
979 }
980
981 $ticket_id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0;
982 if ( ! $ticket_id ) {
983 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
984 }
985
986 $ar_ticket = new WPSC_Archive_Ticket( $ticket_id );
987 if ( ! $ar_ticket->id ) {
988 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
989 }
990
991 $success = WPSC_Archive_Ticket::restore_archive_ticket( $ar_ticket );
992 if ( $success ) {
993 $ticket = new WPSC_Ticket( $ar_ticket->id );
994 do_action( 'wpsc_archive_ticket_restore', $ticket, $ar_ticket );
995 }
996 }
997
998 /**
999 * Get thread info
1000 *
1001 * @return void
1002 */
1003 public static function it_archive_thread_info() {
1004
1005 if ( check_ajax_referer( 'wpsc_it_thread_info', '_ajax_nonce', false ) != 1 ) {
1006 wp_send_json_error( 'Unauthorized request!', 401 );
1007 }
1008
1009 self::load_current_ticket();
1010
1011 $current_user = WPSC_Current_User::$current_user;
1012
1013 if ( ! ( self::$view_profile == 'agent' ) ) {
1014 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
1015 }
1016
1017 $title = esc_attr__( 'Thread info', 'supportcandy' );
1018
1019 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
1020 if ( ! $thread_id ) {
1021 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1022 }
1023
1024 $thread = new WPSC_Archive_Thread( $thread_id );
1025 if ( ! $thread->id ) {
1026 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1027 }
1028
1029 if ( self::$ticket != $thread->ticket ) {
1030 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1031 }
1032
1033 $settings = get_option( 'wpsc-gs-general', array() );
1034 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1035 $ip_address = $thread->ip_address ? $thread->ip_address : esc_attr__( 'Not Applicable', 'supportcandy' );
1036 $os = $thread->os ? $thread->os : esc_attr__( 'Not Applicable', 'supportcandy' );
1037 $browser = $thread->browser ? $thread->browser : esc_attr__( 'Not Applicable', 'supportcandy' );
1038 $source = $thread->source && isset( WPSC_DF_Source::$sources[ $thread->source ] ) ? WPSC_DF_Source::$sources[ $thread->source ] : esc_attr__( 'Not Applicable', 'supportcandy' );
1039
1040 ob_start();
1041 ?>
1042 <div class="wpsc-thread-info">
1043
1044 <div class="info-list-item">
1045 <div class="info-label"><?php esc_attr_e( 'Name', 'supportcandy' ); ?>:</div>
1046 <div class="info-val"><?php echo esc_attr( $thread->customer->name ); ?></div>
1047 </div>
1048 <?php
1049
1050 if ( $current_user->is_agent && in_array( $current_user->agent->role, $settings['allow-ar-thread-email'] ) ) {
1051 ?>
1052 <div class="info-list-item">
1053 <div class="info-label"><?php esc_attr_e( 'Email Address', 'supportcandy' ); ?>:</div>
1054 <div class="info-val"><?php echo esc_attr( $thread->customer->email ); ?></div>
1055 </div>
1056 <?php
1057 }
1058 ?>
1059
1060 <div class="info-list-item">
1061 <div class="info-label"><?php esc_attr_e( 'Source', 'supportcandy' ); ?>:</div>
1062 <div class="info-val"><?php echo esc_attr( $source ); ?></div>
1063 </div>
1064
1065 <div class="info-list-item">
1066 <div class="info-label"><?php esc_attr_e( 'IP Address', 'supportcandy' ); ?>:</div>
1067 <div class="info-val"><?php echo esc_attr( $ip_address ); ?></div>
1068 </div>
1069
1070 <div class="info-list-item">
1071 <div class="info-label"><?php esc_attr_e( 'Browser', 'supportcandy' ); ?>:</div>
1072 <div class="info-val"><?php echo esc_attr( $browser ); ?></div>
1073 </div>
1074
1075 <div class="info-list-item">
1076 <div class="info-label"><?php esc_attr_e( 'Operating System', 'supportcandy' ); ?>:</div>
1077 <div class="info-val"><?php echo esc_attr( $os ); ?></div>
1078 </div>
1079
1080 <?php
1081 if ( $thread->type == 'reply' && $thread->customer != $thread->ticket->customer ) {
1082 ?>
1083 <div class="info-list-item">
1084 <div class="info-label"><?php esc_attr_e( 'Seen', 'supportcandy' ); ?>:</div>
1085 <div class="info-val">
1086 <?php echo is_object( $thread->seen ) ? esc_attr( $thread->seen->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) ) : esc_attr_e( 'No', 'supportcandy' ); ?>
1087 </div>
1088 </div>
1089 <?php
1090 }
1091 do_action( 'wpsc_thread_info_body', $thread );
1092 ?>
1093 </div>
1094 <?php
1095 $body = ob_get_clean();
1096
1097 ob_start();
1098 ?>
1099 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
1100 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
1101 </button>
1102 <?php
1103 $footer = ob_get_clean();
1104
1105 $response = array(
1106 'title' => $title,
1107 'body' => $body,
1108 'footer' => $footer,
1109 );
1110 wp_send_json( $response );
1111 }
1112
1113 /**
1114 * Permanently delete ticket ajax request
1115 *
1116 * @return void
1117 */
1118 public static function iat_delete_permanently() {
1119
1120 if ( check_ajax_referer( 'wpsc_iat_delete_permanently', '_ajax_nonce', false ) != 1 ) {
1121 wp_send_json_error( 'Unauthorized request!', 401 );
1122 }
1123
1124 $current_user = WPSC_Current_User::$current_user;
1125 if ( ! $current_user->is_agent ) {
1126 wp_send_json_error( 'Unauthorized request!', 401 );
1127 }
1128
1129 $ticket_id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0;
1130 if ( ! $ticket_id ) {
1131 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1132 }
1133
1134 $ticket = new WPSC_Archive_Ticket( $ticket_id );
1135 if ( ! $ticket->id ) {
1136 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
1137 }
1138
1139 if ( ! $current_user->agent->has_cap( 'at-delete-access' ) ) {
1140 wp_send_json_error( 'Unauthorized request!', 401 );
1141 }
1142
1143 self::delete_archive_ticket( $ticket );
1144 }
1145
1146 /**
1147 * Delete ticket permanently.
1148 *
1149 * @param WPSC_Ticket $ticket - ticket object.
1150 * @return void
1151 */
1152 public static function delete_archive_ticket( $ticket ) {
1153
1154 WPSC_Archive_Ticket::destroy( $ticket );
1155 do_action( 'wpsc_delete_archive_ticket_permanently', $ticket );
1156 }
1157 }
1158 endif;
1159 WPSC_Individual_Archive_Ticket::init();
1160