PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.3.7
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.3.7
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / includes / admin / tickets / class-wpsc-individual-ticket.php

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

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