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

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

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