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-ticket.php

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

3,638 lines 117.6 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_Ticket' ) ) :
7
8 final class WPSC_Individual_Ticket {
9
10 /**
11 * Current ticket object (ticket model object)
12 *
13 * @var WPSC_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 * Set whether ticket action is restricted or not.
69 *
70 * @var boolean
71 */
72 public static $is_restricted = false;
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_ticket', array( __CLASS__, 'layout' ) );
82 add_action( 'wp_ajax_nopriv_wpsc_get_individual_ticket', array( __CLASS__, 'layout' ) );
83
84 // Submit ticket.
85 add_action( 'wp_ajax_wpsc_it_add_reply', array( __CLASS__, 'add_reply' ) );
86 add_action( 'wp_ajax_nopriv_wpsc_it_add_reply', array( __CLASS__, 'add_reply' ) );
87
88 // Submit note.
89 add_action( 'wp_ajax_wpsc_it_add_note', array( __CLASS__, 'add_note' ) );
90 add_action( 'wp_ajax_nopriv_wpsc_it_add_note', array( __CLASS__, 'add_note' ) );
91
92 // Submit note.
93 add_action( 'wp_ajax_wpsc_it_reply_and_close', array( __CLASS__, 'it_reply_and_close' ) );
94 add_action( 'wp_ajax_nopriv_wpsc_it_reply_and_close', array( __CLASS__, 'it_reply_and_close' ) );
95
96 // Duplicate ticket.
97 add_action( 'wp_ajax_wpsc_it_get_duplicate_ticket', array( __CLASS__, 'get_duplicate_ticket' ) );
98 add_action( 'wp_ajax_nopriv_wpsc_it_get_duplicate_ticket', array( __CLASS__, 'get_duplicate_ticket' ) );
99 add_action( 'wp_ajax_wpsc_it_set_duplicate_ticket', array( __CLASS__, 'set_duplicate_ticket' ) );
100 add_action( 'wp_ajax_nopriv_wpsc_it_set_duplicate_ticket', array( __CLASS__, 'set_duplicate_ticket' ) );
101
102 // Delete ticket.
103 add_action( 'wp_ajax_wpsc_it_delete_ticket', array( __CLASS__, 'set_delete_ticket' ) );
104 add_action( 'wp_ajax_nopriv_wpsc_it_delete_ticket', array( __CLASS__, 'set_delete_ticket' ) );
105 add_action( 'wp_ajax_wpsc_it_archive_ticket', array( __CLASS__, 'set_archive_ticket' ) );
106 add_action( 'wp_ajax_nopriv_wpsc_it_archive_ticket', array( __CLASS__, 'set_archive_ticket' ) );
107 add_action( 'wp_ajax_wpsc_it_ticket_restore', array( __CLASS__, 'ticket_restore' ) );
108 add_action( 'wp_ajax_nopriv_wpsc_it_ticket_restore', array( __CLASS__, 'ticket_restore' ) );
109 add_action( 'wp_ajax_wpsc_it_delete_permanently', array( __CLASS__, 'set_delete_ticket_permanently' ) );
110 add_action( 'wp_ajax_nopriv_wpsc_it_delete_permanently', array( __CLASS__, 'set_delete_ticket_permanently' ) );
111
112 // Thread actions.
113 add_action( 'wp_ajax_wpsc_it_thread_info', array( __CLASS__, 'it_thread_info' ) );
114 add_action( 'wp_ajax_nopriv_wpsc_it_thread_info', array( __CLASS__, 'it_thread_info' ) );
115 add_action( 'wp_ajax_wpsc_it_get_edit_thread', array( __CLASS__, 'get_edit_thread' ) );
116 add_action( 'wp_ajax_nopriv_wpsc_it_get_edit_thread', array( __CLASS__, 'get_edit_thread' ) );
117 add_action( 'wp_ajax_wpsc_it_set_edit_thread', array( __CLASS__, 'set_edit_thread' ) );
118 add_action( 'wp_ajax_nopriv_wpsc_it_set_edit_thread', array( __CLASS__, 'set_edit_thread' ) );
119 add_action( 'wp_ajax_wpsc_it_get_thread', array( __CLASS__, 'get_thread_html' ) );
120 add_action( 'wp_ajax_nopriv_wpsc_it_get_thread', array( __CLASS__, 'get_thread_html' ) );
121 add_action( 'wp_ajax_wpsc_it_thread_delete', array( __CLASS__, 'delete_thread' ) );
122 add_action( 'wp_ajax_nopriv_wpsc_it_thread_delete', array( __CLASS__, 'delete_thread' ) );
123 add_action( 'wp_ajax_wpsc_it_view_thread_log', array( __CLASS__, 'view_thread_log' ) );
124 add_action( 'wp_ajax_nopriv_wpsc_it_view_thread_log', array( __CLASS__, 'view_thread_log' ) );
125 add_action( 'wp_ajax_wpsc_it_view_deleted_thread', array( __CLASS__, 'view_deleted_thread' ) );
126 add_action( 'wp_ajax_nopriv_wpsc_it_view_deleted_thread', array( __CLASS__, 'view_deleted_thread' ) );
127 add_action( 'wp_ajax_wpsc_it_restore_thread', array( __CLASS__, 'restore_thread' ) );
128 add_action( 'wp_ajax_nopriv_wpsc_it_restore_thread', array( __CLASS__, 'restore_thread' ) );
129 add_action( 'wp_ajax_wpsc_it_thread_delete_permanently', array( __CLASS__, 'thread_delete_permanently' ) );
130 add_action( 'wp_ajax_nopriv_wpsc_it_thread_delete_permanently', array( __CLASS__, 'thread_delete_permanently' ) );
131 add_action( 'wp_ajax_wpsc_it_thread_new_ticket', array( __CLASS__, 'it_thread_new_ticket' ) );
132 add_action( 'wp_ajax_nopriv_wpsc_it_thread_new_ticket', array( __CLASS__, 'it_thread_new_ticket' ) );
133 add_action( 'wp_ajax_wpsc_it_set_thread_new_ticket', array( __CLASS__, 'it_set_thread_new_ticket' ) );
134 add_action( 'wp_ajax_nopriv_wpsc_it_set_thread_new_ticket', array( __CLASS__, 'it_set_thread_new_ticket' ) );
135
136 // Load older threads.
137 add_action( 'wp_ajax_wpsc_load_older_threads', array( __CLASS__, 'load_older_threads' ) );
138 add_action( 'wp_ajax_nopriv_wpsc_load_older_threads', array( __CLASS__, 'load_older_threads' ) );
139
140 // agent collision.
141 add_filter( 'wp_ajax_wpsc_check_live_agents', array( __CLASS__, 'check_live_agents' ) );
142 add_action( 'wp_ajax_nopriv_wpsc_check_live_agents', array( __CLASS__, 'check_live_agents' ) );
143 }
144
145 /**
146 * Ajax callback function for individual ticket
147 *
148 * @return void
149 */
150 public static function layout() {
151
152 $current_user = WPSC_Current_User::$current_user;
153
154 $gs = get_option( 'wpsc-gs-general' );
155 self::load_current_ticket();
156 self::load_actions();
157 self::load_widget_html();?>
158 <div class="wpsc-it-container">
159 <div class="wpsc-it-body">
160 <?php
161 self::get_actions();
162 self::get_subject();
163 self::get_live_agents();
164 self::get_mobile_widgets();
165 if ( $gs['reply-form-position'] == 'top' ) {
166 self::get_reply_section();
167 self::get_thread_section();
168 } else {
169 self::get_thread_section();
170 self::get_reply_section();
171 }
172 ?>
173 </div>
174 <div class="wpsc-it-sidebar-widget-container wpsc-hidden-xs wpsc-hidden-sm">
175 <?php echo self::$widget_html; // phpcs:ignore?>
176 </div>
177 </div>
178 <div style="display:none" id="wpsc-ticket-url"><?php echo esc_url( self::$ticket->get_url() ); ?></div>
179 <input type="hidden" id="wpsc-current-ticket" value="<?php echo intval( self::$ticket->id ); ?>">
180 <input type="hidden" id="wpsc-current-agent" value="<?php echo $current_user->is_agent ? intval( $current_user->agent->id ) : 0; ?>">
181 <script>
182
183 var arrow_up = '<?php WPSC_Icons::get( 'chevron-up' ); ?>';
184 var arrow_down = '<?php WPSC_Icons::get( 'chevron-down' ); ?>';
185
186 jQuery(document).ready(function() {
187
188 var widgets_hidden = JSON.parse(localStorage.getItem('wpsc_itw_hidden')) || [];
189 widgets_hidden.forEach(function(widget){
190
191 jQuery('.' + widget).find(".wpsc-widget-body").hide();
192 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_down);
193 });
194
195 jQuery('.wpsc-itw-toggle').on('click',function() {
196
197 var widgets_hidden = JSON.parse(localStorage.getItem('wpsc_itw_hidden')) || [];
198 var widget = jQuery(this).data('widget');
199
200 if(widgets_hidden.includes(widget)){
201
202 jQuery('.' + widget).find(".wpsc-widget-body").slideDown();
203 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_up);
204
205 widgets_hidden = widgets_hidden.filter(function(element) {
206 return element !== widget;
207 });
208 } else {
209
210 jQuery('.' + widget).find(".wpsc-widget-body").slideUp();
211 jQuery('.' + widget).find(".wpsc-widget-header .wpsc-itw-toggle").html(arrow_down);
212 widgets_hidden.push(widget);
213 }
214
215 localStorage.setItem('wpsc_itw_hidden', JSON.stringify(widgets_hidden));
216 });
217 });
218 </script>
219 <?php
220 do_action( 'wpsc_it_layout_section', self::$ticket );
221 wp_die();
222 }
223
224 /**
225 * Load current ticket using id we got from ajax request
226 * Ignore phpcs nonce issue as we already checked where it is called from.
227 *
228 * @return void
229 */
230 public static function load_current_ticket() {
231
232 $current_user = WPSC_Current_User::$current_user;
233 $id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0; // phpcs:ignore
234 if ( ! $id ) {
235 wp_send_json_error( new WP_Error( '001', 'Unauthorized!' ), 401 );
236 }
237
238 $ticket = new WPSC_Ticket( $id );
239 if ( ! $ticket->id ) {
240 $ticket = new WPSC_Archive_Ticket( $id );
241 if ( $ticket->id ) {
242 ?>
243 <div style="align-item:center;" ><h6><?php esc_attr_e( 'Unauthorized access!', 'supportcandy' ); ?></h6></div>
244 <?php
245 wp_die();
246 } else {
247 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
248 }
249 }
250 self::$ticket = $ticket;
251 self::$is_restricted = WPSC_Ticket_Restrictions_Manager::is_restricted( $ticket );
252
253 // url authentication.
254 $auth_code = isset( $_REQUEST['auth-code'] ) ? sanitize_text_field( $_REQUEST['auth-code'] ) : ''; // phpcs:ignore
255 if ( ! $auth_code ) {
256 $auth_code = isset( $_REQUEST['auth_code'] ) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
257 }
258 if ( $auth_code && $ticket->auth_code == $auth_code ) {
259 self::$url_auth = true;
260 }
261
262 // Set view profile as 'customer' if the user is either a valid logged-in customer or a guest with a valid auth code.
263 if ( self::is_customer() || ( $current_user->is_guest && ! $current_user->is_customer && self::$url_auth ) ) {
264 self::$view_profile = 'customer';
265 }
266
267 // Check whether view profile is an agent.
268 if ( $current_user->is_agent && self::has_ticket_cap( 'view' ) ) {
269 self::$view_profile = 'agent';
270 }
271
272 if ( ! self::$view_profile ) :
273 ?>
274 <div style="align-item:center;" ><h6><?php esc_attr_e( 'Unauthorized access!', 'supportcandy' ); ?></h6></div>
275 <?php
276 wp_die();
277 endif;
278
279 // Check if ticket is deleted and whether current user has access to deleted tickets.
280 if ( ! self::$ticket->is_active && ! ( self::$view_profile == 'agent' ) ) {
281 ?>
282 <div style="align-item:center;" ><h6><?php esc_attr_e( 'Unauthorized access!', 'supportcandy' ); ?></h6></div>
283 <?php
284 wp_die();
285 }
286 }
287
288 /**
289 * Load actions of ticket which will be used in action bar
290 *
291 * @return void
292 */
293 public static function load_actions() {
294
295 $current_user = WPSC_Current_User::$current_user;
296 $actions = array(
297 'refresh' => array(
298 'label' => esc_attr__( 'Refresh', 'supportcandy' ),
299 'callback' => 'wpsc_it_ab_refresh(' . self::$ticket->id . ');',
300 ),
301 );
302
303 $gs = get_option( 'wpsc-gs-general' );
304 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
305 $ms_advanced = get_option( 'wpsc-ms-advanced-settings' );
306
307 $close_flag = false;
308 if ( $current_user->is_customer &&
309 (
310 ( $current_user->customer->id == self::$ticket->customer->id && in_array( 'customer', $gs['allow-close-ticket'] ) ) ||
311 ( $current_user->is_agent && self::has_ticket_cap( 'cs' ) && in_array( $current_user->agent->role, $gs['allow-close-ticket'] ) )
312 )
313 ) {
314 $close_flag = true;
315 }
316
317 $close_flag = apply_filters( 'wpsc_it_action_close_flag', $close_flag, self::$ticket );
318
319 if (
320 $close_flag &&
321 ! (
322 self::$ticket->status->id == $gs['close-ticket-status'] ||
323 in_array( self::$ticket->status->id, $tl_advanced['closed-ticket-statuses'] )
324 )
325 ) {
326 $actions['close'] = array(
327 'label' => esc_attr__( 'Close', 'supportcandy' ),
328 'callback' => 'wpsc_it_close_ticket(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_close_ticket' ) ) . '\');',
329 );
330 }
331
332 // Duplicate.
333 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dt' ) ) {
334 $actions['duplicate'] = array(
335 'label' => esc_attr__( 'Duplicate', 'supportcandy' ),
336 'callback' => 'wpsc_it_get_duplicate_ticket(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_get_duplicate_ticket' ) ) . '\');',
337 );
338 }
339
340 // Copy url.
341 $actions['copy'] = array(
342 'label' => esc_attr__( 'Copy URL', 'supportcandy' ),
343 'callback' => 'wpsc_it_copy_url(' . self::$ticket->id . ');',
344 );
345
346 // Archive.
347 if ( $current_user->is_agent && self::has_ticket_cap( 'at' ) ) {
348 $actions['archive'] = array(
349 'label' => esc_attr__( 'Archive', 'supportcandy' ),
350 'callback' => 'wpsc_it_archive_ticket(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_archive_ticket' ) ) . '\');',
351 );
352 }
353
354 // Restore permanently delete.
355 if ( $current_user->is_agent && self::has_ticket_cap( 'dtt' ) ) {
356 $actions['restore'] = array(
357 'label' => esc_attr__( 'Restore', 'supportcandy' ),
358 'callback' => 'wpsc_it_ticket_restore(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_ticket_restore' ) ) . '\');',
359 );
360 }
361
362 // Delete.
363 if ( $current_user->is_agent && self::has_ticket_cap( 'dtt' ) ) {
364 $actions['delete'] = array(
365 'label' => esc_attr__( 'Delete', 'supportcandy' ),
366 'callback' => 'wpsc_it_delete_ticket(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_delete_ticket' ) ) . '\');',
367 );
368 }
369
370 // Permanently delete.
371 if ( $current_user->is_agent && WPSC_Functions::is_site_admin() ) {
372 $actions['delete-permanently'] = array(
373 'label' => esc_attr__( 'Delete permanently', 'supportcandy' ),
374 'callback' => 'wpsc_it_delete_permanently(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_delete_permanently' ) ) . '\');',
375 );
376 }
377
378 self::$actions = apply_filters( 'wpsc_individual_ticket_actions', $actions, self::$ticket );
379
380 if ( self::$view_profile == 'agent' ) {
381
382 // Submit actions.
383 $submit_actions = array();
384 if ( self::has_ticket_cap( 'reply' ) || self::is_customer() ) {
385 $submit_actions['reply'] = array(
386 'icon' => 'reply',
387 'label' => esc_attr__( 'Reply', 'supportcandy' ),
388 'callback' => 'wpsc_it_add_reply(' . self::$ticket->id . ');',
389 );
390 }
391 if ( self::has_ticket_cap( 'pn' ) ) {
392 $submit_actions['private-note'] = array(
393 'icon' => 'notes',
394 'label' => esc_attr__( 'Private Note', 'supportcandy' ),
395 'callback' => 'wpsc_it_add_private_note(' . self::$ticket->id . ');',
396 );
397 }
398 if ( ( self::has_ticket_cap( 'reply' ) && self::has_ticket_cap( 'cs' ) ) &&
399 ! (
400 self::$ticket->status->id == $gs['close-ticket-status'] ||
401 in_array( self::$ticket->status->id, $tl_advanced['closed-ticket-statuses'] )
402 ) &&
403 ( $current_user->is_agent && in_array( $current_user->agent->role, $gs['allow-close-ticket'] ) ) && ( $ms_advanced['reply-close'] )
404 ) {
405 $submit_actions['reply-and-close'] = array(
406 'icon' => 'reply',
407 'label' => esc_attr__( 'Reply & Close', 'supportcandy' ),
408 'callback' => 'wpsc_it_reply_and_close(' . self::$ticket->id . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_reply_and_close' ) ) . '\');',
409 );
410 }
411 self::$submit_actions = apply_filters( 'wpsc_it_submit_actions', $submit_actions, self::$ticket );
412
413 // Thread actions.
414 $thread_actions = array(
415 'info' => array(
416 'icon' => 'info-circle',
417 'label' => esc_attr__( 'Info', 'supportcandy' ),
418 'callback' => 'wpsc_it_thread_info',
419 ),
420 'new-ticket' => array(
421 'icon' => 'plus-square',
422 'label' => esc_attr__( 'Create new ticket from this thread', 'supportcandy' ),
423 'callback' => 'wpsc_it_thread_new_ticket',
424 ),
425 );
426
427 if ( self::has_ticket_cap( 'eth' ) ) {
428 $thread_actions['edit'] = array(
429 'icon' => 'edit',
430 'label' => esc_attr__( 'Edit', 'supportcandy' ),
431 'callback' => 'wpsc_it_get_edit_thread',
432 );
433 }
434
435 if ( self::has_ticket_cap( 'dth' ) ) {
436 $thread_actions['delete'] = array(
437 'icon' => 'trash-alt',
438 'label' => esc_attr__( 'Delete', 'supportcandy' ),
439 'callback' => 'wpsc_it_thread_delete',
440 );
441 }
442 self::$thread_actions = apply_filters( 'wpsc_it_thread_actions', $thread_actions, self::$ticket );
443 }
444 }
445
446 /**
447 * Load widget at once and save html to $widget_html
448 *
449 * @return void
450 */
451 private static function load_widget_html() {
452
453 $current_user = WPSC_Current_User::$current_user;
454 $widgets = get_option( 'wpsc-ticket-widget' );
455 ob_start();
456
457 do_action( 'wpsc_before_ticket_widget', self::$ticket );
458
459 foreach ( $widgets as $slug => $widget ) :
460 if ( ! $widget['is_enable'] ) {
461 continue;
462 }
463 if ( ! class_exists( $widget['class'] ) ) {
464 continue;
465 }
466 $widget['class']::print_widget( self::$ticket, $widget );
467 endforeach;
468
469 do_action( 'wpsc_after_ticket_widget', self::$ticket );
470
471 self::$widget_html = ob_get_clean();
472 }
473
474 /**
475 * Action bar where verious actions like refresh, close, duplicate, etc. are available
476 *
477 * @return void
478 */
479 public static function get_actions() {
480
481 $actions_arr = array();
482 foreach ( self::$actions as $key => $action ) :
483 ob_start();
484 ?>
485 <span
486 class="wpsc-link wpsc-it-<?php echo esc_attr( $key ); ?>"
487 onclick="<?php echo esc_attr( $action['callback'] ); ?>">
488 <?php echo esc_attr( $action['label'] ); ?>
489 </span>
490 <?php
491 $actions_arr[] = ob_get_clean();
492 endforeach;
493 ?>
494
495 <div class="wpsc-it-action-container">
496 <div class="wpsc-filter-actions">
497 <?php echo implode( '<div class="action-devider"></div>', $actions_arr ); // phpcs:ignore?>
498 </div>
499 </div>
500 <?php
501 }
502
503 /**
504 * Subject bar of the ticket
505 *
506 * @return void
507 */
508 public static function get_subject() {
509 $gs = get_option( 'wpsc-gs-general' );
510 ?>
511 <div class="wpsc-it-body-item wpsc-it-subject-container">
512 <h2><?php echo '[' . esc_attr( $gs['ticket-alice'] ) . esc_attr( self::$ticket->id ) . '] ' . esc_attr( self::$ticket->subject ); ?></h2>
513 <?php
514 if ( self::$ticket->is_active && self::$view_profile == 'agent' && self::has_ticket_cap( 'ctf' ) ) :
515 ?>
516 <span onclick="wpsc_it_get_edit_subject(<?php echo esc_attr( self::$ticket->id ); ?>)"><?php WPSC_Icons::get( 'edit' ); ?></span>
517 <?php
518 endif
519 ?>
520 </div>
521 <?php
522 }
523
524 /**
525 * Displayed only on sm and xs screens
526 *
527 * @return void
528 */
529 public static function get_mobile_widgets() {
530 ?>
531 <div class="wpsc-it-body-item wpsc-it-mobile-widget-container wpsc-visible-xs wpsc-visible-sm">
532 <div class="wpsc-it-mob-widget-trigger-btn" onclick="wpsc_toggle_mob_it_widgets();">
533 <h2><?php esc_attr_e( 'Ticket Details', 'supportcandy' ); ?></h2>
534 <span class="down"><?php WPSC_Icons::get( 'chevron-down' ); ?></span>
535 <span class="up" style="display:none;"><?php WPSC_Icons::get( 'chevron-up' ); ?></span>
536 </div>
537 <div class="wpsc-it-mob-widgets-inner-container" data-status="0" style="display: none;">
538 <?php echo self::$widget_html; // phpcs:ignore?>
539 </div>
540 </div>
541 <?php
542 }
543
544 /**
545 * Get reply section of individual tickets
546 *
547 * @return void
548 */
549 public static function get_reply_section() {
550
551 // Return if ticket is not active (deleted).
552 if ( self::$is_restricted ) {
553 return;
554 }
555
556 $current_user = WPSC_Current_User::$current_user;
557
558 // URL authentication.
559 if ( self::$url_auth && ! $current_user->is_customer ) {
560 $gs = get_option( 'wpsc-gs-general' );
561 $page_settings = get_option( 'wpsc-gs-page-settings' );
562 ?>
563 <div class="wpsc_url_auth_sign_in wpsc-widget-body">
564 <p class="wpsc_auth_header"><?php esc_attr_e( 'You must sign in to submit a reply', 'supportcandy' ); ?></p>
565 <p class="wpsc_auth_link wpsc-signin-customer">
566 <?php
567 printf(
568 /* translators: %s: Sign in */
569 esc_attr__( '%s using email and password (registered user)', 'supportcandy' ),
570 '<a class="wpsc-link" href="javascript:wpsc_user_sign_in();">' . esc_attr__( 'Sign in', 'supportcandy' ) . '</a>'
571 );
572 ?>
573 </p>
574 <?php
575 if ( $page_settings['otp-login'] && in_array( 'guest', $gs['allow-create-ticket'] ) ) {
576 ?>
577 <p class="wpsc_auth_link wpsc-signin-guest">
578 <?php
579 printf(
580 /* translators: %s: Sign in */
581 esc_attr__( '%s using email and one time password (guest user)', 'supportcandy' ),
582 '<a class="wpsc-link" href="javascript:wpsc_get_guest_sign_in();">' . esc_attr__( 'Sign in', 'supportcandy' ) . '</a>'
583 );
584 ?>
585 </p>
586 <?php
587 }
588 ?>
589 </div>
590 <script>
591
592 /**
593 * Open sign in form
594 *
595 * @return void
596 */
597 function wpsc_user_sign_in() {
598 var url = new URL(window.location.href);
599 var search_params = url.searchParams;
600 search_params.delete('auth_code');
601 search_params.delete('auth-code');
602 url.search = search_params.toString();
603 window.location.href = url.toString();
604 }
605
606 /**
607 * Get guest otp login screen
608 *
609 * @return void
610 */
611 function wpsc_get_guest_sign_in() {
612 jQuery('.wpsc-body').remove();
613 jQuery('.wpsc-shortcode-container').html('<div class="wpsc-auth-container"><div class="auth-inner-container"></div></div>');
614 jQuery('.auth-inner-container').html(supportcandy.loader_html);
615 var data = { action: 'wpsc_get_guest_sign_in' };
616 jQuery.post(supportcandy.ajax_url, data, function (res) {
617 if (typeof(res) == "object") {
618 alert(supportcandy.translations.something_wrong);
619 window.location.reload();
620 } else {
621 jQuery('.auth-inner-container').html(res);
622 }
623 });
624 }
625
626 /**
627 * Send login OTP
628 *
629 * @return void
630 */
631 function wpsc_authenticate_guest_login(el) {
632
633 var dataform = new FormData(jQuery(el).closest('form')[0]);
634
635 var email_address = dataform.get('email_address').trim();
636 if ( ! email_address ) {
637 alert(supportcandy.translations.req_fields_missing);
638 return;
639 }
640
641 if (!validateEmail(email_address)) {
642 alert(supportcandy.translations.incorrect_email);
643 return;
644 }
645
646 jQuery(el).text(supportcandy.translations.please_wait);
647 jQuery.ajax({
648 url: supportcandy.ajax_url,
649 type: 'POST',
650 data: dataform,
651 processData: false,
652 contentType: false
653 }).done(function (res) {
654 if (typeof(res) == "object") {
655 alert(supportcandy.translations.something_wrong);
656 wpsc_get_guest_sign_in();
657 } else {
658 jQuery('.auth-inner-container').html(res);
659 }
660 }).fail(function (res) {
661 alert(supportcandy.translations.something_wrong);
662 window.location.reload();
663 });
664 }
665
666 /**
667 * Confirm guest login auth
668 *
669 * @return void
670 */
671 function wpsc_confirm_guest_login(el) {
672
673 jQuery('.auth-inner-container').html(supportcandy.loader_html);
674 var dataform = new FormData(jQuery(el).closest('form')[0]);
675 jQuery.ajax({
676 url: supportcandy.ajax_url,
677 type: 'POST',
678 data: dataform,
679 processData: false,
680 contentType: false
681 }).done(function (res) {
682 if (res.isSuccess == 1) {
683 window.location.reload();
684 } else {
685 alert(supportcandy.translations.something_wrong);
686 wpsc_get_guest_sign_in();
687 }
688 });
689 }
690 </script>
691 <?php
692 return;
693 }
694
695 $flag = false;
696
697 if ( self::$view_profile == 'agent' && self::$submit_actions ) {
698 $flag = true;
699 }
700
701 if ( ! $flag && self::is_customer() ) {
702 $flag = true;
703 }
704
705 $gs = get_option( 'wpsc-gs-general' );
706 $ms_advanced = get_option( 'wpsc-ms-advanced-settings' );
707
708 if (
709 self::$ticket->status->id == $gs['close-ticket-status'] &&
710 (
711 ( $current_user->is_agent && ! in_array( 'agent', $ms_advanced['allow-reply-to-close-ticket'] ) ) ||
712 ( ! $current_user->is_agent && ! in_array( 'customer', $ms_advanced['allow-reply-to-close-ticket'] ) )
713 )
714 ) {
715 $flag = false;
716 }
717
718 if ( ! $flag ) {
719 return;
720 }
721
722 // Set reply profile.
723 self::$reply_profile = 'customer';
724 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) {
725 self::$reply_profile = 'agent';
726 }
727
728 $advanced = get_option( 'wpsc-ms-advanced-settings' );
729 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
730
731 // check reply permission for public tickets.
732 if ( ! $current_user->is_agent && $advanced['public-mode'] && ! $advanced['public-mode-reply'] && self::$ticket->customer->id != $current_user->customer->id ) {
733 return;
734 }
735
736 $unique_id = uniqid( 'wpsc_' );
737 ?>
738 <div class="wpsc-it-body-item wpsc-it-reply-section-container">
739 <form action="#" class="wpsc-reply-section" onsubmit="return false;">
740 <textarea class="wpsc_textarea" id="description"></textarea>
741 <?php
742 if ( $advanced['allow-cc'] ) :
743 ?>
744 <input id="wpsc-it-cc" name="cc" type="text" style="border:none !important;" placeholder="<?php esc_attr_e( 'CC (comma separated list)', 'supportcandy' ); ?>" autocomplete="off">
745 <?php
746 endif;
747 if ( $advanced['allow-bcc'] ) :
748 ?>
749 <input id="wpsc-it-bcc" name="bcc" type="text" style="border:none !important;" placeholder="<?php esc_attr_e( 'BCC (comma separated list)', 'supportcandy' ); ?>" autocomplete="off">
750 <?php
751 endif;
752 ?>
753 <input
754 class="<?php echo esc_attr( $unique_id ); ?>"
755 type="file"
756 onchange="wpsc_set_attach_multiple(this, '<?php echo esc_attr( $unique_id ); ?>', 'description_attachments')"
757 multiple
758 style="display: none;"/>
759 <div class="wpsc-it-editor-action-container">
760 <div class="actions">
761 <div class="wpsc-editor-actions">
762 <?php
763 if ( WPSC_Text_Editor::is_allow_attachments() ) :
764 ?>
765 <span class="wpsc-link wpsc-it-attach" onclick="wpsc_trigger_desc_attachments('<?php echo esc_attr( $unique_id ); ?>');"><?php esc_attr_e( 'Attach Files', 'supportcandy' ); ?></span>
766 <?php
767 endif;
768 if ( $current_user->is_agent ) :
769 ?>
770 <span class="wpsc-link wpsc-it-macro" onclick="wpsc_get_macros()"><?php esc_attr_e( 'Insert Macro', 'supportcandy' ); ?></span>
771 <?php
772 endif;
773 do_action( 'wpsc_it_editor_actions' );
774 ?>
775 </div>
776 <?php
777 if ( WPSC_Text_Editor::is_attachment_notice() && WPSC_Text_Editor::is_allow_attachments() ) :
778 ?>
779 <div class="wpsc-file-attachment-notice"><?php echo esc_attr( WPSC_Text_Editor::file_attachment_notice_text() ); ?></div>
780 <?php
781 endif;
782 ?>
783 <div class="<?php echo esc_attr( $unique_id ); ?> wpsc-editor-attachment-container"></div>
784 </div>
785 <div class="submit-container">
786 <?php
787
788 if ( self::$view_profile == 'agent' ) {
789 ?>
790
791 <button
792 id="wpsc-it-editor-submit"
793 class="wpsc-it-editor-submit wpsc-button normal primary"
794 type="button"
795 data-popover="wpsc-it-submit-actions">
796 <?php esc_attr_e( 'SUBMIT AS', 'supportcandy' ); ?>
797 </button>
798 <div id="wpsc-it-submit-actions" class="gpopover wpsc-popover-menu">
799 <?php
800 foreach ( self::$submit_actions as $key => $action ) :
801 ?>
802 <div class="wpsc-popover-menu-item<?php echo ( $key == 'reply-and-close' ) ? ' wpsc-reply-close' : ''; ?>" onclick="<?php echo esc_attr( $action['callback'] ); ?>">
803 <?php WPSC_Icons::get( $action['icon'] ); ?>
804 <span><?php echo esc_attr( $action['label'] ); ?></span>
805 </div>
806 <?php
807 endforeach;
808 ?>
809 </div>
810 <script>jQuery('#wpsc-it-editor-submit').gpopover({width: 200});</script>
811 <?php
812
813 } else {
814 ?>
815
816 <button
817 id="wpsc-it-editor-submit"
818 class="wpsc-it-editor-submit wpsc-button normal primary"
819 type="button"
820 onclick="wpsc_it_add_reply(<?php echo esc_attr( self::$ticket->id ); ?>)">
821 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
822 </button>
823 <?php
824 if ( (
825 $current_user->is_customer &&
826 ( $current_user->customer->id == self::$ticket->customer->id && in_array( 'customer', $gs['allow-close-ticket'] ) ) )
827 &&
828 ! (
829 self::$ticket->status->id == $gs['close-ticket-status'] ||
830 in_array( self::$ticket->status->id, $tl_advanced['closed-ticket-statuses'] )
831 ) && ( $ms_advanced['reply-close'] )
832 ) {
833 ?>
834 <button
835 class="wpsc-it-editor-submit wpsc-button normal primary"
836 type="button"
837 onclick="wpsc_it_reply_and_close(<?php echo esc_attr( self::$ticket->id ); ?>, '<?php echo esc_attr( wp_create_nonce( 'wpsc_it_reply_and_close' ) ); ?>')">
838 <?php esc_attr_e( 'Reply & Close', 'supportcandy' ); ?>
839 </button>
840 <?php
841 }
842 }
843 ?>
844
845 </div>
846 </div>
847 <?php
848 $recaptcha = get_option( 'wpsc-recaptcha-settings' );
849 if ( $recaptcha['allow-recaptcha'] === 1 && $recaptcha['recaptcha-version'] == 3 && $recaptcha['recaptcha-site-key'] && $recaptcha['recaptcha-secret-key'] ) {
850 ?>
851 <script src="https://www.google.com/recaptcha/api.js?render=<?php echo esc_attr( $recaptcha['recaptcha-site-key'] ); ?>"></script> <?php // phpcs:ignore ?>
852 <?php
853 }
854 ?>
855 </form>
856 </div>
857 <script>
858 <?php
859 WPSC_Text_Editor::print_editor_init_scripts( 'description', 'wpsc-description' );
860 do_action( 'wpsc_js_it_functions' );
861 ?>
862
863 /**
864 * add new reply to ticket
865 * @param {*} ticket_id
866 */
867 function wpsc_it_add_reply(ticket_id) {
868
869 var is_tinymce = (typeof tinyMCE != "undefined") && tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden();
870 var description = is_tinymce && tinymce.get('description') ? tinyMCE.get('description').getContent() : jQuery('#description').val().trim();
871 if (!description) return;
872
873 <?php if ( $advanced['reply-confirmation'] ) : ?>
874 var flag = confirm(supportcandy.translations.confirm);
875 if (!flag) return;
876 <?php endif; ?>
877
878 var form = jQuery('form.wpsc-reply-section')[0];
879 var dataform = new FormData(form);
880 dataform.append('ticket_id', ticket_id);
881 dataform.append('description', description);
882 dataform.append('is_editor', isWPSCEditor);
883 dataform.append('action', 'wpsc_it_add_reply');
884 dataform.append('_ajax_nonce', supportcandy.nonce);
885 jQuery('.wpsc-body').html(supportcandy.loader_html);
886 jQuery.ajax({
887 url: supportcandy.ajax_url,
888 type: 'POST',
889 data: dataform,
890 processData: false,
891 contentType: false
892 }).done(function (res) {
893 wpsc_clear_saved_draft_reply(ticket_id);
894 wpsc_run_ajax_background_process();
895 wpsc_after_ticket_reply(ticket_id);
896 });
897 }
898
899 /**
900 * Add private note
901 */
902 function wpsc_it_add_private_note(ticket_id) {
903
904 var is_tinymce = (typeof tinyMCE != "undefined") && tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden();
905 var description = is_tinymce && tinymce.get('description') ? tinyMCE.get('description').getContent() : jQuery('#description').val().trim();
906 if (!description) return;
907
908 <?php if ( $advanced['note-confirmation'] ) : ?>
909 var flag = confirm(supportcandy.translations.confirm);
910 if (!flag) return;
911 <?php endif; ?>
912
913 var form = jQuery('form.wpsc-reply-section')[0];
914 var dataform = new FormData(form);
915 dataform.append('ticket_id', ticket_id);
916 dataform.append('description', description);
917 dataform.append('is_editor', isWPSCEditor);
918 dataform.append('action', 'wpsc_it_add_note');
919 dataform.append('_ajax_nonce', supportcandy.nonce);
920 jQuery('.wpsc-body').html(supportcandy.loader_html);
921 jQuery.ajax({
922 url: supportcandy.ajax_url,
923 type: 'POST',
924 data: dataform,
925 processData: false,
926 contentType: false
927 }).done(function (res) {
928 wpsc_clear_saved_draft_reply(ticket_id);
929 wpsc_get_individual_ticket(ticket_id);
930 wpsc_run_ajax_background_process();
931 });
932 }
933
934 /**
935 * Reply & close ticket
936 */
937 function wpsc_it_reply_and_close(ticket_id, nonce){
938
939 var is_tinymce = (typeof tinyMCE != "undefined") && tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden();
940 var description = is_tinymce && tinymce.get('description') ? tinyMCE.get('description').getContent() : jQuery('#description').val().trim();
941 if (!description) {
942 confirm(supportcandy.translations.req_fields_missing);
943 return;
944 }
945
946 <?php if ( $advanced['reply-confirmation'] ) : ?>
947 var flag = confirm(supportcandy.translations.confirm);
948 if (!flag) return;
949 <?php endif; ?>
950
951 var form = jQuery('form.wpsc-reply-section')[0];
952 var dataform = new FormData(form);
953 dataform.append('ticket_id', ticket_id);
954 dataform.append('description', description);
955 dataform.append('is_editor', isWPSCEditor);
956 dataform.append('action', 'wpsc_it_reply_and_close');
957 dataform.append('_ajax_nonce', nonce);
958 jQuery('.wpsc-body').html(supportcandy.loader_html);
959 jQuery.ajax({
960 url: supportcandy.ajax_url,
961 type: 'POST',
962 data: dataform,
963 processData: false,
964 contentType: false
965 }).done(function (res) {
966 wpsc_clear_saved_draft_reply(ticket_id);
967 wpsc_run_ajax_background_process();
968 wpsc_after_ticket_reply(ticket_id);
969 wpsc_after_close_ticket(ticket_id);
970 if ( jQuery.isFunction(jQuery.fn.wpsc_get_ticket_list) ) { wpsc_get_ticket_list();}
971 });
972 }
973
974 // auto-save
975 wpsc_auto_save(<?php echo esc_attr( self::$ticket->id ); ?>);
976 function wpsc_auto_save( ticket_id ) {
977
978 var draft_replies = JSON.parse(localStorage.getItem('wpsc_auto_draft')) || {};
979 current_ticket = jQuery('#wpsc-current-ticket').val();
980 if( current_ticket != ticket_id ){
981 return;
982 }
983 setTimeout(() => {
984 wpsc_auto_save( ticket_id );
985 }, 10000);
986
987 var is_tinymce = (typeof tinyMCE != "undefined") && tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden();
988 var description = is_tinymce && tinymce.get('description') ? tinyMCE.get('description').getContent() : jQuery('#description').val().trim();
989 if ( ! description ) return;
990
991 // Check if the ticket_id already exists in the array
992 if (draft_replies.hasOwnProperty(ticket_id)) {
993 // Update existing entry
994 draft_replies[ticket_id]['description'] = description;
995 draft_replies[ticket_id]['date_updated'] = new Date().getTime();
996 } else {
997 // Add new entry
998 draft_replies[ticket_id] = {
999 'description': description,
1000 'date_updated': new Date().getTime()
1001 };
1002 }
1003 localStorage.setItem('wpsc_auto_draft', JSON.stringify(draft_replies));
1004 }
1005
1006 jQuery(document).ready(function(){
1007 //autofill reply textarea.
1008 ticket_id = jQuery('#wpsc-current-ticket').val();
1009 draft_replies = JSON.parse(localStorage.getItem('wpsc_auto_draft')) || {};
1010 savedText = ! jQuery.isEmptyObject(draft_replies) && draft_replies.hasOwnProperty(ticket_id) ? draft_replies[ticket_id]['description'] : '';
1011 jQuery('#description').val( savedText );
1012 });
1013
1014 jQuery('.wpsc_textarea').keyup(function() {
1015 if( jQuery(this).val() == '' ) {
1016 ticket_id = jQuery('#wpsc-current-ticket').val();
1017 wpsc_clear_saved_draft_reply( ticket_id );
1018 }
1019 });
1020 </script>
1021 <?php
1022 }
1023
1024 /**
1025 * Get threads section of individual ticket
1026 *
1027 * @return void
1028 */
1029 public static function get_thread_section() {
1030
1031 $gs = get_option( 'wpsc-gs-general' );
1032 $filters = array(
1033 'meta_query' => array(
1034 'relation' => 'AND',
1035 array(
1036 'slug' => 'ticket',
1037 'compare' => '=',
1038 'val' => self::$ticket->id,
1039 ),
1040 ),
1041 'orderby' => 'id',
1042 'order' => 'DESC',
1043 );
1044
1045 $thread_types = array(
1046 'slug' => 'type',
1047 'compare' => 'IN',
1048 'val' => array( 'report', 'reply' ),
1049 );
1050
1051 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
1052 $thread_types['val'][] = 'note';
1053 }
1054 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
1055 $thread_types['val'][] = 'log';
1056 }
1057
1058 $filters['meta_query'][] = $thread_types;
1059
1060 $filters = apply_filters( 'wpsc_filter_ticket_threads', $filters );
1061
1062 $response = WPSC_Thread::find( $filters );
1063 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
1064
1065 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'bottom' ) :
1066 ?>
1067 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
1068 <button class="wpsc-button small secondary" onclick="wpsc_load_older_threads(this, <?php echo esc_attr( self::$ticket->id ); ?>);"><?php esc_attr_e( 'Load older communications', 'supportcandy' ); ?></button>
1069 </div>
1070 <?php
1071 endif;
1072 ?>
1073
1074 <div class="wpsc-it-body-item wpsc-it-thread-section-container">
1075 <?php
1076 if ( $gs['reply-form-position'] == 'top' ) {
1077 foreach ( $response['results'] as $thread ) {
1078 if ( $thread->type == 'log' ) {
1079 self::print_log( $thread );
1080 } else {
1081 self::print_thread( $thread );
1082 }
1083 }
1084 } else {
1085 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
1086 $thread = $response['results'][ $i ];
1087 if ( $thread->type == 'log' ) {
1088 self::print_log( $thread );
1089 } else {
1090 self::print_thread( $thread );
1091 }
1092 }
1093 }
1094 ?>
1095 </div>
1096
1097 <script>
1098
1099 jQuery(document).find('.thread-text').each(function(){
1100 var height = parseInt(jQuery(this).height());
1101 <?php
1102 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1103 if ( $advanced['view-more'] ) {
1104 ?>
1105 if( height > 100){
1106 jQuery(this).height(100);
1107 jQuery(this).parent().find('.wpsc-ticket-thread-expander').text(supportcandy.translations.view_more);
1108 jQuery(this).parent().find('.wpsc-ticket-thread-expander').show();
1109 }
1110 <?php
1111 } else {
1112 ?>
1113 jQuery(this).parent().find('.thread-text').height('auto');
1114 <?php
1115 }
1116 ?>
1117
1118 });
1119
1120 supportcandy.threads = {last_thread: <?php echo esc_attr( $last_id ); ?>}
1121 </script>
1122 <?php
1123
1124 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'top' ) :
1125 ?>
1126 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
1127 <button class="wpsc-button small secondary" onclick="wpsc_load_older_threads(this, <?php echo esc_attr( self::$ticket->id ); ?>);"><?php esc_attr_e( 'Load older communications', 'supportcandy' ); ?></button>
1128 </div>
1129 <?php
1130 endif;
1131 }
1132
1133 /**
1134 * Print thread
1135 *
1136 * @param WPSC_Thread $thread - thread object.
1137 * @return void
1138 */
1139 public static function print_thread( $thread ) {
1140
1141 // If thread is of type "Private Note", return if current user does not have permission to view logs.
1142 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
1143 return;
1144 }
1145
1146 // If thread is deleted, show only if current user has view log permission. It will show deleteted log with view content link.
1147 if ( ! $thread->is_active && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
1148 return;
1149 }
1150
1151 $settings = get_option( 'wpsc-gs-general' );
1152 $current_user = WPSC_Current_User::$current_user;
1153 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1154
1155 $now = new DateTime();
1156 $date = $thread->date_created->setTimezone( wp_timezone() );
1157 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
1158 $time_title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1159 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1160 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
1161
1162 if (
1163 ! is_object( $thread->seen ) &&
1164 $current_user->customer == $thread->ticket->customer &&
1165 in_array( $thread->type, array( 'report', 'reply' ) )
1166 ) {
1167
1168 $today = new DateTime();
1169 $thread->seen = $today->format( 'Y-m-d H:i:s' );
1170 $thread->save();
1171 }
1172
1173 $classes = $thread->type == 'note' ? 'note' : 'reply';
1174 if ( in_array( $thread->type, array( 'report', 'reply' ) ) ) {
1175 $thread_user = get_user_by( 'email', $thread->customer->email );
1176 $user_class = $thread_user && $thread_user->has_cap( 'wpsc_agent' ) ? 'agent' : 'customer';
1177 $classes = $classes . ' ' . $user_class;
1178 }
1179 $classes = apply_filters( 'wpsc_it_thread_add_classes', $classes, $thread );
1180 self::$thread_actions = apply_filters( 'wpsc_it_filter_thread_actions', self::$thread_actions, $thread );
1181 ?>
1182
1183 <div class="wpsc-thread <?php echo esc_attr( $classes ); ?> <?php echo esc_attr( $thread->id ); ?>">
1184
1185 <div class="thread-avatar">
1186 <?php echo get_avatar( $thread->customer->email, 32 ); ?>
1187 </div>
1188
1189 <div class="thread-body">
1190
1191 <div class="thread-header">
1192
1193 <div class="user-info">
1194 <div style="display: flex;">
1195 <h2 class="user-name"><?php echo esc_attr( $thread->customer->name ) . ' '; ?></h2>
1196 <h2>
1197 <small class="thread-type">
1198 <i>
1199 <?php
1200 switch ( $thread->type ) {
1201
1202 case 'report':
1203 esc_attr_e( 'reported', 'supportcandy' );
1204 break;
1205
1206 case 'reply':
1207 esc_attr_e( 'replied', 'supportcandy' );
1208 break;
1209
1210 case 'note':
1211 esc_attr_e( 'added a note', 'supportcandy' );
1212 break;
1213 }
1214 ?>
1215 </i>
1216 </small>
1217 </h2>
1218 </div>
1219 <span class="thread-time" title="<?php echo esc_attr( $time_title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1220 </div>
1221 <?php
1222 do_action( 'wpsc_it_thread_header', $thread );
1223 if ( $thread->is_active ) {
1224 ?>
1225 <div class="actions">
1226 <?php
1227 foreach ( self::$thread_actions as $action ) :
1228 ?>
1229 <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'] ) ) . '\')'; ?>">
1230 <?php WPSC_Icons::get( $action['icon'] ); ?>
1231 </span>
1232 <?php
1233 endforeach;
1234 ?>
1235 </div>
1236 <?php
1237 }
1238 ?>
1239
1240 </div>
1241
1242 <div class="thread-text">
1243 <?php
1244 if ( $thread->is_active ) {
1245 echo wp_kses_post( $thread->body );
1246 } else {
1247 $logs = $thread->get_logs();
1248 ?>
1249 <i>
1250 <?php
1251 printf(
1252 /* translators: %1$s: customer name, %2$s: datetime */
1253 esc_attr__( 'This thread was deleted by %1$s on %2$s.', 'supportcandy' ),
1254 '<strong>' . esc_attr( $logs[0]->modified_by->name ) . '</strong>',
1255 esc_attr( $logs[0]->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1256 );
1257 ?>
1258 <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' ) ); ?>')">
1259 <?php esc_attr_e( 'View thread!', 'supportcandy' ); ?>
1260 </a>
1261 </i>
1262 <?php
1263 }
1264 ?>
1265 </div>
1266
1267 <?php
1268 if ( $advanced['view-more'] ) {
1269 ?>
1270 <div class="wpsc-ticket-thread-expander" onclick="wpsc_ticket_thread_expander_toggle(this);" style="display: none;">
1271 <?php esc_attr_e( 'View More ...', 'supportcandy' ); ?>
1272 </div>
1273 <?php
1274 }
1275
1276 // Thread attachments.
1277 if ( $thread->is_active && $thread->attachments ) {
1278 ?>
1279 <div class="wpsc-thread-attachments">
1280 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
1281 <?php
1282 foreach ( $thread->attachments as $attachment ) {
1283 ?>
1284 <div class="wpsc-attachment-item">
1285 <?php
1286 $download_url = site_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&auth_code=' . $thread->ticket->auth_code;
1287 ?>
1288 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
1289 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
1290 </div>
1291 <?php
1292 }
1293 ?>
1294 </div>
1295 <?php
1296 }
1297
1298 // Thread logs.
1299 $logs = $thread->get_logs();
1300 if ( $thread->is_active && self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && $logs ) {
1301 ?>
1302 <div class="wpsc-thread-logs">
1303 <?php
1304 foreach ( $logs as $log ) {
1305
1306 $log_body = json_decode( $log->body );
1307 ?>
1308 <div class="wpsc-thread-log-item">
1309 <?php
1310 switch ( $log_body->type ) {
1311
1312 case 'modify':
1313 printf(
1314 /* translators: %1$s: customer name, %2$s: date time */
1315 esc_attr__( 'Modified by %1$s on %2$s.', 'supportcandy' ),
1316 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1317 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1318 );
1319 ?>
1320 <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">
1321 <?php esc_attr_e( 'View change', 'supportcandy' ); ?>
1322 </a>
1323 <?php
1324 break;
1325
1326 case 'delete':
1327 printf(
1328 /* translators: %1$s: customer name, %2$s: date time */
1329 esc_attr__( 'Deleted by %1$s on %2$s', 'supportcandy' ),
1330 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1331 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1332 );
1333 break;
1334
1335 case 'restore':
1336 printf(
1337 /* translators: %1$s: customer name, %2$s: date time */
1338 esc_attr__( 'Restored by %1$s on %2$s', 'supportcandy' ),
1339 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1340 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1341 );
1342 break;
1343 }
1344 ?>
1345 </div>
1346 <?php
1347 }
1348 ?>
1349 </div>
1350 <?php
1351 }
1352 ?>
1353
1354 </div>
1355
1356 </div>
1357 <?php
1358 }
1359
1360 /**
1361 * Print thread log
1362 *
1363 * @param WPSC_Thread $thread - thread object.
1364 * @return void
1365 */
1366 public static function print_log( $thread ) {
1367
1368 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
1369 return;
1370 }
1371
1372 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1373 $now = new DateTime();
1374 $date = $thread->date_created->setTimezone( wp_timezone() );
1375 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
1376 $title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1377 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1378 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
1379
1380 $body = json_decode( $thread->body );
1381 $is_json = ( json_last_error() == JSON_ERROR_NONE ) ? true : false;
1382
1383 if ( $is_json ) {
1384
1385 $cf = WPSC_Custom_Field::get_cf_by_slug( $body->slug );
1386 if ( ! $cf ) {
1387 return;
1388 }
1389 ?>
1390 <div class="wpsc-thread log">
1391 <div class="thread-avatar">
1392 <?php
1393 if ( $thread->customer ) {
1394 echo get_avatar( $thread->customer->email, 32 );
1395 } else {
1396 WPSC_Icons::get( 'system' );
1397 }
1398 ?>
1399 </div>
1400 <div class="thread-body">
1401 <div class="thread-header">
1402 <div class="user-info">
1403 <div>
1404 <?php
1405 if ( $thread->customer ) {
1406
1407 printf(
1408 /* translators: %1$s: User Name, %2$s: Field Name */
1409 esc_attr__( '%1$s changed the %2$s', 'supportcandy' ),
1410 '<strong>' . esc_attr( $thread->customer->name ) . '</strong>',
1411 '<strong>' . esc_attr( $cf->name ) . '</strong>'
1412 );
1413
1414 } else {
1415
1416 printf(
1417 /* translators: %1$s: Field Name */
1418 esc_attr__( 'The %1$s has been changed', 'supportcandy' ),
1419 '<strong>' . esc_attr( $cf->name ) . '</strong>'
1420 );
1421 }
1422 ?>
1423 </div>
1424 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1425 </div>
1426 </div>
1427 <div class="wpsc-log-diff">
1428 <div class="lhs"><?php $cf->type::print_val( $cf, $body->prev ); ?></div>
1429 <div class="transform-icon">
1430 <?php
1431 if ( is_rtl() ) {
1432 WPSC_Icons::get( 'arrow-left' );
1433 } else {
1434 WPSC_Icons::get( 'arrow-right' );
1435 }
1436 ?>
1437 </div>
1438 <div class="rhs"><?php $cf->type::print_val( $cf, $body->new ); ?></div>
1439 </div>
1440 </div>
1441 </div>
1442 <?php
1443
1444 } else {
1445
1446 ?>
1447 <div class="wpsc-thread log">
1448 <div class="thread-avatar">
1449 <?php
1450 WPSC_Icons::get( 'system' );
1451 ?>
1452 </div>
1453 <div class="thread-body">
1454 <div><?php echo wp_kses_post( $thread->body ); ?></div>
1455 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1456 </div>
1457 </div>
1458 <?php
1459 }
1460 }
1461
1462 /**
1463 * Check whether current user is customer or not
1464 *
1465 * @return boolean
1466 */
1467 public static function is_customer() {
1468
1469 $current_user = WPSC_Current_User::$current_user;
1470 if ( ! $current_user->is_customer ) {
1471 return false;
1472 }
1473
1474 $adv_setting = get_option( 'wpsc-ms-advanced-settings' );
1475 if ( $adv_setting['public-mode'] && ! $current_user->is_agent ) {
1476 return true;
1477 }
1478
1479 $allowed_customers = apply_filters( 'wpsc_non_agent_ticket_customers_allowed', array( self::$ticket->customer->id ), self::$ticket );
1480 if ( in_array( $current_user->customer->id, $allowed_customers ) ) {
1481 return true;
1482 }
1483
1484 return false;
1485 }
1486
1487 /**
1488 * Agent ticket access for cap
1489 *
1490 * @param string $cap - capability name.
1491 * @return boolean
1492 */
1493 public static function has_ticket_cap( $cap ) {
1494
1495 $current_user = WPSC_Current_User::$current_user;
1496
1497 $assigned_agents = array_map(
1498 fn ( $agent ) => $agent->id,
1499 self::$ticket->assigned_agent
1500 );
1501
1502 $flag = false;
1503 if (
1504 (
1505 ! self::$ticket->assigned_agent &&
1506 $current_user->agent->has_cap( $cap . '-unassigned' )
1507 ) ||
1508 (
1509 in_array( $current_user->agent->id, $assigned_agents ) &&
1510 $current_user->agent->has_cap( $cap . '-assigned-me' )
1511 ) ||
1512 (
1513 self::$ticket->assigned_agent &&
1514 ! in_array( $current_user->agent->id, $assigned_agents ) &&
1515 $current_user->agent->has_cap( $cap . '-assigned-others' )
1516 )
1517 ) {
1518 $flag = true;
1519 }
1520
1521 return apply_filters( 'wpsc_it_has_ticket_cap', $flag, self::$ticket, $cap );
1522 }
1523
1524 /**
1525 * Post a reply to ticket
1526 *
1527 * @return void
1528 */
1529 public static function add_reply() {
1530
1531 if ( check_ajax_referer( 'general', '_ajax_nonce', false ) != 1 ) {
1532 wp_send_json_error( 'Unauthorized request!', 401 );
1533 }
1534
1535 $gs = get_option( 'wpsc-gs-general' );
1536 $current_user = WPSC_Current_User::$current_user;
1537 self::load_current_ticket();
1538
1539 if ( ! (
1540 ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) ||
1541 ( self::$view_profile == 'customer' && self::is_customer() )
1542 ) ) {
1543 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1544 }
1545
1546 // Set reply profile.
1547 self::$reply_profile = 'customer';
1548 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) {
1549 self::$reply_profile = 'agent';
1550 }
1551
1552 // Do not allow if ticket is deleted.
1553 if ( ! self::$ticket->is_active ) {
1554 wp_send_json_error( 'Something went wrong!', 400 );
1555 }
1556
1557 $advanced = get_option( 'wpsc-ms-advanced-settings' );
1558
1559 // check reply permission for public tickets.
1560 if ( ! $current_user->is_agent && $advanced['public-mode'] && ! $advanced['public-mode-reply'] && self::$ticket->customer->id != $current_user->customer->id ) {
1561 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1562 }
1563
1564 // description.
1565 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1566 if ( ! $description ) {
1567 wp_send_json_error( 'Something went wrong!', 401 );
1568 }
1569
1570 // replace new line with br if no text editor.
1571 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1572 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1573
1574 // replace macros only when current user is an agent & check if agent has permission to view customer email address.
1575 if ( $current_user->is_agent && ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1576 $description = str_replace( '{{customer_email}}', '', $description );
1577 }
1578 $description = $current_user->is_agent ? WPSC_Macros::replace( $description, self::$ticket ) : $description;
1579
1580 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1581 // remove divs from message.
1582 $description = preg_replace(
1583 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1584 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1585 $description
1586 );
1587
1588 // set signature if agent.
1589 $signature = $current_user->is_agent ? $current_user->agent->get_signature() : '';
1590 if ( $signature ) {
1591 $description .= '<br>' . $signature;
1592 }
1593
1594 // add ticket auth url to tinymce attachments.
1595 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1596 foreach ( $matches[0] as $url ) {
1597 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1598 $description = str_replace( $url, $new_url, $description );
1599 }
1600 }
1601
1602 // description attachments.
1603 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1604
1605 // submit reply.
1606 $thread = WPSC_Thread::insert(
1607 array(
1608 'ticket' => self::$ticket->id,
1609 'customer' => $current_user->customer->id,
1610 'type' => 'reply',
1611 'body' => $description,
1612 'attachments' => implode( '|', $attachments ),
1613 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1614 'source' => 'browser',
1615 'os' => WPSC_DF_OS::get_user_platform(),
1616 'browser' => WPSC_DF_Browser::get_user_browser(),
1617 )
1618 );
1619
1620 // activate description attachments.
1621 foreach ( $attachments as $id ) :
1622 $attachment = new WPSC_Attachment( $id );
1623 $attachment->is_active = 1;
1624 $attachment->source = 'reply';
1625 $attachment->source_id = $thread->id;
1626 $attachment->ticket_id = self::$ticket->id;
1627 $attachment->save();
1628 endforeach;
1629
1630 // activate description editor img attachments.
1631 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(&auth_code=[^&\s]*)?/', $description, $matches ) ) {
1632 foreach ( $matches[1] as $id ) {
1633 $attachment = new WPSC_Attachment( $id );
1634 $attachment->is_active = 1;
1635 $attachment->source_id = $thread->id;
1636 $attachment->ticket_id = self::$ticket->id;
1637 $attachment->source = 'img_editor';
1638 $attachment->save();
1639 }
1640 }
1641
1642 self::$ticket->date_updated = new DateTime();
1643 self::$ticket->last_reply_on = new DateTime();
1644 self::$ticket->last_reply_by = $current_user->customer->id;
1645 self::$ticket->last_reply_source = 'browser';
1646 self::$ticket->save();
1647
1648 do_action( 'wpsc_post_reply', $thread );
1649 wp_die();
1650 }
1651
1652 /**
1653 * Post a note to ticket
1654 *
1655 * @return void
1656 */
1657 public static function add_note() {
1658
1659 if ( check_ajax_referer( 'general', '_ajax_nonce', false ) != 1 ) {
1660 wp_send_json_error( 'Unauthorized request!', 401 );
1661 }
1662
1663 $gs = get_option( 'wpsc-gs-general' );
1664 $current_user = WPSC_Current_User::$current_user;
1665 self::load_current_ticket();
1666
1667 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
1668 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1669 }
1670
1671 // Do not allow if ticket is deleted.
1672 if ( ! self::$ticket->is_active ) {
1673 wp_send_json_error( 'Something went wrong!', 400 );
1674 }
1675
1676 // description.
1677 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1678 if ( ! $description ) {
1679 wp_send_json_error( 'Something went wrong!', 401 );
1680 }
1681
1682 // replace new line with br if no text editor.
1683 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1684 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1685
1686 // check if agent has permission to view customer email address.
1687 if ( ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1688 $description = str_replace( '{{customer_email}}', '', $description );
1689 }
1690
1691 // add ticket auth url to tinymce attachments.
1692 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1693 foreach ( $matches[0] as $url ) {
1694 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1695 $description = str_replace( $url, $new_url, $description );
1696 }
1697 }
1698
1699 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1700 // remove divs from message.
1701 $description = preg_replace(
1702 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1703 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1704 $description
1705 );
1706
1707 // description attachments.
1708 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1709
1710 // submit note.
1711 $thread = WPSC_Thread::insert(
1712 array(
1713 'ticket' => self::$ticket->id,
1714 'customer' => $current_user->customer->id,
1715 'type' => 'note',
1716 'body' => WPSC_Macros::replace( $description, self::$ticket ),
1717 'attachments' => implode( '|', $attachments ),
1718 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1719 'source' => 'browser',
1720 'os' => WPSC_DF_OS::get_user_platform(),
1721 'browser' => WPSC_DF_Browser::get_user_browser(),
1722 )
1723 );
1724
1725 // activate description attachments.
1726 foreach ( $attachments as $id ) :
1727 $attachment = new WPSC_Attachment( $id );
1728 $attachment->is_active = 1;
1729 $attachment->source = 'note';
1730 $attachment->source_id = $thread->id;
1731 $attachment->ticket_id = self::$ticket->id;
1732 $attachment->save();
1733 endforeach;
1734
1735 // activate description editor img attachments.
1736 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(&auth_code=[^&\s]*)?/', $description, $matches ) ) {
1737 foreach ( $matches[1] as $id ) {
1738 $attachment = new WPSC_Attachment( $id );
1739 $attachment->is_active = 1;
1740 $attachment->source_id = $thread->id;
1741 $attachment->ticket_id = self::$ticket->id;
1742 $attachment->source = 'img_editor';
1743 $attachment->save();
1744 }
1745 }
1746
1747 self::$ticket->date_updated = new DateTime();
1748 self::$ticket->save();
1749
1750 do_action( 'wpsc_submit_note', $thread );
1751 wp_die();
1752 }
1753
1754 /**
1755 * Post a reply & close the ticket
1756 *
1757 * @return void
1758 */
1759 public static function it_reply_and_close() {
1760
1761 if ( check_ajax_referer( 'wpsc_it_reply_and_close', '_ajax_nonce', false ) != 1 ) {
1762 wp_send_json_error( 'Unauthorized request!', 401 );
1763 }
1764
1765 $gs = get_option( 'wpsc-gs-general' );
1766 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
1767 $advanced = get_option( 'wpsc-ms-advanced-settings' );
1768 $current_user = WPSC_Current_User::$current_user;
1769 self::load_current_ticket();
1770
1771 if ( ! $advanced['reply-close'] ) {
1772 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1773 }
1774
1775 if ( ! (
1776 ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) ||
1777 ( self::$view_profile == 'customer' && self::is_customer() )
1778 ) ) {
1779 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1780 }
1781
1782 // Set reply profile.
1783 self::$reply_profile = 'customer';
1784 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) {
1785 self::$reply_profile = 'agent';
1786 }
1787
1788 // Do not allow if ticket is deleted.
1789 if ( ! self::$ticket->is_active ) {
1790 wp_send_json_error( 'Something went wrong!', 400 );
1791 }
1792
1793 // check reply permission for public tickets.
1794 if ( ! $current_user->is_agent && $advanced['public-mode'] && ! $advanced['public-mode-reply'] && self::$ticket->customer->id != $current_user->customer->id ) {
1795 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1796 }
1797
1798 // description.
1799 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1800 if ( ! $description ) {
1801 wp_send_json_error( 'Something went wrong!', 401 );
1802 }
1803
1804 // status change permission validation.
1805 $close_flag = false;
1806 if ( $current_user->is_customer &&
1807 (
1808 ( $current_user->customer->id == self::$ticket->customer->id && in_array( 'customer', $gs['allow-close-ticket'] ) ) ||
1809 ( $current_user->is_agent && self::has_ticket_cap( 'cs' ) && in_array( $current_user->agent->role, $gs['allow-close-ticket'] ) )
1810 )
1811 ) {
1812 $close_flag = true;
1813 }
1814
1815 $close_flag = apply_filters( 'wpsc_it_action_close_flag', $close_flag, self::$ticket );
1816
1817 if ( ! (
1818 $close_flag &&
1819 ! (
1820 self::$ticket->status->id == $gs['close-ticket-status'] ||
1821 in_array( self::$ticket->status->id, $tl_advanced['closed-ticket-statuses'] )
1822 )
1823 ) ) {
1824 wp_send_json_error( 'Unauthorized!', 401 );
1825 }
1826
1827 // replace new line with br if no text editor.
1828 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1829 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1830
1831 // replace macros only when current user is an agent & check if agent has permission to view customer email address.
1832 if ( $current_user->is_agent && ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1833 $description = str_replace( '{{customer_email}}', '', $description );
1834 }
1835 $description = $current_user->is_agent ? WPSC_Macros::replace( $description, self::$ticket ) : $description;
1836
1837 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1838 // remove divs from message.
1839 $description = preg_replace(
1840 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1841 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1842 $description
1843 );
1844
1845 // set signature if agent.
1846 $signature = $current_user->is_agent ? $current_user->agent->get_signature() : '';
1847 if ( $signature ) {
1848 $description .= '<br>' . $signature;
1849 }
1850
1851 // add ticket auth url to tinymce attachments.
1852 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1853 foreach ( $matches[0] as $url ) {
1854 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1855 $description = str_replace( $url, $new_url, $description );
1856 }
1857 }
1858
1859 // description attachments.
1860 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1861
1862 // submit reply.
1863 $thread = WPSC_Thread::insert(
1864 array(
1865 'ticket' => self::$ticket->id,
1866 'customer' => $current_user->customer->id,
1867 'type' => 'reply',
1868 'body' => $description,
1869 'attachments' => implode( '|', $attachments ),
1870 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1871 'source' => 'browser',
1872 'os' => WPSC_DF_OS::get_user_platform(),
1873 'browser' => WPSC_DF_Browser::get_user_browser(),
1874 )
1875 );
1876
1877 // activate description attachments.
1878 foreach ( $attachments as $id ) :
1879 $attachment = new WPSC_Attachment( $id );
1880 $attachment->is_active = 1;
1881 $attachment->source = 'reply';
1882 $attachment->source_id = $thread->id;
1883 $attachment->ticket_id = self::$ticket->id;
1884 $attachment->save();
1885 endforeach;
1886
1887 // activate description editor img attachments.
1888 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(&auth_code=[^&\s]*)?/', $description, $matches ) ) {
1889 foreach ( $matches[1] as $id ) {
1890 $attachment = new WPSC_Attachment( $id );
1891 $attachment->is_active = 1;
1892 $attachment->source_id = $thread->id;
1893 $attachment->ticket_id = self::$ticket->id;
1894 $attachment->source = 'img_editor';
1895 $attachment->save();
1896 }
1897 }
1898
1899 $prev = self::$ticket->status->id;
1900
1901 self::$ticket->last_reply_on = new DateTime();
1902 self::$ticket->last_reply_by = $current_user->customer->id;
1903 self::$ticket->last_reply_source = 'browser';
1904 self::$ticket->status = $gs['close-ticket-status'];
1905 self::$ticket->date_closed = new DateTime( 'now' );
1906 self::$ticket->date_updated = new DateTime();
1907 self::$ticket->save();
1908
1909 $thread->ticket = self::$ticket;
1910
1911 remove_action( 'wpsc_post_reply', array( 'WPSC_GS_General', 'change_ticket_status' ) );
1912
1913 do_action( 'wpsc_post_reply', $thread );
1914
1915 remove_action( 'wpsc_change_ticket_status', array( 'WPSC_EN_Change_Ticket_Status', 'process_event' ), 200 );
1916
1917 do_action( 'wpsc_change_ticket_status', self::$ticket, $prev, self::$ticket->status->id, $current_user->customer->id );
1918
1919 wp_die();
1920 }
1921
1922 /**
1923 * Change status of individual ticket
1924 *
1925 * @param integer $prev - privious status.
1926 * @param integer $new - new status.
1927 * @param integer $customer_id - customer id.
1928 * @return void
1929 */
1930 public static function change_status( $prev, $new, $customer_id ) {
1931
1932 $gs = get_option( 'wpsc-gs-general' );
1933 self::$ticket->status = $new;
1934 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
1935 if ( $new == $gs['close-ticket-status'] || in_array( $new, $tl_advanced['closed-ticket-statuses'] ) ) {
1936 self::$ticket->date_closed = new DateTime( 'now' );
1937 } else {
1938 self::$ticket->date_closed = '0000-00-00 00:00:00';
1939 }
1940
1941 self::$ticket->date_updated = new DateTime();
1942 self::$ticket->save();
1943 do_action( 'wpsc_change_ticket_status', self::$ticket, $prev, $new, $customer_id );
1944 }
1945
1946 /**
1947 * Change category of individual ticket
1948 *
1949 * @param integer $prev - privious category.
1950 * @param integer $new - new category.
1951 * @param integer $customer_id - customer id.
1952 * @return void
1953 */
1954 public static function change_category( $prev, $new, $customer_id ) {
1955
1956 self::$ticket->category = $new;
1957 self::$ticket->date_updated = new DateTime();
1958 self::$ticket->save();
1959 do_action( 'wpsc_change_ticket_category', self::$ticket, $prev, $new, $customer_id );
1960 }
1961
1962 /**
1963 * Change priority of individual ticket
1964 *
1965 * @param integer $prev - privious priority.
1966 * @param integer $new - new priority.
1967 * @param integer $customer_id - customer id.
1968 * @return void
1969 */
1970 public static function change_priority( $prev, $new, $customer_id ) {
1971
1972 self::$ticket->priority = $new;
1973 self::$ticket->date_updated = new DateTime();
1974 self::$ticket->save();
1975 do_action( 'wpsc_change_ticket_priority', self::$ticket, $prev, $new, $customer_id );
1976 }
1977
1978 /**
1979 * Change customer
1980 *
1981 * @param WPSC_Customer $prev - privious customer.
1982 * @param WPSC_Customer $new - new customer.
1983 * @param integer $customer_id - customer id.
1984 * @return void
1985 */
1986 public static function change_raised_by( $prev, $new, $customer_id ) {
1987
1988 self::$ticket->customer = $new;
1989 self::$ticket->date_updated = new DateTime();
1990 self::$ticket->save();
1991 do_action( 'wpsc_change_raised_by', self::$ticket, $prev, $new, $customer_id );
1992 }
1993
1994 /**
1995 * Change assignee
1996 *
1997 * @param array $prev - privious assignee.
1998 * @param array $new - new assignee.
1999 * @param integer $customer_id - customer id.
2000 * @return void
2001 */
2002 public static function change_assignee( $prev, $new, $customer_id ) {
2003
2004 self::$ticket->assigned_agent = $new;
2005 self::$ticket->prev_assignee = $prev;
2006 self::$ticket->date_updated = new DateTime();
2007 self::$ticket->save();
2008 do_action( 'wpsc_change_assignee', self::$ticket, $prev, $new, $customer_id );
2009 }
2010
2011 /**
2012 * Change tags
2013 *
2014 * @param array $prev - privious tag.
2015 * @param array $new - new tag.
2016 * @param integer $customer_id - customer id.
2017 * @return void
2018 */
2019 public static function change_tag( $prev, $new, $customer_id ) {
2020
2021 self::$ticket->tags = $new;
2022 self::$ticket->date_updated = new DateTime();
2023 self::$ticket->save();
2024 do_action( 'wpsc_change_tag', self::$ticket, $prev, $new, $customer_id );
2025 }
2026
2027 /**
2028 * Get duplicate ticket modal
2029 *
2030 * @return void
2031 */
2032 public static function get_duplicate_ticket() {
2033
2034 if ( check_ajax_referer( 'wpsc_it_get_duplicate_ticket', '_ajax_nonce', false ) != 1 ) {
2035 wp_send_json_error( 'Unauthorized request!', 401 );
2036 }
2037
2038 self::load_current_ticket();
2039
2040 if ( self::$is_restricted ) {
2041 wp_send_json_error( 'Unauthorized request!', 401 );
2042 }
2043
2044 $current_user = WPSC_Current_User::$current_user;
2045 if ( self::$view_profile != 'agent' ) {
2046 wp_send_json_error( 'Something went wrong!', 401 );
2047 }
2048
2049 $ticket = self::$ticket;
2050 $title = esc_attr__( 'Duplicate', 'supportcandy' );
2051
2052 ob_start();
2053 ?>
2054 <form action="#" onsubmit="return false;" class="duplicate">
2055 <div class="wpsc-input-group">
2056 <div class="label-container">
2057 <label for=""><?php esc_attr_e( 'Subject', 'supportcandy' ); ?></label>
2058 </div>
2059 <input name="subject" type="text" value="<?php echo esc_attr( $ticket->subject ); ?>" autocomplete="off">
2060 </div>
2061 <?php do_action( 'wpsc_it_get_duplicate_ticket', $ticket ); ?>
2062 <input type="hidden" name="action" value="wpsc_it_set_duplicate_ticket">
2063 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( $ticket->id ); ?>">
2064 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_duplicate_ticket' ) ); ?>">
2065 </form>
2066 <?php
2067 $body = ob_get_clean();
2068
2069 ob_start();
2070 ?>
2071 <button class="wpsc-button small primary" onclick="wpsc_it_set_duplicate_ticket(this, <?php echo esc_attr( $ticket->id ); ?>);">
2072 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
2073 </button>
2074 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2075 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
2076 </button>
2077 <?php
2078 do_action( 'wpsc_it_get_duplicate_ticket_footer', $ticket );
2079 $footer = ob_get_clean();
2080
2081 $response = array(
2082 'title' => $title,
2083 'body' => $body,
2084 'footer' => $footer,
2085 );
2086 wp_send_json( $response );
2087 }
2088
2089 /**
2090 * Set duplicate ticket
2091 *
2092 * @return void
2093 */
2094 public static function set_duplicate_ticket() {
2095
2096 if ( check_ajax_referer( 'wpsc_it_set_duplicate_ticket', '_ajax_nonce', false ) != 1 ) {
2097 wp_send_json_error( 'Unauthorized request!', 401 );
2098 }
2099
2100 self::load_current_ticket();
2101 $current_user = WPSC_Current_User::$current_user;
2102 if ( self::$view_profile != 'agent' && ! self::has_ticket_cap( 'dt' ) ) {
2103 wp_send_json_error( 'Something went wrong!', 401 );
2104 }
2105
2106 $ticket = self::$ticket;
2107 $threads = $ticket->get_threads( 1, 0, array( 'report', 'reply' ), 'date_created', 'ASC' );
2108
2109 // ignore custom field types while duplicating ticket.
2110 $ignore_cft = apply_filters(
2111 'wpsc_duplicate_ticket_ignore_cft',
2112 array(
2113 'cf_html',
2114 'df_description',
2115 'df_id',
2116 'df_ip_address',
2117 'df_browser',
2118 'df_assigned_agent',
2119 'df_prev_assignee',
2120 'df_date_closed',
2121 'df_add_recipients',
2122 'df_ip_address',
2123 'df_os',
2124 'df_sla',
2125 'df_last_reply_on',
2126 'df_last_reply_by',
2127 'df_sf_date',
2128 'df_sf_feedback',
2129 'df_sf_rating',
2130 'df_time_spent',
2131 'df_usergroups',
2132 'df_last_reply_source',
2133 )
2134 );
2135
2136 $data = array();
2137 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
2138 if ( ! in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
2139 continue;
2140 }
2141 if ( in_array( $cf->type::$slug, $ignore_cft ) ) {
2142 continue;
2143 }
2144 $data[ $cf->slug ] = $cf->type::get_duplicate_ticket_data( $cf, $ticket );
2145 }
2146 $data['assigned_agent'] = '';
2147 $duplicate = WPSC_Ticket::insert( $data );
2148
2149 foreach ( $threads as $thread ) {
2150 $attachments = array();
2151 foreach ( $thread->attachments as $attachment ) {
2152 $attachments[] = self::get_duplicate_attachment_id( $attachment->id );
2153 }
2154 $duplicate_thread = WPSC_Thread::insert(
2155 array(
2156 'ticket' => $duplicate->id,
2157 'customer' => $thread->customer->id,
2158 'type' => $thread->type,
2159 'body' => $thread->body,
2160 'attachments' => $attachments ? implode( '|', $attachments ) : '',
2161 'ip_address' => $thread->ip_address,
2162 'source' => $thread->source,
2163 'os' => $thread->os,
2164 'browser' => $thread->browser,
2165 'date_created' => $thread->date_created->format( 'Y-m-d H:i:s' ),
2166 'date_updated' => $thread->date_updated->format( 'Y-m-d H:i:s' ),
2167 )
2168 );
2169 foreach ( $attachments as $id ) :
2170 $attachment = new WPSC_Attachment( $id );
2171 $attachment->source = $duplicate_thread->type;
2172 $attachment->source_id = $duplicate_thread->id;
2173 $attachment->ticket_id = $duplicate->id;
2174 $attachment->save();
2175 endforeach;
2176
2177 // duplicate description attachments.
2178 $body_attachments = array();
2179 $attachment_of_body = WPSC_Attachment::find(
2180 array(
2181 'items_per_page' => 0,
2182 'meta_query' => array(
2183 'relation' => 'AND',
2184 array(
2185 'slug' => 'source',
2186 'compare' => '=',
2187 'val' => 'img_editor',
2188 ),
2189 array(
2190 'slug' => 'source_id',
2191 'compare' => '=',
2192 'val' => $thread->id,
2193 ),
2194 ),
2195 )
2196 )['results'];
2197 foreach ( $attachment_of_body as $img ) {
2198 $body_attachments[ $img->id ] = self::get_duplicate_attachment_id( $img->id );
2199 }
2200 foreach ( $body_attachments as $old_id => $id ) :
2201 $attachment = new WPSC_Attachment( $id );
2202 $attachment->source = 'img_editor';
2203 $attachment->source_id = $duplicate_thread->id;
2204 $attachment->ticket_id = $duplicate->id;
2205 $attachment->save();
2206
2207 $old_url = home_url( '/' ) . '?wpsc_attachment=' . $old_id;
2208 $new_url = home_url( '/' ) . '?wpsc_attachment=' . $id;
2209 $duplicate_thread->body = str_replace( $old_url, $new_url, $duplicate_thread->body );
2210 endforeach;
2211
2212 $duplicate_thread->save();
2213 }
2214
2215 do_action( 'wpsc_create_new_ticket', $duplicate );
2216
2217 wp_send_json( array( 'ticket_id' => $duplicate->id ) );
2218 }
2219
2220 /**
2221 * Delete ticket ajax request
2222 */
2223 public static function set_delete_ticket() {
2224
2225 if ( check_ajax_referer( 'wpsc_it_delete_ticket', '_ajax_nonce', false ) != 1 ) {
2226 wp_send_json_error( 'Unauthorized request!', 401 );
2227 }
2228
2229 $current_user = WPSC_Current_User::$current_user;
2230 if ( ! $current_user->is_agent ) {
2231 wp_send_json_error( 'Unauthorized request!', 401 );
2232 }
2233
2234 self::load_current_ticket();
2235
2236 if ( self::$is_restricted || ! self::has_ticket_cap( 'dtt' ) ) {
2237 wp_send_json_error( 'Unauthorized request!', 401 );
2238 }
2239
2240 self::delete_ticket();
2241 wp_die();
2242 }
2243
2244 /**
2245 * Delete current ticket
2246 *
2247 * @return void
2248 */
2249 public static function delete_ticket() {
2250
2251 self::$ticket->is_active = 0;
2252 self::$ticket->date_updated = new DateTime();
2253 self::$ticket->save();
2254 do_action( 'wpsc_delete_ticket', self::$ticket );
2255 }
2256
2257 /**
2258 * Restore ticket ajax request
2259 *
2260 * @return void
2261 */
2262 public static function ticket_restore() {
2263
2264 if ( check_ajax_referer( 'wpsc_it_ticket_restore', '_ajax_nonce', false ) != 1 ) {
2265 wp_send_json_error( 'Unauthorized request!', 401 );
2266 }
2267
2268 $current_user = WPSC_Current_User::$current_user;
2269 if ( ! $current_user->is_agent ) {
2270 wp_send_json_error( 'Unauthorized request!', 401 );
2271 }
2272
2273 self::load_current_ticket();
2274
2275 if ( ! self::$is_restricted || ! self::has_ticket_cap( 'dtt' ) ) {
2276 wp_send_json_error( 'Unauthorized request!', 401 );
2277 }
2278
2279 self::restore_ticket();
2280 wp_die();
2281 }
2282
2283 /**
2284 * Restore current ticket
2285 *
2286 * @return void
2287 */
2288 public static function restore_ticket() {
2289
2290 self::$ticket->is_active = 1;
2291 self::$ticket->date_updated = new DateTime();
2292 self::$ticket->save();
2293 do_action( 'wpsc_ticket_restore', self::$ticket );
2294 }
2295
2296 /**
2297 * Archive ticket ajax request
2298 */
2299 public static function set_archive_ticket() {
2300
2301 if ( check_ajax_referer( 'wpsc_it_archive_ticket', '_ajax_nonce', false ) != 1 ) {
2302 wp_send_json_error( 'Unauthorized request!', 401 );
2303 }
2304
2305 $current_user = WPSC_Current_User::$current_user;
2306 if ( ! $current_user->is_agent ) {
2307 wp_send_json_error( 'Unauthorized request!', 401 );
2308 }
2309
2310 self::load_current_ticket();
2311
2312 if ( self::$is_restricted ) {
2313 wp_send_json_error( 'Unauthorized request!', 401 );
2314 }
2315
2316 self::archive_ticket();
2317 wp_die();
2318 }
2319
2320 /**
2321 * Archive current ticket
2322 *
2323 * @return void
2324 */
2325 public static function archive_ticket() {
2326
2327 self::$ticket->live_agents = '';
2328 self::$ticket->date_updated = new DateTime();
2329 self::$ticket->save();
2330 $id = self::$ticket->id;
2331
2332 $success = WPSC_Archive_Ticket::set_archive_ticket( self::$ticket );
2333 if ( $success ) {
2334 $ticket = new WPSC_Archive_Ticket( $id );
2335 do_action( 'wpsc_ticket_archive', $ticket );
2336 }
2337 }
2338
2339 /**
2340 * Permanently delete ticket ajax request
2341 *
2342 * @return void
2343 */
2344 public static function set_delete_ticket_permanently() {
2345
2346 if ( check_ajax_referer( 'wpsc_it_delete_permanently', '_ajax_nonce', false ) != 1 ) {
2347 wp_send_json_error( 'Unauthorized request!', 401 );
2348 }
2349
2350 $current_user = WPSC_Current_User::$current_user;
2351 if ( ! $current_user->is_agent ) {
2352 wp_send_json_error( 'Unauthorized request!', 401 );
2353 }
2354
2355 self::load_current_ticket();
2356
2357 if ( ! self::has_ticket_cap( 'dtt' ) ) {
2358 wp_send_json_error( 'Unauthorized request!', 401 );
2359 }
2360
2361 self::delete_permanently();
2362 wp_die();
2363 }
2364
2365 /**
2366 * Permanently delete current ticket
2367 *
2368 * @return void
2369 */
2370 public static function delete_permanently() {
2371
2372 WPSC_Ticket::destroy( self::$ticket );
2373 do_action( 'wpsc_ticket_delete_permanently', self::$ticket );
2374 }
2375
2376 /**
2377 * Create ticket from thread
2378 *
2379 * @return void
2380 */
2381 public static function it_thread_new_ticket() {
2382
2383 if ( check_ajax_referer( 'wpsc_it_thread_new_ticket', '_ajax_nonce', false ) != 1 ) {
2384 wp_send_json_error( 'Unauthorized request!', 401 );
2385 }
2386
2387 if ( self::$is_restricted ) {
2388 wp_send_json_error( 'Unauthorized request!', 401 );
2389 }
2390
2391 self::load_current_ticket();
2392 $current_user = WPSC_Current_User::$current_user;
2393 self::load_current_ticket();
2394
2395 if ( !
2396 self::$view_profile == 'agent'
2397 ) {
2398 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
2399 }
2400
2401 $ticket = self::$ticket;
2402 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2403 $title = esc_attr__( 'Create ticket from thread', 'supportcandy' );
2404
2405 ob_start();
2406 ?>
2407 <form action="#" onsubmit="return false;" class="new_ticket_from_thread">
2408 <div class="wpsc-input-group">
2409 <div class="label-container">
2410 <label for=""><?php esc_attr_e( 'Subject', 'supportcandy' ); ?></label>
2411 </div>
2412 <input name="subject" type="text" value="<?php echo esc_attr( $ticket->subject ); ?>" autocomplete="off">
2413 </div>
2414 <?php do_action( 'wpsc_it_thread_new_ticket', $thread_id ); ?>
2415 <input type="hidden" name="action" value="wpsc_it_set_thread_new_ticket">
2416 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( $ticket->id ); ?>">
2417 <input type="hidden" name="thread_id" value="<?php echo esc_attr( $thread_id ); ?>">
2418 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_thread_new_ticket' ) ); ?>">
2419 </form>
2420 <?php
2421 $body = ob_get_clean();
2422
2423 ob_start();
2424 ?>
2425 <button class="wpsc-button small primary" onclick="wpsc_it_set_thread_new_ticket(this, <?php echo esc_attr( $thread_id ); ?>, '<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_thread_new_ticket' ) ); ?>');">
2426 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
2427 </button>
2428 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2429 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
2430 </button>
2431 <?php
2432 do_action( 'wpsc_it_set_thread_new_ticket_footer', $thread_id );
2433 $footer = ob_get_clean();
2434
2435 $response = array(
2436 'title' => $title,
2437 'body' => $body,
2438 'footer' => $footer,
2439 );
2440 wp_send_json( $response );
2441 }
2442
2443 /**
2444 * Get thread info
2445 *
2446 * @return void
2447 */
2448 public static function it_thread_info() {
2449
2450 if ( check_ajax_referer( 'wpsc_it_thread_info', '_ajax_nonce', false ) != 1 ) {
2451 wp_send_json_error( 'Unauthorized request!', 401 );
2452 }
2453
2454 self::load_current_ticket();
2455
2456 $current_user = WPSC_Current_User::$current_user;
2457
2458 if ( ! ( self::$view_profile == 'agent' ) ) {
2459 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2460 }
2461
2462 $title = esc_attr__( 'Thread info', 'supportcandy' );
2463
2464 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2465 if ( ! $thread_id ) {
2466 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2467 }
2468
2469 $thread = new WPSC_Thread( $thread_id );
2470 if ( ! $thread->id ) {
2471 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2472 }
2473
2474 if ( self::$ticket != $thread->ticket ) {
2475 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2476 }
2477
2478 $settings = get_option( 'wpsc-gs-general', array() );
2479 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
2480 $ip_address = $thread->ip_address ? $thread->ip_address : esc_attr__( 'Not Applicable', 'supportcandy' );
2481 $os = $thread->os ? $thread->os : esc_attr__( 'Not Applicable', 'supportcandy' );
2482 $browser = $thread->browser ? $thread->browser : esc_attr__( 'Not Applicable', 'supportcandy' );
2483 $source = $thread->source && isset( WPSC_DF_Source::$sources[ $thread->source ] ) ? WPSC_DF_Source::$sources[ $thread->source ] : esc_attr__( 'Not Applicable', 'supportcandy' );
2484
2485 ob_start();
2486 ?>
2487 <div class="wpsc-thread-info">
2488
2489 <div class="info-list-item">
2490 <div class="info-label"><?php esc_attr_e( 'Name', 'supportcandy' ); ?>:</div>
2491 <div class="info-val"><?php echo esc_attr( $thread->customer->name ); ?></div>
2492 </div>
2493 <?php
2494
2495 if ( $current_user->is_agent && in_array( $current_user->agent->role, $settings['allow-ar-thread-email'] ) ) {
2496 ?>
2497 <div class="info-list-item">
2498 <div class="info-label"><?php esc_attr_e( 'Email Address', 'supportcandy' ); ?>:</div>
2499 <div class="info-val"><?php echo esc_attr( $thread->customer->email ); ?></div>
2500 </div>
2501 <?php
2502 }
2503 ?>
2504
2505 <div class="info-list-item">
2506 <div class="info-label"><?php esc_attr_e( 'Source', 'supportcandy' ); ?>:</div>
2507 <div class="info-val"><?php echo esc_attr( $source ); ?></div>
2508 </div>
2509
2510 <div class="info-list-item">
2511 <div class="info-label"><?php esc_attr_e( 'IP Address', 'supportcandy' ); ?>:</div>
2512 <div class="info-val"><?php echo esc_attr( $ip_address ); ?></div>
2513 </div>
2514
2515 <div class="info-list-item">
2516 <div class="info-label"><?php esc_attr_e( 'Browser', 'supportcandy' ); ?>:</div>
2517 <div class="info-val"><?php echo esc_attr( $browser ); ?></div>
2518 </div>
2519
2520 <div class="info-list-item">
2521 <div class="info-label"><?php esc_attr_e( 'Operating System', 'supportcandy' ); ?>:</div>
2522 <div class="info-val"><?php echo esc_attr( $os ); ?></div>
2523 </div>
2524
2525 <?php
2526 if ( $thread->type == 'reply' && $thread->customer != $thread->ticket->customer ) {
2527 ?>
2528 <div class="info-list-item">
2529 <div class="info-label"><?php esc_attr_e( 'Seen', 'supportcandy' ); ?>:</div>
2530 <div class="info-val">
2531 <?php echo is_object( $thread->seen ) ? esc_attr( $thread->seen->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) ) : esc_attr_e( 'No', 'supportcandy' ); ?>
2532 </div>
2533 </div>
2534 <?php
2535 }
2536 do_action( 'wpsc_thread_info_body', $thread );
2537 ?>
2538 </div>
2539 <?php
2540 $body = ob_get_clean();
2541
2542 ob_start();
2543 ?>
2544 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2545 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
2546 </button>
2547 <?php
2548 $footer = ob_get_clean();
2549
2550 $response = array(
2551 'title' => $title,
2552 'body' => $body,
2553 'footer' => $footer,
2554 );
2555 wp_send_json( $response );
2556 }
2557
2558 /**
2559 * Create new ticket from thread
2560 *
2561 * @return void
2562 */
2563 public static function it_set_thread_new_ticket() {
2564
2565 if ( check_ajax_referer( 'wpsc_it_set_thread_new_ticket', '_ajax_nonce', false ) != 1 ) {
2566 wp_send_json_error( 'Unauthorized request!', 401 );
2567 }
2568
2569 if ( self::$is_restricted ) {
2570 wp_send_json_error( 'Unauthorized request!', 401 );
2571 }
2572
2573 global $wpdb;
2574 self::load_current_ticket();
2575 $current_user = WPSC_Current_User::$current_user;
2576
2577 if ( self::$view_profile != 'agent' ) {
2578 wp_send_json_error( 'Something went wrong!', 401 );
2579 }
2580
2581 $ticket = self::$ticket;
2582 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2583
2584 $thread = new WPSC_Thread( $thread_id );
2585 if ( ! $thread->id ) {
2586 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2587 }
2588
2589 if ( self::$ticket != $thread->ticket ) {
2590 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2591 }
2592
2593 $ignore_cft = apply_filters( 'wpsc_thread_new_ticket_ignore_cft', array( 'cf_html', 'df_description', 'df_id', 'df_ip_address', 'df_browser', 'df_assigned_agent', 'df_add_recipients', 'df_ip_address', 'df_os', 'df_prev_assignee', 'df_date_closed', 'df_sf_rating', 'df_sf_feedback', 'df_sf_date', 'df_time_spent', 'df_sla', 'df_last_reply_on', 'df_last_reply_by', 'df_usergroups', 'df_last_reply_source' ) );
2594 $data = array();
2595 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
2596
2597 if ( in_array( $cf->type::$slug, $ignore_cft ) || ! in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
2598 continue;
2599 }
2600 $data[ $cf->slug ] = $cf->type::get_duplicate_ticket_data( $cf, $ticket );
2601 }
2602
2603 $new_ticket = WPSC_Ticket::insert( $data );
2604
2605 // Duplicate thread attachments.
2606 $attachments = array();
2607 foreach ( $thread->attachments as $attachment ) {
2608 $attachments[] = self::get_duplicate_attachment_id( $attachment->id );
2609 }
2610 $duplicate_thread = WPSC_Thread::insert(
2611 array(
2612 'ticket' => $new_ticket->id,
2613 'customer' => $thread->customer->id,
2614 'type' => $thread->type,
2615 'body' => $thread->body,
2616 'attachments' => $attachments ? implode( '|', $attachments ) : '',
2617 'ip_address' => $thread->ip_address,
2618 'source' => $thread->source,
2619 'os' => $thread->os,
2620 'browser' => $thread->browser,
2621 'date_created' => $thread->date_created->format( 'Y-m-d H:i:s' ),
2622 'date_updated' => $thread->date_updated->format( 'Y-m-d H:i:s' ),
2623 )
2624 );
2625
2626 foreach ( $attachments as $id ) :
2627 $attachment = new WPSC_Attachment( $id );
2628 $attachment->source = $duplicate_thread->type;
2629 $attachment->source_id = $duplicate_thread->id;
2630 $attachment->ticket_id = $new_ticket->id;
2631 $attachment->save();
2632 endforeach;
2633
2634 // duplicate description / body attachments.
2635 $body_attachments = array();
2636 $attachment_of_body = WPSC_Attachment::find(
2637 array(
2638 'items_per_page' => 0,
2639 'meta_query' => array(
2640 'relation' => 'AND',
2641 array(
2642 'slug' => 'source',
2643 'compare' => '=',
2644 'val' => 'img_editor',
2645 ),
2646 array(
2647 'slug' => 'source_id',
2648 'compare' => '=',
2649 'val' => $thread->id,
2650 ),
2651 ),
2652 )
2653 )['results'];
2654 foreach ( $attachment_of_body as $img ) {
2655 $body_attachments[ $img->id ] = self::get_duplicate_attachment_id( $img->id );
2656 }
2657 foreach ( $body_attachments as $old_id => $id ) :
2658 $attachment = new WPSC_Attachment( $id );
2659 $attachment->source = 'img_editor';
2660 $attachment->source_id = $duplicate_thread->id;
2661 $attachment->ticket_id = $new_ticket->id;
2662 $attachment->save();
2663
2664 $old_url = home_url( '/' ) . '?wpsc_attachment=' . $old_id;
2665 $new_url = home_url( '/' ) . '?wpsc_attachment=' . $id;
2666 $duplicate_thread->body = str_replace( $old_url, $new_url, $duplicate_thread->body );
2667 endforeach;
2668
2669 $duplicate_thread->save();
2670
2671 do_action( 'wpsc_create_new_ticket', $new_ticket );
2672
2673 wp_send_json( array( 'ticket_id' => $new_ticket->id ) );
2674 }
2675
2676 /**
2677 * Copy old file into new file
2678 *
2679 * @param integer $attachment_id - attachment id.
2680 * @return new attachment id
2681 */
2682 public static function get_duplicate_attachment_id( $attachment_id ) {
2683
2684 $attachment = new WPSC_Attachment( $attachment_id );
2685
2686 // Database insert array init.
2687 $data = array( 'name' => $attachment->name );
2688
2689 $filename = explode( '.', $attachment->name );
2690 $extension = strtolower( $filename[ count( $filename ) - 1 ] );
2691 unset( $filename[ count( $filename ) - 1 ] );
2692 $filename = implode( '.', $filename );
2693 $filename = str_replace( ' ', '_', $filename );
2694 $filename = str_replace( ',', '_', $filename );
2695 $filename = time() . '_' . preg_replace( '/[^A-Za-z0-9\-]/', '', $filename );
2696
2697 // Check for image type.
2698 $img_extensions = array( 'png', 'jpeg', 'jpg', 'bmp', 'pdf', 'gif' );
2699 if ( ! in_array( $extension, $img_extensions ) ) {
2700 $data['is_image'] = 0;
2701 } else {
2702 $data['is_image'] = 1;
2703 }
2704
2705 // Create file path.
2706 $today = new DateTime();
2707 $upload_dir = wp_upload_dir();
2708 $filepath = $upload_dir['basedir'] . '/wpsc/' . $today->format( 'Y' );
2709 if ( ! file_exists( $filepath ) ) {
2710 mkdir( $filepath, 0755, true );
2711 }
2712 $filepath .= '/' . $today->format( 'm' );
2713 if ( ! file_exists( $filepath ) ) {
2714 mkdir( $filepath, 0755, true );
2715 }
2716 $filepath .= '/' . $filename . '.' . $extension;
2717
2718 $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename . '.' . $extension;
2719 $data['file_path'] = $filepath_short;
2720
2721 // Create time.
2722 $data['date_created'] = $today->format( 'Y-m-d H:i:s' );
2723
2724 // copy to path.
2725 copy( $upload_dir['basedir'] . $attachment->file_path, $filepath );
2726 $data['is_active'] = 1;
2727 $new_attachement = WPSC_Attachment::insert( $data );
2728
2729 return $new_attachement->id;
2730 }
2731
2732 /**
2733 * Get edit thread
2734 *
2735 * @return void
2736 */
2737 public static function get_edit_thread() {
2738
2739 if ( check_ajax_referer( 'wpsc_it_get_edit_thread', '_ajax_nonce', false ) != 1 ) {
2740 wp_send_json_error( 'Unauthorized request!', 401 );
2741 }
2742
2743 if ( self::$is_restricted ) {
2744 wp_send_json_error( 'Unauthorized request!', 401 );
2745 }
2746
2747 self::load_current_ticket();
2748
2749 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
2750 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2751 }
2752
2753 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2754 if ( ! $thread_id ) {
2755 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
2756 }
2757
2758 $thread = new WPSC_Thread( $thread_id );
2759 if ( ! $thread->id ) {
2760 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
2761 }
2762
2763 if ( self::$ticket != $thread->ticket ) {
2764 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2765 }
2766
2767 $current_user = WPSC_Current_User::$current_user;
2768
2769 $unique_id = uniqid( 'wpsc_' )
2770 ?>
2771
2772 <div class="wpsc-edit-thread">
2773 <form onsubmit="return false;" class="edit-thread">
2774 <textarea class="wpsc_textarea" id="<?php echo esc_attr( $unique_id ); ?>"><?php echo wp_kses_post( $thread->body ); ?></textarea>
2775 <input class="<?php echo esc_attr( $unique_id ); ?>" type="file" onchange="wpsc_set_attach_multiple(this, '<?php echo esc_attr( $unique_id ); ?>', 'thread_attachments')" multiple style="display: none;">
2776 <div class="wpsc-it-editor-action-container">
2777 <div class="wpsc-edit-actions">
2778 <div class="wpsc-editor-actions">
2779 <?php
2780 if ( WPSC_Text_Editor::is_allow_attachments() ) :
2781 ?>
2782 <span class="wpsc-link wpsc-it-attach" onclick="wpsc_trigger_desc_attachments('<?php echo esc_attr( $unique_id ); ?>');"><?php esc_attr_e( 'Attach Files', 'supportcandy' ); ?></span>
2783 <?php
2784 endif;
2785 ?>
2786 </div>
2787 <?php
2788 if ( WPSC_Text_Editor::is_attachment_notice() && WPSC_Text_Editor::is_allow_attachments() ) :
2789 ?>
2790 <div class="wpsc-file-attachment-notice"><?php echo esc_attr( WPSC_Text_Editor::file_attachment_notice_text() ); ?></div>
2791 <?php
2792 endif;
2793 ?>
2794 <div class="<?php echo esc_attr( $unique_id ); ?> wpsc-editor-attachment-container">
2795 <?php
2796 foreach ( $thread->attachments as $attachment ) :
2797 ?>
2798 <div class="wpsc-editor-attachment upload-success">
2799 <div class="attachment-label"><?php echo esc_attr( $attachment->name ); ?></div>
2800 <div class="attachment-remove" onclick="wpsc_remove_attachment(this)">
2801 <?php WPSC_Icons::get( 'times' ); ?>
2802 </div>
2803 <input type="hidden" name="thread_attachments[]" value="<?php echo esc_attr( $attachment->id ); ?>">
2804 </div>
2805 <?php
2806 endforeach;
2807 ?>
2808 </div>
2809 </div>
2810 <div class="submit-container">
2811 <button class="wpsc-button small primary margin-right" onclick="wpsc_it_set_edit_thread(this, '<?php echo esc_attr( $unique_id ); ?>')"><?php esc_attr_e( 'Submit', 'supportcandy' ); ?></button>
2812 <button class="wpsc-button small secondary" onclick="wpsc_it_get_thread(this, <?php echo esc_attr( self::$ticket->id ); ?>, <?php echo esc_attr( $thread_id ); ?>, '<?php echo esc_attr( wp_create_nonce( 'wpsc_it_get_thread' ) ); ?>')"><?php esc_attr_e( 'Cancel', 'supportcandy' ); ?></button>
2813 </div>
2814 </div>
2815 <input type="hidden" name="action" value="wpsc_it_set_edit_thread"/>
2816 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( self::$ticket->id ); ?>"/>
2817 <input type="hidden" name="thread_id" value="<?php echo esc_attr( $thread->id ); ?>"/>
2818 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_edit_thread' ) ); ?>">
2819 </form>
2820 </div>
2821 <script><?php WPSC_Text_Editor::print_editor_init_scripts( $unique_id, $unique_id . '_body' ); ?></script>
2822 <?php
2823 wp_die();
2824 }
2825
2826 /**
2827 * Set edit thread
2828 *
2829 * @return void
2830 */
2831 public static function set_edit_thread() {
2832
2833 if ( check_ajax_referer( 'wpsc_it_set_edit_thread', '_ajax_nonce', false ) != 1 ) {
2834 wp_send_json_error( 'Unauthorized request!', 401 );
2835 }
2836
2837 self::load_current_ticket();
2838 self::load_actions();
2839
2840 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
2841 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2842 }
2843
2844 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2845 if ( ! $thread_id ) {
2846 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
2847 }
2848
2849 $thread = new WPSC_Thread( $thread_id );
2850 if ( ! $thread->id ) {
2851 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
2852 }
2853
2854 if ( self::$ticket != $thread->ticket ) {
2855 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2856 }
2857
2858 $thread_content = isset( $_POST['thread_content'] ) ? wp_kses_post( wp_unslash( $_POST['thread_content'] ) ) : '';
2859 if ( ! $thread_content ) {
2860 wp_send_json_error( new WP_Error( '007', 'Bad request!' ), 400 );
2861 }
2862
2863 // add ticket auth url to tinymce attachments.
2864 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d+)(?!&auth_code=)([^"]*)/', $thread_content, $matches ) ) {
2865 foreach ( $matches[0] as $url ) {
2866 if ( strpos( $url, 'auth_code=' ) === false ) {
2867 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
2868 $thread_content = str_replace( $url, $new_url, $thread_content );
2869 }
2870 }
2871 }
2872
2873 $thread_attachments = isset( $_POST['thread_attachments'] ) ? array_filter( array_map( 'intval', $_POST['thread_attachments'] ) ) : array();
2874 $new_attachments = array();
2875 foreach ( $thread_attachments as $id ) {
2876 $attachment = new WPSC_Attachment( $id );
2877 $attachment->is_active = 1;
2878 $attachment->ticket_id = self::$ticket->id;
2879 $attachment->source = $thread->type;
2880 $attachment->source_id = $thread->id;
2881 $attachment->save();
2882 $new_attachments[] = $attachment;
2883 }
2884
2885 $is_modify = false;
2886
2887 // Set new thread.
2888 $prev_content = $thread->body;
2889 if ( $prev_content != $thread_content ) {
2890
2891 $thread->body = $thread_content;
2892
2893 // tinymce img attachments.
2894 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(&auth_code=[^&\s]*)?/', $thread_content, $matches ) ) {
2895
2896 foreach ( $matches[1] as $id ) {
2897
2898 $attachment = new WPSC_Attachment( $id );
2899 if ( $attachment->is_active ) {
2900 continue;
2901 }
2902
2903 $attachment->is_active = 1;
2904 $attachment->source_id = $thread->id;
2905 $attachment->ticket_id = self::$ticket->id;
2906 $attachment->source = 'img_editor';
2907 $attachment->save();
2908 }
2909 }
2910
2911 $is_modify = true;
2912 }
2913
2914 // Attachments.
2915 $prev_attachments = array_map( fn( $attachment ) => $attachment->id, $thread->attachments );
2916 if ( array_diff( $prev_attachments, $thread_attachments ) || array_diff( $thread_attachments, $prev_attachments ) ) {
2917 $thread->attachments = $new_attachments;
2918 $is_modify = true;
2919 }
2920
2921 // Return if there is no change.
2922 if ( ! $is_modify ) {
2923 self::print_thread( $thread );
2924 wp_die();
2925 }
2926
2927 // Set log for this change.
2928 WPSC_Log::insert(
2929 array(
2930 'type' => 'thread',
2931 'ref_id' => $thread->id,
2932 'modified_by' => WPSC_Current_User::$current_user->customer->id,
2933 'body' => wp_json_encode(
2934 array(
2935 'type' => 'modify',
2936 'prev' => array(
2937 'content' => str_replace( PHP_EOL, '', $prev_content ),
2938 'attachments' => $prev_attachments,
2939 ),
2940 'new' => array(
2941 'content' => str_replace( PHP_EOL, '', $thread_content ),
2942 'attachments' => $thread_attachments,
2943 ),
2944 )
2945 ),
2946 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
2947 )
2948 );
2949
2950 // Save changes to thread and print updated thread HTML.
2951 $thread->save();
2952 self::print_thread( $thread );
2953 wp_die();
2954 }
2955
2956 /**
2957 * Print HTML for a thread
2958 *
2959 * @return void
2960 */
2961 public static function get_thread_html() {
2962
2963 if ( check_ajax_referer( 'wpsc_it_get_thread', '_ajax_nonce', false ) != 1 ) {
2964 wp_send_json_error( 'Unauthorized request!', 401 );
2965 }
2966
2967 self::load_current_ticket();
2968 self::load_actions();
2969
2970 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
2971 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2972 }
2973
2974 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2975 if ( ! $thread_id ) {
2976 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
2977 }
2978
2979 $thread = new WPSC_Thread( $thread_id );
2980 if ( ! $thread->id ) {
2981 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
2982 }
2983
2984 self::print_thread( $thread );
2985 wp_die();
2986 }
2987
2988 /**
2989 * Delete thread
2990 */
2991 public static function delete_thread() {
2992
2993 if ( check_ajax_referer( 'wpsc_it_thread_delete', '_ajax_nonce', false ) != 1 ) {
2994 wp_send_json_error( 'Unauthorized request!', 401 );
2995 }
2996
2997 if ( self::$is_restricted ) {
2998 wp_send_json_error( 'Unauthorized request!', 401 );
2999 }
3000
3001 self::load_current_ticket();
3002 self::load_actions();
3003
3004 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) ) ) {
3005 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3006 }
3007
3008 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3009 if ( ! $thread_id ) {
3010 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3011 }
3012
3013 $thread = new WPSC_Thread( $thread_id );
3014 if ( ! $thread->id ) {
3015 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3016 }
3017
3018 if ( self::$ticket != $thread->ticket ) {
3019 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
3020 }
3021
3022 $thread->is_active = 0;
3023 $thread->save();
3024
3025 // Set log for this change.
3026 WPSC_Log::insert(
3027 array(
3028 'type' => 'thread',
3029 'ref_id' => $thread->id,
3030 'modified_by' => WPSC_Current_User::$current_user->customer->id,
3031 'body' => wp_json_encode(
3032 array(
3033 'type' => 'delete',
3034 )
3035 ),
3036 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
3037 )
3038 );
3039
3040 self::print_thread( $thread );
3041 wp_die();
3042 }
3043
3044 /**
3045 * View thread log
3046 */
3047 public static function view_thread_log() {
3048
3049 if ( check_ajax_referer( 'wpsc_it_view_thread_log', '_ajax_nonce', false ) != 1 ) {
3050 wp_send_json_error( 'Unauthorized request!', 401 );
3051 }
3052
3053 self::load_current_ticket();
3054
3055 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
3056 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3057 }
3058
3059 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3060 if ( ! $thread_id ) {
3061 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3062 }
3063
3064 $thread = new WPSC_Thread( $thread_id );
3065 if ( ! $thread->id ) {
3066 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3067 }
3068
3069 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3070 wp_send_json_error( new WP_Error( '006', 'Unauthorized!' ), 401 );
3071 }
3072
3073 $log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0;
3074 if ( ! $log_id ) {
3075 wp_send_json_error( new WP_Error( '007', 'Bad request!' ), 400 );
3076 }
3077
3078 $log = new WPSC_Log( $log_id );
3079 if ( ! $log->id ) {
3080 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3081 }
3082
3083 $body = json_decode( $log->body );
3084 $title = esc_attr__( 'Thread log', 'supportcandy' );
3085
3086 ob_start();
3087 ?>
3088 <div class="wpsc-modal-thread-log">
3089
3090 <div class="wpsc-log-diff">
3091
3092 <div class="lhs">
3093 <div>
3094 <div class="thread-text" style="margin-bottom:0;">
3095 <?php echo wp_kses_post( $body->prev->content ); ?>
3096 </div>
3097 <?php
3098 if ( $body->prev->attachments ) :
3099 ?>
3100 <div class="wpsc-thread-attachments">
3101 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3102 <?php
3103 foreach ( $body->prev->attachments as $id ) :
3104 $attachment = new WPSC_Attachment( $id );
3105 if ( ! $attachment->id ) {
3106 continue;
3107 }
3108 ?>
3109 <div class="wpsc-attachment-item">
3110 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span>
3111 </div>
3112 <?php
3113 endforeach;
3114 ?>
3115 </div>
3116 <?php
3117 endif;
3118 ?>
3119 </div>
3120 </div>
3121
3122 <div class="transform-icon">
3123 <?php
3124 if ( is_rtl() ) {
3125 WPSC_Icons::get( 'arrow-left' );
3126 } else {
3127 WPSC_Icons::get( 'arrow-right' );
3128 }
3129 ?>
3130 </div>
3131
3132 <div class="rhs">
3133 <div>
3134 <div class="thread-text" style="margin-bottom:0;">
3135 <?php echo wp_kses_post( $body->new->content ); ?>
3136 </div>
3137 <?php
3138 if ( $body->new->attachments ) :
3139 ?>
3140 <div class="wpsc-thread-attachments">
3141 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3142 <?php
3143 foreach ( $body->new->attachments as $id ) :
3144 $attachment = new WPSC_Attachment( $id );
3145 if ( ! $attachment->id ) {
3146 continue;
3147 }
3148 ?>
3149 <div class="wpsc-attachment-item">
3150 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span>
3151 </div>
3152 <?php
3153 endforeach;
3154 ?>
3155 </div>
3156 <?php
3157 endif;
3158 ?>
3159 </div>
3160 </div>
3161
3162 </div>
3163
3164 </div>
3165 <?php
3166 $body = ob_get_clean();
3167
3168 ob_start();
3169 ?>
3170 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
3171 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
3172 </button>
3173 <?php
3174 $footer = ob_get_clean();
3175
3176 $response = array(
3177 'title' => $title,
3178 'body' => $body,
3179 'footer' => $footer,
3180 );
3181 wp_send_json( $response );
3182 }
3183
3184 /**
3185 * View deleted thread
3186 *
3187 * @return void
3188 */
3189 public static function view_deleted_thread() {
3190
3191 if ( check_ajax_referer( 'wpsc_it_view_deleted_thread', '_ajax_nonce', false ) != 1 ) {
3192 wp_send_json_error( 'Unauthorized request!', 401 );
3193 }
3194
3195 self::load_current_ticket();
3196
3197 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
3198 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3199 }
3200
3201 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3202 if ( ! $thread_id ) {
3203 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3204 }
3205
3206 $thread = new WPSC_Thread( $thread_id );
3207 if ( ! $thread->id ) {
3208 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3209 }
3210
3211 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3212 wp_send_json_error( new WP_Error( '006', 'Unauthorized!' ), 401 );
3213 }
3214
3215 if ( self::$ticket != $thread->ticket ) {
3216 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
3217 }
3218
3219 $title = $thread->customer->name;
3220
3221 ob_start();
3222 ?>
3223 <div class="wpsc-modal-thread-log">
3224 <div class="thread-text"><?php echo wp_kses_post( $thread->body ); ?></div>
3225 <?php
3226 if ( $thread->attachments ) :
3227 ?>
3228 <div class="wpsc-thread-attachments">
3229 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3230 <?php
3231 foreach ( $thread->attachments as $attachment ) :
3232 ?>
3233 <div class="wpsc-attachment-item">
3234 <?php
3235 $download_url = home_url( '/' ) . '?wpsc_attachment=' . $attachment->id;
3236 ?>
3237 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
3238 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
3239 </div>
3240 <?php
3241 endforeach;
3242 ?>
3243 </div>
3244 <?php
3245 endif;
3246 ?>
3247 </div>
3248 <?php
3249 $body = ob_get_clean();
3250
3251 ob_start();
3252 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) ) :
3253 ?>
3254 <button class="wpsc-button small primary" onclick="wpsc_it_restore_thread(<?php echo esc_attr( self::$ticket->id ) . ', ' . esc_attr( $thread->id ) . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_restore_thread' ) ) . '\''; ?>);">
3255 <?php esc_attr_e( 'Restore', 'supportcandy' ); ?>
3256 </button>
3257 <?php
3258 endif;
3259 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) && WPSC_Functions::is_site_admin() ) :
3260 ?>
3261 <button class="wpsc-button small primary" onclick="wpsc_it_thread_delete_permanently(<?php echo esc_attr( self::$ticket->id ) . ', ' . esc_attr( $thread->id ) . ', \'' . esc_attr( wp_create_nonce( 'wpsc_it_thread_delete_permanently' ) ) . '\''; ?>);">
3262 <?php esc_attr_e( 'Delete Permanently', 'supportcandy' ); ?>
3263 </button>
3264 <?php
3265 endif;
3266 ?>
3267 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
3268 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
3269 </button>
3270 <?php
3271 $footer = ob_get_clean();
3272
3273 $response = array(
3274 'title' => $title,
3275 'body' => $body,
3276 'footer' => $footer,
3277 );
3278 wp_send_json( $response );
3279 }
3280
3281 /**
3282 * Restore thread
3283 *
3284 * @return void
3285 */
3286 public static function restore_thread() {
3287
3288 if ( check_ajax_referer( 'wpsc_it_restore_thread', '_ajax_nonce', false ) != 1 ) {
3289 wp_send_json_error( 'Unauthorized request!', 401 );
3290 }
3291
3292 self::load_current_ticket();
3293 self::load_actions();
3294
3295 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && self::has_ticket_cap( 'dth' ) ) ) {
3296 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3297 }
3298
3299 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3300 if ( ! $thread_id ) {
3301 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3302 }
3303
3304 $thread = new WPSC_Thread( $thread_id );
3305 if ( ! $thread->id ) {
3306 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3307 }
3308
3309 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3310 wp_send_json_error( new WP_Error( '007', 'Unauthorized!' ), 401 );
3311 }
3312
3313 if ( self::$ticket != $thread->ticket ) {
3314 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3315 }
3316
3317 $thread->is_active = 1;
3318 $thread->save();
3319
3320 // Set log for this change.
3321 WPSC_Log::insert(
3322 array(
3323 'type' => 'thread',
3324 'ref_id' => $thread->id,
3325 'modified_by' => WPSC_Current_User::$current_user->customer->id,
3326 'body' => wp_json_encode(
3327 array(
3328 'type' => 'restore',
3329 )
3330 ),
3331 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
3332 )
3333 );
3334
3335 self::print_thread( $thread );
3336 wp_die();
3337 }
3338
3339 /**
3340 * Delete thread permanently
3341 */
3342 public static function thread_delete_permanently() {
3343
3344 if ( check_ajax_referer( 'wpsc_it_thread_delete_permanently', '_ajax_nonce', false ) != 1 ) {
3345 wp_send_json_error( 'Unauthorized request!', 401 );
3346 }
3347
3348 self::load_current_ticket();
3349
3350 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && self::has_ticket_cap( 'dth' ) && WPSC_Functions::is_site_admin() ) ) {
3351 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3352 }
3353
3354 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3355 if ( ! $thread_id ) {
3356 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3357 }
3358
3359 $thread = new WPSC_Thread( $thread_id );
3360 if ( ! $thread->id ) {
3361 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3362 }
3363
3364 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3365 wp_send_json_error( new WP_Error( '007', 'Unauthorized!' ), 401 );
3366 }
3367
3368 if ( self::$ticket != $thread->ticket ) {
3369 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3370 }
3371
3372 WPSC_Thread::destroy( $thread );
3373 wp_die();
3374 }
3375
3376 /**
3377 * Load older threads
3378 *
3379 * @return void
3380 */
3381 public static function load_older_threads() {
3382
3383 self::load_current_ticket();
3384 self::load_actions();
3385
3386 $last_thread = isset( $_POST['last_thread'] ) ? intval( $_POST['last_thread'] ) : 0; // phpcs:ignore
3387 if ( ! $last_thread ) {
3388 wp_send_json_error( new WP_Error( '004', 'Bad request!' ), 401 );
3389 }
3390
3391 $gs = get_option( 'wpsc-gs-general' );
3392 $filters = array(
3393 'meta_query' => array(
3394 'relation' => 'AND',
3395 array(
3396 'slug' => 'ticket',
3397 'compare' => '=',
3398 'val' => self::$ticket->id,
3399 ),
3400 array(
3401 'slug' => 'id',
3402 'compare' => '<',
3403 'val' => $last_thread,
3404 ),
3405 ),
3406 'orderby' => 'id',
3407 'order' => 'DESC',
3408 );
3409
3410 $thread_types = array(
3411 'slug' => 'type',
3412 'compare' => 'IN',
3413 'val' => array( 'report', 'reply' ),
3414 );
3415
3416 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
3417 $thread_types['val'][] = 'note';
3418 }
3419 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
3420 $thread_types['val'][] = 'log';
3421 }
3422
3423 $filters['meta_query'][] = $thread_types;
3424
3425 $response = WPSC_Thread::find( $filters );
3426 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
3427
3428 ob_start();
3429 if ( $gs['reply-form-position'] == 'top' ) {
3430 foreach ( $response['results'] as $thread ) {
3431 if ( $thread->type == 'log' ) {
3432 self::print_log( $thread );
3433 } else {
3434 self::print_thread( $thread );
3435 }
3436 }
3437 } else {
3438 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
3439 $thread = $response['results'][ $i ];
3440 if ( $thread->type == 'log' ) {
3441 self::print_log( $thread );
3442 } else {
3443 self::print_thread( $thread );
3444 }
3445 }
3446 }
3447 $threads = ob_get_clean();
3448
3449 $html_response = array(
3450 'last_thread' => $last_id,
3451 'has_next_page' => $response['has_next_page'],
3452 'threads' => $threads,
3453 );
3454
3455 wp_send_json( $html_response );
3456 wp_die();
3457 }
3458
3459 /**
3460 * Show the list of live agents.
3461 *
3462 * @return void
3463 */
3464 public static function get_live_agents() {
3465
3466 $current_user = WPSC_Current_User::$current_user;
3467 $ms_advanced = get_option( 'wpsc-ms-advanced-settings' );
3468 if ( ! ( $current_user->is_agent && $ms_advanced['agent-collision'] ) ) {
3469 return;
3470 }
3471 ?>
3472 <div class="wpsc-it-body-item wpsc-agent-collision wpsc-it-widget">
3473 <div class="wpsc-widget-header">
3474 <h2><?php esc_attr_e( 'Currently viewing', 'supportcandy' ); ?></h2>
3475 </div>
3476 <div class="wpsc-widget-body wpsc-live-agents">
3477 </div>
3478 </div>
3479
3480 <script>
3481 supportcandy.agent_collision = true;
3482 wpsc_get_live_agents(<?php echo intval( $current_user->agent->id ); ?>, <?php echo intval( self::$ticket->id ); ?>);
3483 function wpsc_get_live_agents( agent_id, ticket_id ){
3484
3485 jQuery('.wpsc-live-agents').html('');
3486 jQuery('.wpsc-agent-collision').hide();
3487 var current_tid = jQuery('#wpsc-current-ticket').val();
3488 if( current_tid != ticket_id ){
3489 return;
3490 }
3491 const urlParams = new URLSearchParams(window.location.search);
3492 if( supportcandy.is_frontend === '0' ) {
3493 section = urlParams.get('section');
3494 }else{
3495 section = urlParams.get('wpsc-section');
3496 }
3497 if( ! ( section == 'ticket-list' && ( urlParams.has('id') || urlParams.has('ticket-id')) ) ){
3498 return;
3499 }
3500 var data = { action: 'wpsc_check_live_agents', agent_id, ticket_id, operation: 'check', _ajax_nonce: supportcandy.nonce };
3501 jQuery.post(
3502 supportcandy.ajax_url,
3503 data,
3504 function (response) {
3505 if ( response.agents ) {
3506 jQuery('.wpsc-live-agents').html(response.agents);
3507 jQuery('.wpsc-agent-collision').show();
3508 } else {
3509 jQuery('.wpsc-live-agents').html('');
3510 jQuery('.wpsc-agent-collision').hide();
3511 }
3512 }
3513 );
3514 setTimeout(
3515 function () {
3516 wpsc_get_live_agents( agent_id, ticket_id );
3517 },
3518 60000
3519 );
3520 }
3521
3522 jQuery(document).ready(function(){
3523 window.addEventListener('beforeunload', function () {
3524 const urlParams = new URLSearchParams(window.location.search);
3525
3526 // Determine section
3527 let section = supportcandy.is_frontend === '0'
3528 ? urlParams.get('section')
3529 : urlParams.get('wpsc-section');
3530
3531 // Only trigger if we are on the ticket detail page
3532 if (!(section === 'ticket-list' && (urlParams.has('id') || urlParams.has('ticket-id')))) {
3533 return;
3534 }
3535
3536 // Prepare data
3537 const data = new FormData();
3538 data.append('action', 'wpsc_check_live_agents');
3539 data.append('agent_id', '<?php echo intval( $current_user->agent->id ); ?>');
3540 data.append('ticket_id', '<?php echo intval( self::$ticket->id ); ?>');
3541 data.append('operation', 'leave');
3542 data.append('_ajax_nonce', supportcandy.nonce);
3543
3544 // Send reliably on tab close
3545 navigator.sendBeacon(supportcandy.ajax_url, data);
3546 });
3547 });
3548 </script>
3549 <?php
3550 }
3551
3552 /**
3553 * Get list of live agents.
3554 *
3555 * @return void
3556 */
3557 public static function check_live_agents() {
3558
3559 if ( check_ajax_referer( 'general', '_ajax_nonce', false ) != 1 ) {
3560 wp_send_json_error( 'Unauthorized request!', 401 );
3561 }
3562
3563 $id = isset( $_POST['ticket_id'] ) ? intval( $_POST['ticket_id'] ) : 0; // phpcs:ignore
3564 if ( ! $id ) {
3565 wp_send_json_error( new WP_Error( '001', 'Unauthorized!' ), 401 );
3566 }
3567
3568 $ticket = new WPSC_Ticket( $id );
3569 if ( ! $ticket->id ) {
3570 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
3571 }
3572
3573 $agent_id = isset( $_POST['agent_id'] ) ? intval( $_POST['agent_id'] ) : 0; // phpcs:ignore
3574 if ( ! $agent_id ) {
3575 wp_send_json_error( new WP_Error( '001', 'Unauthorized!' ), 401 );
3576 }
3577
3578 $agent = new WPSC_Agent( $agent_id );
3579 if ( ! $agent->id ) {
3580 wp_send_json_error( new WP_Error( '002', 'Something went wrong!' ), 400 );
3581 }
3582
3583 $operation = isset( $_POST['operation'] ) ? esc_attr( $_POST['operation'] ) : 'check'; // phpcs:ignore
3584
3585 $agents = json_decode( $ticket->live_agents, true );
3586 $agents = $agents ? $agents : array();
3587
3588 if ( $operation == 'leave' ) {
3589
3590 if ( array_key_exists( $agent->id, $agents ) ) {
3591 unset( $agents[ $agent->id ] );
3592 $agents = wp_json_encode( $agents );
3593 $ticket->live_agents = $agents;
3594 $ticket->save();
3595 }
3596 wp_die();
3597 }
3598 // check inactive agents.
3599 foreach ( $agents as $key => $tm ) {
3600 if ( $key == $agent->id ) {
3601 continue;
3602 }
3603
3604 $time = DateTime::createFromFormat( 'Y-m-d H:i:s', $tm );
3605 $now = new DateTime();
3606 $interval = $now->diff( $time );
3607 if ( $interval->i >= 1 && $interval->s > 1 ) {
3608 unset( $agents[ $key ] );
3609 }
3610 }
3611
3612 $agents[ $agent->id ] = ( new DateTime() )->format( 'Y-m-d H:i:s' );
3613
3614 $html = '';
3615 foreach ( $agents as $ag_id => $tmp ) {
3616 if ( $ag_id == $agent->id ) {
3617 continue;
3618 }
3619 $agt = new WPSC_Agent( $ag_id );
3620
3621 $html .= '<div class="wpsc-ac-agent"> ' .
3622 get_avatar( $agt->customer->email, 20 ) .
3623 '<span class="ac-name">' . $agt->name . '</span></div>';
3624 }
3625
3626 $agents = wp_json_encode( $agents );
3627 $ticket->live_agents = $agents;
3628 $ticket->save();
3629 $response = array(
3630 'agents' => $html,
3631 );
3632 wp_send_json( $response );
3633 }
3634 }
3635 endif;
3636
3637 WPSC_Individual_Ticket::init();
3638