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

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

3,656 lines 119.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly!
4 }
5
6 if ( ! class_exists( 'WPSC_Individual_Ticket' ) ) :
7
8 final class WPSC_Individual_Ticket {
9
10 /**
11 * Current ticket object (ticket model object)
12 *
13 * @var WPSC_Ticket
14 */
15 public static $ticket;
16
17 /**
18 * Viewing profile of current user of this ticket
19 *
20 * @var string
21 */
22 public static $view_profile;
23
24 /**
25 * Reply profile of current user of this ticket
26 *
27 * @var string
28 */
29 public static $reply_profile;
30
31 /**
32 * Actions for the individual ticket
33 *
34 * @var array
35 */
36 private static $actions;
37
38 /**
39 * Submit actions
40 *
41 * @var array
42 */
43 private static $submit_actions;
44
45 /**
46 * Thread actions
47 *
48 * @var array
49 */
50 private static $thread_actions = array();
51
52 /**
53 * Widget HTML displayed in two places, sidebar and body to maintain responsive behaviour.
54 * By loading their HTML only once, we have optmized load time here.
55 *
56 * @var string
57 */
58 private static $widget_html;
59
60 /**
61 * Set whether ticket url is authenticated or not.
62 *
63 * @var boolean
64 */
65 public static $url_auth = false;
66
67 /**
68 * Set whether ticket action is restricted or not.
69 *
70 * @var boolean
71 */
72 public static $is_restricted = false;
73
74 /**
75 * Initialize the class
76 *
77 * @return void
78 */
79 public static function init() {
80
81 add_action( 'wp_ajax_wpsc_get_individual_ticket', array( __CLASS__, 'layout' ) );
82 add_action( 'wp_ajax_nopriv_wpsc_get_individual_ticket', array( __CLASS__, 'layout' ) );
83
84 // Submit ticket.
85 add_action( 'wp_ajax_wpsc_it_add_reply', array( __CLASS__, 'add_reply' ) );
86 add_action( 'wp_ajax_nopriv_wpsc_it_add_reply', array( __CLASS__, 'add_reply' ) );
87
88 // Submit note.
89 add_action( 'wp_ajax_wpsc_it_add_note', array( __CLASS__, 'add_note' ) );
90 add_action( 'wp_ajax_nopriv_wpsc_it_add_note', array( __CLASS__, 'add_note' ) );
91
92 // Submit note.
93 add_action( 'wp_ajax_wpsc_it_reply_and_close', array( __CLASS__, 'it_reply_and_close' ) );
94 add_action( 'wp_ajax_nopriv_wpsc_it_reply_and_close', array( __CLASS__, 'it_reply_and_close' ) );
95
96 // Duplicate ticket.
97 add_action( 'wp_ajax_wpsc_it_get_duplicate_ticket', array( __CLASS__, 'get_duplicate_ticket' ) );
98 add_action( 'wp_ajax_nopriv_wpsc_it_get_duplicate_ticket', array( __CLASS__, 'get_duplicate_ticket' ) );
99 add_action( 'wp_ajax_wpsc_it_set_duplicate_ticket', array( __CLASS__, 'set_duplicate_ticket' ) );
100 add_action( 'wp_ajax_nopriv_wpsc_it_set_duplicate_ticket', array( __CLASS__, 'set_duplicate_ticket' ) );
101
102 // Delete ticket.
103 add_action( 'wp_ajax_wpsc_it_delete_ticket', array( __CLASS__, 'set_delete_ticket' ) );
104 add_action( 'wp_ajax_nopriv_wpsc_it_delete_ticket', array( __CLASS__, 'set_delete_ticket' ) );
105 add_action( 'wp_ajax_wpsc_it_archive_ticket', array( __CLASS__, 'set_archive_ticket' ) );
106 add_action( 'wp_ajax_nopriv_wpsc_it_archive_ticket', array( __CLASS__, 'set_archive_ticket' ) );
107 add_action( 'wp_ajax_wpsc_it_ticket_restore', array( __CLASS__, 'ticket_restore' ) );
108 add_action( 'wp_ajax_nopriv_wpsc_it_ticket_restore', array( __CLASS__, 'ticket_restore' ) );
109 add_action( 'wp_ajax_wpsc_it_delete_permanently', array( __CLASS__, 'set_delete_ticket_permanently' ) );
110 add_action( 'wp_ajax_nopriv_wpsc_it_delete_permanently', array( __CLASS__, 'set_delete_ticket_permanently' ) );
111 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 && hash_equals( (string) $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 wpsc_init_thread_expanders(jQuery(document).find('.thread-text'));
1193
1194 supportcandy.threads = {last_thread: <?php echo esc_attr( $last_id ); ?>}
1195 </script>
1196 <?php
1197
1198 if ( $response['has_next_page'] && $gs['reply-form-position'] == 'top' ) :
1199 ?>
1200 <div class="wpsc-it-body-item" style="display: flex; justify-content:center; margin-bottom: 50px;">
1201 <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>
1202 </div>
1203 <?php
1204 endif;
1205 }
1206
1207 /**
1208 * Print thread
1209 *
1210 * @param WPSC_Thread $thread - thread object.
1211 * @return void
1212 */
1213 public static function print_thread( $thread ) {
1214
1215 // If thread is of type "Private Note", return if current user does not have permission to view logs.
1216 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
1217 return;
1218 }
1219
1220 // If thread is deleted, show only if current user has view log permission. It will show deleteted log with view content link.
1221 if ( ! $thread->is_active && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
1222 return;
1223 }
1224
1225 $settings = get_option( 'wpsc-gs-general' );
1226 $current_user = WPSC_Current_User::$current_user;
1227 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1228
1229 $now = new DateTime();
1230 $date = $thread->date_created->setTimezone( wp_timezone() );
1231 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
1232 $time_title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1233 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1234 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
1235
1236 if (
1237 ! is_object( $thread->seen ) &&
1238 $current_user->customer == $thread->ticket->customer &&
1239 in_array( $thread->type, array( 'report', 'reply' ) )
1240 ) {
1241
1242 $today = new DateTime();
1243 $thread->seen = $today->format( 'Y-m-d H:i:s' );
1244 $thread->save();
1245 }
1246
1247 $classes = $thread->type == 'note' ? 'note' : 'reply';
1248 if ( in_array( $thread->type, array( 'report', 'reply' ) ) ) {
1249 $thread_user = get_user_by( 'email', $thread->customer->email );
1250 $user_class = $thread_user && $thread_user->has_cap( 'wpsc_agent' ) ? 'agent' : 'customer';
1251 $classes = $classes . ' ' . $user_class;
1252 }
1253 $classes = apply_filters( 'wpsc_it_thread_add_classes', $classes, $thread );
1254 self::$thread_actions = apply_filters( 'wpsc_it_filter_thread_actions', self::$thread_actions, $thread );
1255 ?>
1256
1257 <div class="wpsc-thread <?php echo esc_attr( $classes ); ?> <?php echo esc_attr( $thread->id ); ?>">
1258
1259 <div class="thread-avatar">
1260 <?php echo get_avatar( $thread->customer->email, 32 ); ?>
1261 </div>
1262
1263 <div class="thread-body">
1264
1265 <div class="thread-header">
1266
1267 <div class="user-info">
1268 <div style="display: flex;">
1269 <h2 class="user-name"><?php echo esc_attr( $thread->customer->name ) . ' '; ?></h2>
1270 <h2>
1271 <small class="thread-type">
1272 <i>
1273 <?php
1274 switch ( $thread->type ) {
1275
1276 case 'report':
1277 esc_attr_e( 'reported', 'supportcandy' );
1278 break;
1279
1280 case 'reply':
1281 esc_attr_e( 'replied', 'supportcandy' );
1282 break;
1283
1284 case 'note':
1285 esc_attr_e( 'added a note', 'supportcandy' );
1286 break;
1287 }
1288 ?>
1289 </i>
1290 </small>
1291 </h2>
1292 </div>
1293 <span class="thread-time" title="<?php echo esc_attr( $time_title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1294 </div>
1295 <?php
1296 do_action( 'wpsc_it_thread_header', $thread );
1297 if ( $thread->is_active ) {
1298 ?>
1299 <div class="actions">
1300 <?php
1301 foreach ( self::$thread_actions as $action ) :
1302 ?>
1303 <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'] ) ) . '\')'; ?>">
1304 <?php WPSC_Icons::get( $action['icon'] ); ?>
1305 </span>
1306 <?php
1307 endforeach;
1308 ?>
1309 </div>
1310 <?php
1311 }
1312 ?>
1313
1314 </div>
1315
1316 <div class="thread-text<?php echo $advanced['view-more'] ? ' wpsc-collapsed' : ''; ?>">
1317 <?php
1318 if ( $thread->is_active ) {
1319 echo wp_kses_post( $thread->body );
1320 } else {
1321 $logs = $thread->get_logs();
1322 ?>
1323 <i>
1324 <?php
1325 printf(
1326 /* translators: %1$s: customer name, %2$s: datetime */
1327 esc_attr__( 'This thread was deleted by %1$s on %2$s.', 'supportcandy' ),
1328 '<strong>' . esc_attr( $logs[0]->modified_by->name ) . '</strong>',
1329 esc_attr( $logs[0]->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1330 );
1331 ?>
1332 <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' ) ); ?>')">
1333 <?php esc_attr_e( 'View thread!', 'supportcandy' ); ?>
1334 </span>
1335 </i>
1336 <?php
1337 }
1338 ?>
1339 </div>
1340
1341 <?php
1342 if ( $advanced['view-more'] ) {
1343 ?>
1344 <div class="wpsc-ticket-thread-expander" onclick="wpsc_ticket_thread_expander_toggle(this);" style="display: none;">
1345 <?php esc_attr_e( 'View More ...', 'supportcandy' ); ?>
1346 </div>
1347 <?php
1348 }
1349
1350 // Thread attachments.
1351 if ( $thread->is_active && $thread->attachments ) {
1352 ?>
1353 <div class="wpsc-thread-attachments">
1354 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
1355 <?php
1356 foreach ( $thread->attachments as $attachment ) {
1357 ?>
1358 <div class="wpsc-attachment-item">
1359 <?php
1360 $download_url = site_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&auth_code=' . $thread->ticket->auth_code;
1361 ?>
1362 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
1363 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
1364 </div>
1365 <?php
1366 }
1367 ?>
1368 </div>
1369 <?php
1370 }
1371
1372 // Thread logs.
1373 $logs = $thread->get_logs();
1374 if ( $thread->is_active && self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && $logs ) {
1375 ?>
1376 <div class="wpsc-thread-logs">
1377 <?php
1378 foreach ( $logs as $log ) {
1379
1380 $log_body = json_decode( $log->body );
1381 ?>
1382 <div class="wpsc-thread-log-item">
1383 <?php
1384 switch ( $log_body->type ) {
1385
1386 case 'modify':
1387 printf(
1388 /* translators: %1$s: customer name, %2$s: date time */
1389 esc_attr__( 'Modified by %1$s on %2$s.', 'supportcandy' ),
1390 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1391 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1392 );
1393 ?>
1394 <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' ) ); ?>');">
1395 <?php esc_attr_e( 'View change', 'supportcandy' ); ?>
1396 </span>
1397 <?php
1398 break;
1399
1400 case 'delete':
1401 printf(
1402 /* translators: %1$s: customer name, %2$s: date time */
1403 esc_attr__( 'Deleted by %1$s on %2$s', 'supportcandy' ),
1404 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1405 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1406 );
1407 break;
1408
1409 case 'restore':
1410 printf(
1411 /* translators: %1$s: customer name, %2$s: date time */
1412 esc_attr__( 'Restored by %1$s on %2$s', 'supportcandy' ),
1413 '<strong>' . esc_attr( $log->modified_by->name ) . '</strong>',
1414 esc_attr( $log->date_created->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) )
1415 );
1416 break;
1417 }
1418 ?>
1419 </div>
1420 <?php
1421 }
1422 ?>
1423 </div>
1424 <?php
1425 }
1426 ?>
1427
1428 </div>
1429
1430 </div>
1431 <?php
1432 }
1433
1434 /**
1435 * Print thread log
1436 *
1437 * @param WPSC_Thread $thread - thread object.
1438 * @return void
1439 */
1440 public static function print_log( $thread ) {
1441
1442 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
1443 return;
1444 }
1445
1446 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
1447 $now = new DateTime();
1448 $date = $thread->date_created->setTimezone( wp_timezone() );
1449 $time_diff_str = WPSC_Functions::date_interval_highest_unit_ago( $date->diff( $now ) );
1450 $title = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1451 $time_date_str = wp_date( $advanced['thread-date-format'], $thread->date_created->setTimezone( wp_timezone() )->getTimestamp() );
1452 $time_str = $advanced['thread-date-display-as'] == 'date' ? $time_date_str : $time_diff_str;
1453
1454 $body = json_decode( $thread->body );
1455 $is_json = ( json_last_error() == JSON_ERROR_NONE ) ? true : false;
1456
1457 if ( $is_json ) {
1458
1459 $cf = WPSC_Custom_Field::get_cf_by_slug( $body->slug );
1460 if ( ! $cf ) {
1461 return;
1462 }
1463 ?>
1464 <div class="wpsc-thread log">
1465 <div class="thread-avatar">
1466 <?php
1467 if ( $thread->customer ) {
1468 echo get_avatar( $thread->customer->email, 32 );
1469 } else {
1470 WPSC_Icons::get( 'system' );
1471 }
1472 ?>
1473 </div>
1474 <div class="thread-body">
1475 <div class="thread-header">
1476 <div class="user-info">
1477 <div>
1478 <?php
1479 if ( $thread->customer ) {
1480
1481 printf(
1482 /* translators: %1$s: User Name, %2$s: Field Name */
1483 esc_attr__( '%1$s changed the %2$s', 'supportcandy' ),
1484 '<strong>' . esc_attr( $thread->customer->name ) . '</strong>',
1485 '<strong>' . esc_attr( $cf->name ) . '</strong>'
1486 );
1487
1488 } else {
1489
1490 printf(
1491 /* translators: %1$s: Field Name */
1492 esc_attr__( 'The %1$s has been changed', 'supportcandy' ),
1493 '<strong>' . esc_attr( $cf->name ) . '</strong>'
1494 );
1495 }
1496 ?>
1497 </div>
1498 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1499 </div>
1500 </div>
1501 <div class="wpsc-log-diff">
1502 <div class="lhs"><?php $cf->type::print_val( $cf, $body->prev ); ?></div>
1503 <div class="transform-icon">
1504 <?php
1505 if ( is_rtl() ) {
1506 WPSC_Icons::get( 'arrow-left' );
1507 } else {
1508 WPSC_Icons::get( 'arrow-right' );
1509 }
1510 ?>
1511 </div>
1512 <div class="rhs"><?php $cf->type::print_val( $cf, $body->new ); ?></div>
1513 </div>
1514 </div>
1515 </div>
1516 <?php
1517
1518 } else {
1519
1520 ?>
1521 <div class="wpsc-thread log">
1522 <div class="thread-avatar">
1523 <?php
1524 WPSC_Icons::get( 'system' );
1525 ?>
1526 </div>
1527 <div class="thread-body">
1528 <div><?php echo wp_kses_post( $thread->body ); ?></div>
1529 <span class="thread-time" title="<?php echo esc_attr( $title ); ?>"><?php echo esc_attr( $time_str ); ?></span>
1530 </div>
1531 </div>
1532 <?php
1533 }
1534 }
1535
1536 /**
1537 * Check whether current user is customer or not
1538 *
1539 * @return boolean
1540 */
1541 public static function is_customer() {
1542
1543 $current_user = WPSC_Current_User::$current_user;
1544 if ( ! $current_user->is_customer ) {
1545 return false;
1546 }
1547
1548 $adv_setting = get_option( 'wpsc-ms-advanced-settings' );
1549 if ( $adv_setting['public-mode'] && ! $current_user->is_agent ) {
1550 return true;
1551 }
1552
1553 $allowed_customers = apply_filters( 'wpsc_non_agent_ticket_customers_allowed', array( self::$ticket->customer->id ), self::$ticket );
1554 if ( in_array( $current_user->customer->id, $allowed_customers ) ) {
1555 return true;
1556 }
1557
1558 return false;
1559 }
1560
1561 /**
1562 * Agent ticket access for cap
1563 *
1564 * @param string $cap - capability name.
1565 * @return boolean
1566 */
1567 public static function has_ticket_cap( $cap ) {
1568
1569 $current_user = WPSC_Current_User::$current_user;
1570
1571 $assigned_agents = array_map(
1572 fn ( $agent ) => $agent->id,
1573 self::$ticket->assigned_agent
1574 );
1575
1576 $flag = false;
1577 if (
1578 (
1579 ! self::$ticket->assigned_agent &&
1580 $current_user->agent->has_cap( $cap . '-unassigned' )
1581 ) ||
1582 (
1583 in_array( $current_user->agent->id, $assigned_agents ) &&
1584 $current_user->agent->has_cap( $cap . '-assigned-me' )
1585 ) ||
1586 (
1587 self::$ticket->assigned_agent &&
1588 ! in_array( $current_user->agent->id, $assigned_agents ) &&
1589 $current_user->agent->has_cap( $cap . '-assigned-others' )
1590 )
1591 ) {
1592 $flag = true;
1593 }
1594
1595 return apply_filters( 'wpsc_it_has_ticket_cap', $flag, self::$ticket, $cap );
1596 }
1597
1598 /**
1599 * Post a reply to ticket
1600 *
1601 * @return void
1602 */
1603 public static function add_reply() {
1604
1605 if ( check_ajax_referer( 'general', '_ajax_nonce', false ) != 1 ) {
1606 wp_send_json_error( 'Unauthorized request!', 401 );
1607 }
1608
1609 $gs = get_option( 'wpsc-gs-general' );
1610 $current_user = WPSC_Current_User::$current_user;
1611 self::load_current_ticket();
1612
1613 if ( ! (
1614 ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) ||
1615 ( self::$view_profile == 'customer' && self::is_customer() )
1616 ) ) {
1617 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1618 }
1619
1620 // Set reply profile.
1621 self::$reply_profile = 'customer';
1622 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) {
1623 self::$reply_profile = 'agent';
1624 }
1625
1626 // Do not allow if ticket is deleted.
1627 if ( ! self::$ticket->is_active ) {
1628 wp_send_json_error( 'Something went wrong!', 400 );
1629 }
1630
1631 $advanced = get_option( 'wpsc-ms-advanced-settings' );
1632
1633 // check reply permission for public tickets.
1634 if ( ! $current_user->is_agent && $advanced['public-mode'] && ! $advanced['public-mode-reply'] && self::$ticket->customer->id != $current_user->customer->id ) {
1635 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1636 }
1637
1638 // description.
1639 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1640 if ( ! $description ) {
1641 wp_send_json_error( 'Something went wrong!', 401 );
1642 }
1643
1644 // replace new line with br if no text editor.
1645 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1646 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1647
1648 // replace macros only when current user is an agent & check if agent has permission to view customer email address.
1649 if ( $current_user->is_agent && ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1650 $description = str_replace( '{{customer_email}}', '', $description );
1651 }
1652 $description = $current_user->is_agent ? WPSC_Macros::replace( $description, self::$ticket ) : $description;
1653
1654 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1655 // remove divs from message.
1656 $description = preg_replace(
1657 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1658 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1659 $description
1660 );
1661
1662 // set signature if agent.
1663 $signature = $current_user->is_agent ? $current_user->agent->get_signature() : '';
1664 if ( $signature ) {
1665 $description .= '<br>' . $signature;
1666 }
1667
1668 // add ticket auth url to tinymce attachments.
1669 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1670 foreach ( $matches[0] as $url ) {
1671 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1672 $description = str_replace( $url, $new_url, $description );
1673 }
1674 }
1675
1676 // description attachments.
1677 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1678 $validated_attachments = array();
1679 foreach ( $attachments as $id ) {
1680
1681 $attachment = new WPSC_Attachment( $id );
1682 // Only the uploader may bind their own, not-yet-bound attachment.
1683 if ( ! WPSC_Attachment::can_claim( $attachment ) ) {
1684 continue;
1685 }
1686
1687 $validated_attachments[] = $id;
1688 }
1689 $attachments = $validated_attachments;
1690
1691 // submit reply.
1692 $thread = WPSC_Thread::insert(
1693 array(
1694 'ticket' => self::$ticket->id,
1695 'customer' => $current_user->customer->id,
1696 'type' => 'reply',
1697 'body' => $description,
1698 'attachments' => implode( '|', $attachments ),
1699 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1700 'source' => 'browser',
1701 'os' => WPSC_DF_OS::get_user_platform(),
1702 'browser' => WPSC_DF_Browser::get_user_browser(),
1703 )
1704 );
1705
1706 // activate description attachments.
1707 foreach ( $attachments as $id ) :
1708 $attachment = new WPSC_Attachment( $id );
1709 $attachment->is_active = 1;
1710 $attachment->source = 'reply';
1711 $attachment->source_id = $thread->id;
1712 $attachment->ticket_id = self::$ticket->id;
1713 $attachment->save();
1714 endforeach;
1715
1716 // activate description editor img attachments.
1717 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(?:&auth_code=[^&\s"\']*)?(?:&(?:amp;)?wpsc_nonce=([a-f0-9]*))?/', $description, $matches ) ) {
1718 foreach ( $matches[1] as $key => $id ) {
1719 $attachment = new WPSC_Attachment( $id );
1720 // Only the uploader may bind their own, not-yet-bound
1721 // attachment, proven by the recorded uploader identity or
1722 // by the per-attachment nonce carried in the image url.
1723 $nonce = isset( $matches[2][ $key ] ) ? $matches[2][ $key ] : '';
1724 if ( ! WPSC_Attachment::can_claim( $attachment, $nonce ) ) {
1725 continue;
1726 }
1727 $attachment->is_active = 1;
1728 $attachment->source_id = $thread->id;
1729 $attachment->ticket_id = self::$ticket->id;
1730 $attachment->source = 'img_editor';
1731 $attachment->save();
1732 }
1733 }
1734
1735 self::$ticket->date_updated = new DateTime();
1736 self::$ticket->last_reply_on = new DateTime();
1737 self::$ticket->last_reply_by = $current_user->customer->id;
1738 self::$ticket->last_reply_source = 'browser';
1739 self::$ticket->save();
1740
1741 do_action( 'wpsc_post_reply', $thread );
1742 wp_die();
1743 }
1744
1745 /**
1746 * Post a note to ticket
1747 *
1748 * @return void
1749 */
1750 public static function add_note() {
1751
1752 if ( check_ajax_referer( 'general', '_ajax_nonce', false ) != 1 ) {
1753 wp_send_json_error( 'Unauthorized request!', 401 );
1754 }
1755
1756 $gs = get_option( 'wpsc-gs-general' );
1757 $current_user = WPSC_Current_User::$current_user;
1758 self::load_current_ticket();
1759
1760 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
1761 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1762 }
1763
1764 // Do not allow if ticket is deleted.
1765 if ( ! self::$ticket->is_active ) {
1766 wp_send_json_error( 'Something went wrong!', 400 );
1767 }
1768
1769 // description.
1770 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1771 if ( ! $description ) {
1772 wp_send_json_error( 'Something went wrong!', 401 );
1773 }
1774
1775 // replace new line with br if no text editor.
1776 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1777 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1778
1779 // check if agent has permission to view customer email address.
1780 if ( ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1781 $description = str_replace( '{{customer_email}}', '', $description );
1782 }
1783
1784 // add ticket auth url to tinymce attachments.
1785 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1786 foreach ( $matches[0] as $url ) {
1787 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1788 $description = str_replace( $url, $new_url, $description );
1789 }
1790 }
1791
1792 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1793 // remove divs from message.
1794 $description = preg_replace(
1795 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1796 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1797 $description
1798 );
1799
1800 // description attachments.
1801 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1802 $validated_attachments = array();
1803 foreach ( $attachments as $id ) {
1804
1805 $attachment = new WPSC_Attachment( $id );
1806 // Only the uploader may bind their own, not-yet-bound attachment.
1807 if ( ! WPSC_Attachment::can_claim( $attachment ) ) {
1808 continue;
1809 }
1810
1811 $validated_attachments[] = $id;
1812 }
1813 $attachments = $validated_attachments;
1814
1815 // submit note.
1816 $thread = WPSC_Thread::insert(
1817 array(
1818 'ticket' => self::$ticket->id,
1819 'customer' => $current_user->customer->id,
1820 'type' => 'note',
1821 'body' => WPSC_Macros::replace( $description, self::$ticket ),
1822 'attachments' => implode( '|', $attachments ),
1823 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1824 'source' => 'browser',
1825 'os' => WPSC_DF_OS::get_user_platform(),
1826 'browser' => WPSC_DF_Browser::get_user_browser(),
1827 )
1828 );
1829
1830 // activate description attachments.
1831 foreach ( $attachments as $id ) :
1832 $attachment = new WPSC_Attachment( $id );
1833 $attachment->is_active = 1;
1834 $attachment->source = 'note';
1835 $attachment->source_id = $thread->id;
1836 $attachment->ticket_id = self::$ticket->id;
1837 $attachment->save();
1838 endforeach;
1839
1840 // activate description editor img attachments.
1841 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(?:&auth_code=[^&\s"\']*)?(?:&(?:amp;)?wpsc_nonce=([a-f0-9]*))?/', $description, $matches ) ) {
1842 foreach ( $matches[1] as $key => $id ) {
1843 $attachment = new WPSC_Attachment( $id );
1844 // Only the uploader may bind their own, not-yet-bound
1845 // attachment, proven by the recorded uploader identity or
1846 // by the per-attachment nonce carried in the image url.
1847 $nonce = isset( $matches[2][ $key ] ) ? $matches[2][ $key ] : '';
1848 if ( ! WPSC_Attachment::can_claim( $attachment, $nonce ) ) {
1849 continue;
1850 }
1851 $attachment->is_active = 1;
1852 $attachment->source_id = $thread->id;
1853 $attachment->ticket_id = self::$ticket->id;
1854 $attachment->source = 'img_editor';
1855 $attachment->save();
1856 }
1857 }
1858
1859 self::$ticket->date_updated = new DateTime();
1860 self::$ticket->save();
1861
1862 do_action( 'wpsc_submit_note', $thread );
1863 wp_die();
1864 }
1865
1866 /**
1867 * Post a reply & close the ticket
1868 *
1869 * @return void
1870 */
1871 public static function it_reply_and_close() {
1872
1873 if ( check_ajax_referer( 'wpsc_it_reply_and_close', '_ajax_nonce', false ) != 1 ) {
1874 wp_send_json_error( 'Unauthorized request!', 401 );
1875 }
1876
1877 $gs = get_option( 'wpsc-gs-general' );
1878 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
1879 $advanced = get_option( 'wpsc-ms-advanced-settings' );
1880 $current_user = WPSC_Current_User::$current_user;
1881 self::load_current_ticket();
1882
1883 if ( ! $advanced['reply-close'] ) {
1884 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1885 }
1886
1887 if ( ! (
1888 ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) ||
1889 ( self::$view_profile == 'customer' && self::is_customer() )
1890 ) ) {
1891 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1892 }
1893
1894 // Set reply profile.
1895 self::$reply_profile = 'customer';
1896 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'reply' ) ) {
1897 self::$reply_profile = 'agent';
1898 }
1899
1900 // Do not allow if ticket is deleted.
1901 if ( ! self::$ticket->is_active ) {
1902 wp_send_json_error( 'Something went wrong!', 400 );
1903 }
1904
1905 // check reply permission for public tickets.
1906 if ( ! $current_user->is_agent && $advanced['public-mode'] && ! $advanced['public-mode-reply'] && self::$ticket->customer->id != $current_user->customer->id ) {
1907 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
1908 }
1909
1910 // description.
1911 $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
1912 if ( ! $description ) {
1913 wp_send_json_error( 'Something went wrong!', 401 );
1914 }
1915
1916 // status change permission validation.
1917 $close_flag = false;
1918 if ( $current_user->is_customer &&
1919 (
1920 ( $current_user->customer->id == self::$ticket->customer->id && in_array( 'customer', $gs['allow-close-ticket'] ) ) ||
1921 ( $current_user->is_agent && self::has_ticket_cap( 'cs' ) && in_array( $current_user->agent->role, $gs['allow-close-ticket'] ) )
1922 )
1923 ) {
1924 $close_flag = true;
1925 }
1926
1927 $close_flag = apply_filters( 'wpsc_it_action_close_flag', $close_flag, self::$ticket );
1928
1929 if ( ! (
1930 $close_flag &&
1931 ! (
1932 self::$ticket->status->id == $gs['close-ticket-status'] ||
1933 in_array( self::$ticket->status->id, $tl_advanced['closed-ticket-statuses'] )
1934 )
1935 ) ) {
1936 wp_send_json_error( 'Unauthorized!', 401 );
1937 }
1938
1939 // replace new line with br if no text editor.
1940 $is_editor = isset( $_POST['is_editor'] ) ? intval( $_POST['is_editor'] ) : 0;
1941 $description = ! $is_editor ? nl2br( esc_html( $description ) ) : $description;
1942
1943 // replace macros only when current user is an agent & check if agent has permission to view customer email address.
1944 if ( $current_user->is_agent && ! in_array( $current_user->agent->role, $gs['allow-ar-thread-email'] ) ) {
1945 $description = str_replace( '{{customer_email}}', '', $description );
1946 }
1947 $description = $current_user->is_agent ? WPSC_Macros::replace( $description, self::$ticket ) : $description;
1948
1949 $description = preg_replace( '/(<(script|style)\b[^>]*>).*?(<\/\2>)/s', '', $description );
1950 // remove divs from message.
1951 $description = preg_replace(
1952 array( '/<div(.*?)>/s', '/<\/div>/s', '/<table(.*?)>/s', '/<tbody(.*?)>/s', '/<tr(.*?)>/s', '/<th(.*?)>/s', '/<td(.*?)>/s', '/<pre(.*?)>/s', '/<\/pre>/s' ),
1953 array( '<p>', '</p>', '<table>', '<tbody>', '<tr>', '<th>', '<td>', '<p>', '</p>' ),
1954 $description
1955 );
1956
1957 // set signature if agent.
1958 $signature = $current_user->is_agent ? $current_user->agent->get_signature() : '';
1959 if ( $signature ) {
1960 $description .= '<br>' . $signature;
1961 }
1962
1963 // add ticket auth url to tinymce attachments.
1964 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)/', $description, $matches ) ) {
1965 foreach ( $matches[0] as $url ) {
1966 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
1967 $description = str_replace( $url, $new_url, $description );
1968 }
1969 }
1970
1971 // description attachments.
1972 $attachments = isset( $_POST['description_attachments'] ) ? array_filter( array_map( 'intval', $_POST['description_attachments'] ) ) : array();
1973 $validated_attachments = array();
1974 foreach ( $attachments as $id ) {
1975
1976 $attachment = new WPSC_Attachment( $id );
1977 // Only the uploader may bind their own, not-yet-bound attachment.
1978 if ( ! WPSC_Attachment::can_claim( $attachment ) ) {
1979 continue;
1980 }
1981
1982 $validated_attachments[] = $id;
1983 }
1984 $attachments = $validated_attachments;
1985
1986 // submit reply.
1987 $thread = WPSC_Thread::insert(
1988 array(
1989 'ticket' => self::$ticket->id,
1990 'customer' => $current_user->customer->id,
1991 'type' => 'reply',
1992 'body' => $description,
1993 'attachments' => implode( '|', $attachments ),
1994 'ip_address' => WPSC_DF_IP_Address::get_current_user_ip(),
1995 'source' => 'browser',
1996 'os' => WPSC_DF_OS::get_user_platform(),
1997 'browser' => WPSC_DF_Browser::get_user_browser(),
1998 )
1999 );
2000
2001 // activate description attachments.
2002 foreach ( $attachments as $id ) :
2003 $attachment = new WPSC_Attachment( $id );
2004 $attachment->is_active = 1;
2005 $attachment->source = 'reply';
2006 $attachment->source_id = $thread->id;
2007 $attachment->ticket_id = self::$ticket->id;
2008 $attachment->save();
2009 endforeach;
2010
2011 // activate description editor img attachments.
2012 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(?:&auth_code=[^&\s"\']*)?(?:&(?:amp;)?wpsc_nonce=([a-f0-9]*))?/', $description, $matches ) ) {
2013 foreach ( $matches[1] as $key => $id ) {
2014 $attachment = new WPSC_Attachment( $id );
2015 // Only the uploader may bind their own, not-yet-bound
2016 // attachment, proven by the recorded uploader identity or
2017 // by the per-attachment nonce carried in the image url.
2018 $nonce = isset( $matches[2][ $key ] ) ? $matches[2][ $key ] : '';
2019 if ( ! WPSC_Attachment::can_claim( $attachment, $nonce ) ) {
2020 continue;
2021 }
2022 $attachment->is_active = 1;
2023 $attachment->source_id = $thread->id;
2024 $attachment->ticket_id = self::$ticket->id;
2025 $attachment->source = 'img_editor';
2026 $attachment->save();
2027 }
2028 }
2029
2030 $prev = self::$ticket->status->id;
2031
2032 self::$ticket->last_reply_on = new DateTime();
2033 self::$ticket->last_reply_by = $current_user->customer->id;
2034 self::$ticket->last_reply_source = 'browser';
2035 self::$ticket->status = $gs['close-ticket-status'];
2036 self::$ticket->date_closed = new DateTime( 'now' );
2037 self::$ticket->date_updated = new DateTime();
2038 self::$ticket->save();
2039
2040 $thread->ticket = self::$ticket;
2041
2042 remove_action( 'wpsc_post_reply', array( 'WPSC_GS_General', 'change_ticket_status' ) );
2043
2044 do_action( 'wpsc_post_reply', $thread );
2045
2046 remove_action( 'wpsc_change_ticket_status', array( 'WPSC_EN_Change_Ticket_Status', 'process_event' ), 200 );
2047
2048 do_action( 'wpsc_change_ticket_status', self::$ticket, $prev, self::$ticket->status->id, $current_user->customer->id );
2049
2050 wp_die();
2051 }
2052
2053 /**
2054 * Change status of individual ticket
2055 *
2056 * @param integer $prev - privious status.
2057 * @param integer $new - new status.
2058 * @param integer $customer_id - customer id.
2059 * @return void
2060 */
2061 public static function change_status( $prev, $new, $customer_id ) {
2062
2063 $gs = get_option( 'wpsc-gs-general' );
2064 self::$ticket->status = $new;
2065 $tl_advanced = get_option( 'wpsc-tl-ms-advanced' );
2066 if ( $new == $gs['close-ticket-status'] || in_array( $new, $tl_advanced['closed-ticket-statuses'] ) ) {
2067 self::$ticket->date_closed = new DateTime( 'now' );
2068 } else {
2069 self::$ticket->date_closed = '0000-00-00 00:00:00';
2070 }
2071
2072 self::$ticket->date_updated = new DateTime();
2073 self::$ticket->save();
2074 do_action( 'wpsc_change_ticket_status', self::$ticket, $prev, $new, $customer_id );
2075 }
2076
2077 /**
2078 * Change category of individual ticket
2079 *
2080 * @param integer $prev - privious category.
2081 * @param integer $new - new category.
2082 * @param integer $customer_id - customer id.
2083 * @return void
2084 */
2085 public static function change_category( $prev, $new, $customer_id ) {
2086
2087 self::$ticket->category = $new;
2088 self::$ticket->date_updated = new DateTime();
2089 self::$ticket->save();
2090 do_action( 'wpsc_change_ticket_category', self::$ticket, $prev, $new, $customer_id );
2091 }
2092
2093 /**
2094 * Change priority of individual ticket
2095 *
2096 * @param integer $prev - privious priority.
2097 * @param integer $new - new priority.
2098 * @param integer $customer_id - customer id.
2099 * @return void
2100 */
2101 public static function change_priority( $prev, $new, $customer_id ) {
2102
2103 self::$ticket->priority = $new;
2104 self::$ticket->date_updated = new DateTime();
2105 self::$ticket->save();
2106 do_action( 'wpsc_change_ticket_priority', self::$ticket, $prev, $new, $customer_id );
2107 }
2108
2109 /**
2110 * Change customer
2111 *
2112 * @param WPSC_Customer $prev - privious customer.
2113 * @param WPSC_Customer $new - new customer.
2114 * @param integer $customer_id - customer id.
2115 * @return void
2116 */
2117 public static function change_raised_by( $prev, $new, $customer_id ) {
2118
2119 self::$ticket->customer = $new;
2120 self::$ticket->date_updated = new DateTime();
2121 self::$ticket->save();
2122 do_action( 'wpsc_change_raised_by', self::$ticket, $prev, $new, $customer_id );
2123 }
2124
2125 /**
2126 * Change assignee
2127 *
2128 * @param array $prev - privious assignee.
2129 * @param array $new - new assignee.
2130 * @param integer $customer_id - customer id.
2131 * @return void
2132 */
2133 public static function change_assignee( $prev, $new, $customer_id ) {
2134
2135 self::$ticket->assigned_agent = $new;
2136 self::$ticket->prev_assignee = $prev;
2137 self::$ticket->date_updated = new DateTime();
2138 self::$ticket->save();
2139 do_action( 'wpsc_change_assignee', self::$ticket, $prev, $new, $customer_id );
2140 }
2141
2142 /**
2143 * Change tags
2144 *
2145 * @param array $prev - privious tag.
2146 * @param array $new - new tag.
2147 * @param integer $customer_id - customer id.
2148 * @return void
2149 */
2150 public static function change_tag( $prev, $new, $customer_id ) {
2151
2152 self::$ticket->tags = $new;
2153 self::$ticket->date_updated = new DateTime();
2154 self::$ticket->save();
2155 do_action( 'wpsc_change_tag', self::$ticket, $prev, $new, $customer_id );
2156 }
2157
2158 /**
2159 * Get duplicate ticket modal
2160 *
2161 * @return void
2162 */
2163 public static function get_duplicate_ticket() {
2164
2165 if ( check_ajax_referer( 'wpsc_it_get_duplicate_ticket', '_ajax_nonce', false ) != 1 ) {
2166 wp_send_json_error( 'Unauthorized request!', 401 );
2167 }
2168
2169 self::load_current_ticket();
2170
2171 if ( self::$is_restricted ) {
2172 wp_send_json_error( 'Unauthorized request!', 401 );
2173 }
2174
2175 $current_user = WPSC_Current_User::$current_user;
2176 if ( self::$view_profile != 'agent' ) {
2177 wp_send_json_error( 'Something went wrong!', 401 );
2178 }
2179
2180 $ticket = self::$ticket;
2181 $title = esc_attr__( 'Duplicate', 'supportcandy' );
2182
2183 ob_start();
2184 ?>
2185 <form action="#" onsubmit="return false;" class="duplicate">
2186 <div class="wpsc-input-group">
2187 <div class="label-container">
2188 <label for=""><?php esc_attr_e( 'Subject', 'supportcandy' ); ?></label>
2189 </div>
2190 <input name="subject" type="text" value="<?php echo esc_attr( $ticket->subject ); ?>" autocomplete="off">
2191 </div>
2192 <?php do_action( 'wpsc_it_get_duplicate_ticket', $ticket ); ?>
2193 <input type="hidden" name="action" value="wpsc_it_set_duplicate_ticket">
2194 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( $ticket->id ); ?>">
2195 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_duplicate_ticket' ) ); ?>">
2196 </form>
2197 <?php
2198 $body = ob_get_clean();
2199
2200 ob_start();
2201 ?>
2202 <button class="wpsc-button small primary" onclick="wpsc_it_set_duplicate_ticket(this, <?php echo esc_attr( $ticket->id ); ?>);">
2203 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
2204 </button>
2205 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2206 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
2207 </button>
2208 <?php
2209 do_action( 'wpsc_it_get_duplicate_ticket_footer', $ticket );
2210 $footer = ob_get_clean();
2211
2212 $response = array(
2213 'title' => $title,
2214 'body' => $body,
2215 'footer' => $footer,
2216 );
2217 wp_send_json( $response );
2218 }
2219
2220 /**
2221 * Set duplicate ticket
2222 *
2223 * @return void
2224 */
2225 public static function set_duplicate_ticket() {
2226
2227 if ( check_ajax_referer( 'wpsc_it_set_duplicate_ticket', '_ajax_nonce', false ) != 1 ) {
2228 wp_send_json_error( 'Unauthorized request!', 401 );
2229 }
2230
2231 self::load_current_ticket();
2232 $current_user = WPSC_Current_User::$current_user;
2233 if ( self::$view_profile != 'agent' && ! self::has_ticket_cap( 'dt' ) ) {
2234 wp_send_json_error( 'Something went wrong!', 401 );
2235 }
2236
2237 $ticket = self::$ticket;
2238 $threads = $ticket->get_threads( 1, 0, array( 'report', 'reply' ), 'date_created', 'ASC' );
2239
2240 // ignore custom field types while duplicating ticket.
2241 $ignore_cft = apply_filters(
2242 'wpsc_duplicate_ticket_ignore_cft',
2243 array(
2244 'cf_html',
2245 'df_description',
2246 'df_id',
2247 'df_ip_address',
2248 'df_browser',
2249 'df_assigned_agent',
2250 'df_prev_assignee',
2251 'df_date_closed',
2252 'df_add_recipients',
2253 'df_ip_address',
2254 'df_os',
2255 'df_sla',
2256 'df_last_reply_on',
2257 'df_last_reply_by',
2258 'df_sf_date',
2259 'df_sf_feedback',
2260 'df_sf_rating',
2261 'df_time_spent',
2262 'df_usergroups',
2263 'df_last_reply_source',
2264 )
2265 );
2266
2267 $data = array();
2268 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
2269 if ( ! in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
2270 continue;
2271 }
2272 if ( in_array( $cf->type::$slug, $ignore_cft ) ) {
2273 continue;
2274 }
2275 $data[ $cf->slug ] = $cf->type::get_duplicate_ticket_data( $cf, $ticket );
2276 }
2277 $data['assigned_agent'] = '';
2278 $duplicate = WPSC_Ticket::insert( $data );
2279
2280 foreach ( $threads as $thread ) {
2281 $attachments = array();
2282 foreach ( $thread->attachments as $attachment ) {
2283 $attachments[] = self::get_duplicate_attachment_id( $attachment->id );
2284 }
2285 $duplicate_thread = WPSC_Thread::insert(
2286 array(
2287 'ticket' => $duplicate->id,
2288 'customer' => $thread->customer->id,
2289 'type' => $thread->type,
2290 'body' => $thread->body,
2291 'attachments' => $attachments ? implode( '|', $attachments ) : '',
2292 'ip_address' => $thread->ip_address,
2293 'source' => $thread->source,
2294 'os' => $thread->os,
2295 'browser' => $thread->browser,
2296 'date_created' => $thread->date_created->format( 'Y-m-d H:i:s' ),
2297 'date_updated' => $thread->date_updated->format( 'Y-m-d H:i:s' ),
2298 )
2299 );
2300 foreach ( $attachments as $id ) :
2301 $attachment = new WPSC_Attachment( $id );
2302 $attachment->source = $duplicate_thread->type;
2303 $attachment->source_id = $duplicate_thread->id;
2304 $attachment->ticket_id = $duplicate->id;
2305 $attachment->save();
2306 endforeach;
2307
2308 // duplicate description attachments.
2309 $body_attachments = array();
2310 $attachment_of_body = WPSC_Attachment::find(
2311 array(
2312 'items_per_page' => 0,
2313 'meta_query' => array(
2314 'relation' => 'AND',
2315 array(
2316 'slug' => 'source',
2317 'compare' => '=',
2318 'val' => 'img_editor',
2319 ),
2320 array(
2321 'slug' => 'source_id',
2322 'compare' => '=',
2323 'val' => $thread->id,
2324 ),
2325 ),
2326 )
2327 )['results'];
2328 foreach ( $attachment_of_body as $img ) {
2329 $body_attachments[ $img->id ] = self::get_duplicate_attachment_id( $img->id );
2330 }
2331 foreach ( $body_attachments as $old_id => $id ) :
2332 $attachment = new WPSC_Attachment( $id );
2333 $attachment->source = 'img_editor';
2334 $attachment->source_id = $duplicate_thread->id;
2335 $attachment->ticket_id = $duplicate->id;
2336 $attachment->save();
2337
2338 $old_url = home_url( '/' ) . '?wpsc_attachment=' . $old_id;
2339 $new_url = home_url( '/' ) . '?wpsc_attachment=' . $id;
2340 $duplicate_thread->body = str_replace( $old_url, $new_url, $duplicate_thread->body );
2341 endforeach;
2342
2343 $duplicate_thread->save();
2344 }
2345
2346 do_action( 'wpsc_create_new_ticket', $duplicate );
2347
2348 wp_send_json( array( 'ticket_id' => $duplicate->id ) );
2349 }
2350
2351 /**
2352 * Delete ticket ajax request
2353 */
2354 public static function set_delete_ticket() {
2355
2356 if ( check_ajax_referer( 'wpsc_it_delete_ticket', '_ajax_nonce', false ) != 1 ) {
2357 wp_send_json_error( 'Unauthorized request!', 401 );
2358 }
2359
2360 $current_user = WPSC_Current_User::$current_user;
2361 if ( ! $current_user->is_agent ) {
2362 wp_send_json_error( 'Unauthorized request!', 401 );
2363 }
2364
2365 self::load_current_ticket();
2366
2367 if ( self::$is_restricted || ! self::has_ticket_cap( 'dtt' ) ) {
2368 wp_send_json_error( 'Unauthorized request!', 401 );
2369 }
2370
2371 self::delete_ticket();
2372 wp_die();
2373 }
2374
2375 /**
2376 * Delete current ticket
2377 *
2378 * @return void
2379 */
2380 public static function delete_ticket() {
2381
2382 self::$ticket->is_active = 0;
2383 self::$ticket->date_updated = new DateTime();
2384 self::$ticket->save();
2385 do_action( 'wpsc_delete_ticket', self::$ticket );
2386 }
2387
2388 /**
2389 * Restore ticket ajax request
2390 *
2391 * @return void
2392 */
2393 public static function ticket_restore() {
2394
2395 if ( check_ajax_referer( 'wpsc_it_ticket_restore', '_ajax_nonce', false ) != 1 ) {
2396 wp_send_json_error( 'Unauthorized request!', 401 );
2397 }
2398
2399 $current_user = WPSC_Current_User::$current_user;
2400 if ( ! $current_user->is_agent ) {
2401 wp_send_json_error( 'Unauthorized request!', 401 );
2402 }
2403
2404 self::load_current_ticket();
2405
2406 if ( ! self::$is_restricted || ! self::has_ticket_cap( 'dtt' ) ) {
2407 wp_send_json_error( 'Unauthorized request!', 401 );
2408 }
2409
2410 self::restore_ticket();
2411 wp_die();
2412 }
2413
2414 /**
2415 * Restore current ticket
2416 *
2417 * @return void
2418 */
2419 public static function restore_ticket() {
2420
2421 self::$ticket->is_active = 1;
2422 self::$ticket->date_updated = new DateTime();
2423 self::$ticket->save();
2424 do_action( 'wpsc_ticket_restore', self::$ticket );
2425 }
2426
2427 /**
2428 * Archive ticket ajax request
2429 */
2430 public static function set_archive_ticket() {
2431
2432 if ( check_ajax_referer( 'wpsc_it_archive_ticket', '_ajax_nonce', false ) != 1 ) {
2433 wp_send_json_error( 'Unauthorized request!', 401 );
2434 }
2435
2436 $current_user = WPSC_Current_User::$current_user;
2437 if ( ! $current_user->is_agent ) {
2438 wp_send_json_error( 'Unauthorized request!', 401 );
2439 }
2440
2441 self::load_current_ticket();
2442 self::archive_ticket();
2443 wp_die();
2444 }
2445
2446 /**
2447 * Archive current ticket
2448 *
2449 * @return void
2450 */
2451 public static function archive_ticket() {
2452
2453 self::$ticket->live_agents = '';
2454 self::$ticket->date_updated = new DateTime();
2455 self::$ticket->save();
2456 $id = self::$ticket->id;
2457
2458 $success = WPSC_Archive_Ticket::set_archive_ticket( self::$ticket );
2459 if ( $success ) {
2460 $ar_ticket = new WPSC_Archive_Ticket( $id );
2461 do_action( 'wpsc_ticket_archive', self::$ticket, $ar_ticket );
2462 }
2463 }
2464
2465 /**
2466 * Permanently delete ticket ajax request
2467 *
2468 * @return void
2469 */
2470 public static function set_delete_ticket_permanently() {
2471
2472 if ( check_ajax_referer( 'wpsc_it_delete_permanently', '_ajax_nonce', false ) != 1 ) {
2473 wp_send_json_error( 'Unauthorized request!', 401 );
2474 }
2475
2476 $current_user = WPSC_Current_User::$current_user;
2477 if ( ! $current_user->is_agent ) {
2478 wp_send_json_error( 'Unauthorized request!', 401 );
2479 }
2480
2481 self::load_current_ticket();
2482 if ( ! self::has_ticket_cap( 'dtt' ) ) {
2483 wp_send_json_error( 'Unauthorized request!', 401 );
2484 }
2485
2486 self::delete_permanently();
2487 wp_die();
2488 }
2489
2490 /**
2491 * Block/Unblock ticket notifications ajax request
2492 *
2493 * @return void
2494 */
2495 public static function set_block_unblock_notifications() {
2496
2497 if ( ! check_ajax_referer( 'wpsc_it_block_unblock_notifications', '_ajax_nonce', false ) ) {
2498 wp_send_json_error( __( 'Unauthorized request.', 'supportcandy' ), 401 );
2499 }
2500
2501 $current_user = WPSC_Current_User::$current_user;
2502 if ( ! $current_user || ! $current_user->is_agent ) {
2503 wp_send_json_error( __( 'Unauthorized request.', 'supportcandy' ), 401 );
2504 }
2505
2506 self::load_current_ticket();
2507 if ( self::$is_restricted || ! self::$ticket ) {
2508 wp_send_json_error( __( 'Unauthorized request.', 'supportcandy' ), 401 );
2509 }
2510
2511 $is_block = isset( $_POST['is_block'] ) ? (int) $_POST['is_block'] : 0;
2512 $is_block = $is_block === 1 ? 1 : 0;
2513
2514 $misc = is_array( self::$ticket->misc ) ? self::$ticket->misc : array();
2515 if ( $is_block ) {
2516 $misc['block_notifications'] = 1;
2517 } else {
2518 unset( $misc['block_notifications'] );
2519 }
2520
2521 self::$ticket->misc = $misc;
2522 self::$ticket->save();
2523 do_action( 'wpsc_ticket_block_notifications', self::$ticket, $is_block );
2524 wp_die();
2525 }
2526
2527 /**
2528 * Permanently delete current ticket
2529 *
2530 * @return void
2531 */
2532 public static function delete_permanently() {
2533
2534 WPSC_Ticket::destroy( self::$ticket );
2535 do_action( 'wpsc_ticket_delete_permanently', self::$ticket );
2536 }
2537
2538 /**
2539 * Create ticket from thread
2540 *
2541 * @return void
2542 */
2543 public static function it_thread_new_ticket() {
2544
2545 if ( check_ajax_referer( 'wpsc_it_thread_new_ticket', '_ajax_nonce', false ) != 1 ) {
2546 wp_send_json_error( 'Unauthorized request!', 401 );
2547 }
2548
2549 if ( self::$is_restricted ) {
2550 wp_send_json_error( 'Unauthorized request!', 401 );
2551 }
2552
2553 self::load_current_ticket();
2554 $current_user = WPSC_Current_User::$current_user;
2555 if ( self::$is_restricted ) {
2556 wp_send_json_error( 'Unauthorised request!', 401 );
2557 }
2558
2559 if ( !
2560 self::$view_profile == 'agent'
2561 ) {
2562 wp_send_json_error( __( 'Unauthorized', 'supportcandy' ), 401 );
2563 }
2564
2565 $ticket = self::$ticket;
2566 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2567 $title = esc_attr__( 'Create ticket from thread', 'supportcandy' );
2568
2569 ob_start();
2570 ?>
2571 <form action="#" onsubmit="return false;" class="new_ticket_from_thread">
2572 <div class="wpsc-input-group">
2573 <div class="label-container">
2574 <label for=""><?php esc_attr_e( 'Subject', 'supportcandy' ); ?></label>
2575 </div>
2576 <input name="subject" type="text" value="<?php echo esc_attr( $ticket->subject ); ?>" autocomplete="off">
2577 </div>
2578 <?php do_action( 'wpsc_it_thread_new_ticket', $thread_id ); ?>
2579 <input type="hidden" name="action" value="wpsc_it_set_thread_new_ticket">
2580 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( $ticket->id ); ?>">
2581 <input type="hidden" name="thread_id" value="<?php echo esc_attr( $thread_id ); ?>">
2582 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_thread_new_ticket' ) ); ?>">
2583 </form>
2584 <?php
2585 $body = ob_get_clean();
2586
2587 ob_start();
2588 ?>
2589 <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' ) ); ?>');">
2590 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
2591 </button>
2592 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2593 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
2594 </button>
2595 <?php
2596 do_action( 'wpsc_it_set_thread_new_ticket_footer', $thread_id );
2597 $footer = ob_get_clean();
2598
2599 $response = array(
2600 'title' => $title,
2601 'body' => $body,
2602 'footer' => $footer,
2603 );
2604 wp_send_json( $response );
2605 }
2606
2607 /**
2608 * Get thread info
2609 *
2610 * @return void
2611 */
2612 public static function it_thread_info() {
2613
2614 if ( check_ajax_referer( 'wpsc_it_thread_info', '_ajax_nonce', false ) != 1 ) {
2615 wp_send_json_error( 'Unauthorised request!', 401 );
2616 }
2617
2618 self::load_current_ticket();
2619
2620 $current_user = WPSC_Current_User::$current_user;
2621
2622 if ( ! ( self::$view_profile == 'agent' ) ) {
2623 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2624 }
2625
2626 $title = esc_attr__( 'Thread info', 'supportcandy' );
2627
2628 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2629 if ( ! $thread_id ) {
2630 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2631 }
2632
2633 $thread = new WPSC_Thread( $thread_id );
2634 if ( ! $thread->id ) {
2635 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2636 }
2637
2638 if ( self::$ticket != $thread->ticket ) {
2639 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2640 }
2641
2642 $settings = get_option( 'wpsc-gs-general', array() );
2643 $advanced = get_option( 'wpsc-ms-advanced-settings', array() );
2644 $ip_address = $thread->ip_address ? $thread->ip_address : esc_attr__( 'Not Applicable', 'supportcandy' );
2645 $os = $thread->os ? $thread->os : esc_attr__( 'Not Applicable', 'supportcandy' );
2646 $browser = $thread->browser ? $thread->browser : esc_attr__( 'Not Applicable', 'supportcandy' );
2647 $source = $thread->source && isset( WPSC_DF_Source::$sources[ $thread->source ] ) ? WPSC_DF_Source::$sources[ $thread->source ] : esc_attr__( 'Not Applicable', 'supportcandy' );
2648
2649 ob_start();
2650 ?>
2651 <div class="wpsc-thread-info">
2652
2653 <div class="info-list-item">
2654 <div class="info-label"><?php esc_attr_e( 'Name', 'supportcandy' ); ?>:</div>
2655 <div class="info-val"><?php echo esc_attr( $thread->customer->name ); ?></div>
2656 </div>
2657 <?php
2658
2659 if ( $current_user->is_agent && in_array( $current_user->agent->role, $settings['allow-ar-thread-email'] ) ) {
2660 ?>
2661 <div class="info-list-item">
2662 <div class="info-label"><?php esc_attr_e( 'Email Address', 'supportcandy' ); ?>:</div>
2663 <div class="info-val"><?php echo esc_attr( $thread->customer->email ); ?></div>
2664 </div>
2665 <?php
2666 }
2667 ?>
2668
2669 <div class="info-list-item">
2670 <div class="info-label"><?php esc_attr_e( 'Source', 'supportcandy' ); ?>:</div>
2671 <div class="info-val"><?php echo esc_attr( $source ); ?></div>
2672 </div>
2673
2674 <div class="info-list-item">
2675 <div class="info-label"><?php esc_attr_e( 'IP Address', 'supportcandy' ); ?>:</div>
2676 <div class="info-val"><?php echo esc_attr( $ip_address ); ?></div>
2677 </div>
2678
2679 <div class="info-list-item">
2680 <div class="info-label"><?php esc_attr_e( 'Browser', 'supportcandy' ); ?>:</div>
2681 <div class="info-val"><?php echo esc_attr( $browser ); ?></div>
2682 </div>
2683
2684 <div class="info-list-item">
2685 <div class="info-label"><?php esc_attr_e( 'Operating System', 'supportcandy' ); ?>:</div>
2686 <div class="info-val"><?php echo esc_attr( $os ); ?></div>
2687 </div>
2688
2689 <?php
2690 if ( $thread->type == 'reply' && $thread->customer != $thread->ticket->customer ) {
2691 ?>
2692 <div class="info-list-item">
2693 <div class="info-label"><?php esc_attr_e( 'Seen', 'supportcandy' ); ?>:</div>
2694 <div class="info-val">
2695 <?php echo is_object( $thread->seen ) ? esc_attr( $thread->seen->setTimezone( wp_timezone() )->format( $advanced['thread-date-format'] ) ) : esc_attr_e( 'No', 'supportcandy' ); ?>
2696 </div>
2697 </div>
2698 <?php
2699 }
2700 do_action( 'wpsc_thread_info_body', $thread );
2701 ?>
2702 </div>
2703 <?php
2704 $body = ob_get_clean();
2705
2706 ob_start();
2707 ?>
2708 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
2709 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
2710 </button>
2711 <?php
2712 $footer = ob_get_clean();
2713
2714 $response = array(
2715 'title' => $title,
2716 'body' => $body,
2717 'footer' => $footer,
2718 );
2719 wp_send_json( $response );
2720 }
2721
2722 /**
2723 * Create new ticket from thread
2724 *
2725 * @return void
2726 */
2727 public static function it_set_thread_new_ticket() {
2728
2729 if ( check_ajax_referer( 'wpsc_it_set_thread_new_ticket', '_ajax_nonce', false ) != 1 ) {
2730 wp_send_json_error( 'Unauthorized request!', 401 );
2731 }
2732
2733 if ( self::$is_restricted ) {
2734 wp_send_json_error( 'Unauthorized request!', 401 );
2735 }
2736
2737 global $wpdb;
2738 self::load_current_ticket();
2739 $current_user = WPSC_Current_User::$current_user;
2740
2741 if ( self::$view_profile != 'agent' ) {
2742 wp_send_json_error( 'Something went wrong!', 401 );
2743 }
2744
2745 $ticket = self::$ticket;
2746 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2747
2748 $thread = new WPSC_Thread( $thread_id );
2749 if ( ! $thread->id ) {
2750 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2751 }
2752
2753 if ( self::$ticket != $thread->ticket ) {
2754 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2755 }
2756
2757 $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' ) );
2758 $data = array();
2759 foreach ( WPSC_Custom_Field::$custom_fields as $cf ) {
2760
2761 if ( in_array( $cf->type::$slug, $ignore_cft ) || ! in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) {
2762 continue;
2763 }
2764 $data[ $cf->slug ] = $cf->type::get_duplicate_ticket_data( $cf, $ticket );
2765 }
2766
2767 $new_ticket = WPSC_Ticket::insert( $data );
2768
2769 // Duplicate thread attachments.
2770 $attachments = array();
2771 foreach ( $thread->attachments as $attachment ) {
2772 $attachments[] = self::get_duplicate_attachment_id( $attachment->id );
2773 }
2774 $duplicate_thread = WPSC_Thread::insert(
2775 array(
2776 'ticket' => $new_ticket->id,
2777 'customer' => $thread->customer->id,
2778 'type' => $thread->type,
2779 'body' => $thread->body,
2780 'attachments' => $attachments ? implode( '|', $attachments ) : '',
2781 'ip_address' => $thread->ip_address,
2782 'source' => $thread->source,
2783 'os' => $thread->os,
2784 'browser' => $thread->browser,
2785 'date_created' => $thread->date_created->format( 'Y-m-d H:i:s' ),
2786 'date_updated' => $thread->date_updated->format( 'Y-m-d H:i:s' ),
2787 )
2788 );
2789
2790 foreach ( $attachments as $id ) :
2791 $attachment = new WPSC_Attachment( $id );
2792 $attachment->source = $duplicate_thread->type;
2793 $attachment->source_id = $duplicate_thread->id;
2794 $attachment->ticket_id = $new_ticket->id;
2795 $attachment->save();
2796 endforeach;
2797
2798 // duplicate description / body attachments.
2799 $body_attachments = array();
2800 $attachment_of_body = WPSC_Attachment::find(
2801 array(
2802 'items_per_page' => 0,
2803 'meta_query' => array(
2804 'relation' => 'AND',
2805 array(
2806 'slug' => 'source',
2807 'compare' => '=',
2808 'val' => 'img_editor',
2809 ),
2810 array(
2811 'slug' => 'source_id',
2812 'compare' => '=',
2813 'val' => $thread->id,
2814 ),
2815 ),
2816 )
2817 )['results'];
2818 foreach ( $attachment_of_body as $img ) {
2819 $body_attachments[ $img->id ] = self::get_duplicate_attachment_id( $img->id );
2820 }
2821 foreach ( $body_attachments as $old_id => $id ) :
2822 $attachment = new WPSC_Attachment( $id );
2823 $attachment->source = 'img_editor';
2824 $attachment->source_id = $duplicate_thread->id;
2825 $attachment->ticket_id = $new_ticket->id;
2826 $attachment->save();
2827
2828 $old_url = home_url( '/' ) . '?wpsc_attachment=' . $old_id;
2829 $new_url = home_url( '/' ) . '?wpsc_attachment=' . $id;
2830 $duplicate_thread->body = str_replace( $old_url, $new_url, $duplicate_thread->body );
2831 endforeach;
2832
2833 $duplicate_thread->save();
2834
2835 do_action( 'wpsc_create_new_ticket', $new_ticket );
2836
2837 wp_send_json( array( 'ticket_id' => $new_ticket->id ) );
2838 }
2839
2840 /**
2841 * Copy old file into new file
2842 *
2843 * @param integer $attachment_id - attachment id.
2844 * @return new attachment id
2845 */
2846 public static function get_duplicate_attachment_id( $attachment_id ) {
2847
2848 $attachment = new WPSC_Attachment( $attachment_id );
2849
2850 // Database insert array init.
2851 $data = array( 'name' => $attachment->name );
2852
2853 $filename = explode( '.', $attachment->name );
2854 $extension = strtolower( $filename[ count( $filename ) - 1 ] );
2855 unset( $filename[ count( $filename ) - 1 ] );
2856 $filename = implode( '.', $filename );
2857 $filename = str_replace( ' ', '_', $filename );
2858 $filename = str_replace( ',', '_', $filename );
2859 $filename = time() . '_' . preg_replace( '/[^A-Za-z0-9\-]/', '', $filename );
2860
2861 // Check for image type.
2862 $img_extensions = array( 'png', 'jpeg', 'jpg', 'bmp', 'pdf', 'gif' );
2863 if ( ! in_array( $extension, $img_extensions ) ) {
2864 $data['is_image'] = 0;
2865 } else {
2866 $data['is_image'] = 1;
2867 }
2868
2869 // Create file path.
2870 $today = new DateTime();
2871 $upload_dir = wp_upload_dir();
2872 $filepath = $upload_dir['basedir'] . '/wpsc/' . $today->format( 'Y' );
2873 if ( ! file_exists( $filepath ) ) {
2874 mkdir( $filepath, 0755, true );
2875 }
2876 $filepath .= '/' . $today->format( 'm' );
2877 if ( ! file_exists( $filepath ) ) {
2878 mkdir( $filepath, 0755, true );
2879 }
2880 $filepath .= '/' . $filename . '.' . $extension;
2881
2882 $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename . '.' . $extension;
2883 $data['file_path'] = $filepath_short;
2884
2885 // Create time.
2886 $data['date_created'] = $today->format( 'Y-m-d H:i:s' );
2887
2888 // copy to path.
2889 copy( $upload_dir['basedir'] . $attachment->file_path, $filepath );
2890 $data['is_active'] = 1;
2891 $new_attachement = WPSC_Attachment::insert( $data );
2892
2893 return $new_attachement->id;
2894 }
2895
2896 /**
2897 * Get edit thread
2898 *
2899 * @return void
2900 */
2901 public static function get_edit_thread() {
2902
2903 if ( check_ajax_referer( 'wpsc_it_get_edit_thread', '_ajax_nonce', false ) != 1 ) {
2904 wp_send_json_error( 'Unauthorized request!', 401 );
2905 }
2906
2907 if ( self::$is_restricted ) {
2908 wp_send_json_error( 'Unauthorized request!', 401 );
2909 }
2910
2911 self::load_current_ticket();
2912
2913 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
2914 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
2915 }
2916
2917 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
2918 if ( ! $thread_id ) {
2919 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
2920 }
2921
2922 $thread = new WPSC_Thread( $thread_id );
2923 if ( ! $thread->id ) {
2924 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
2925 }
2926
2927 if ( self::$ticket != $thread->ticket ) {
2928 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
2929 }
2930
2931 $current_user = WPSC_Current_User::$current_user;
2932
2933 $unique_id = uniqid( 'wpsc_' )
2934 ?>
2935
2936 <div class="wpsc-edit-thread">
2937 <form onsubmit="return false;" class="edit-thread">
2938 <textarea class="wpsc_textarea" id="<?php echo esc_attr( $unique_id ); ?>"><?php echo wp_kses_post( $thread->body ); ?></textarea>
2939 <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;">
2940 <div class="wpsc-it-editor-action-container">
2941 <div class="wpsc-edit-actions">
2942 <div class="wpsc-editor-actions">
2943 <?php
2944 if ( WPSC_Text_Editor::is_allow_attachments() ) :
2945 ?>
2946 <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>
2947 <?php
2948 endif;
2949 ?>
2950 </div>
2951 <?php
2952 if ( WPSC_Text_Editor::is_attachment_notice() && WPSC_Text_Editor::is_allow_attachments() ) :
2953 ?>
2954 <div class="wpsc-file-attachment-notice"><?php echo esc_attr( WPSC_Text_Editor::file_attachment_notice_text() ); ?></div>
2955 <?php
2956 endif;
2957 ?>
2958 <div class="<?php echo esc_attr( $unique_id ); ?> wpsc-editor-attachment-container">
2959 <?php
2960 foreach ( $thread->attachments as $attachment ) :
2961 ?>
2962 <div class="wpsc-editor-attachment upload-success">
2963 <div class="attachment-label"><?php echo esc_attr( $attachment->name ); ?></div>
2964 <div class="attachment-remove" onclick="wpsc_remove_attachment(this)">
2965 <?php WPSC_Icons::get( 'times' ); ?>
2966 </div>
2967 <input type="hidden" name="thread_attachments[]" value="<?php echo esc_attr( $attachment->id ); ?>">
2968 </div>
2969 <?php
2970 endforeach;
2971 ?>
2972 </div>
2973 </div>
2974 <div class="submit-container">
2975 <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>
2976 <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>
2977 </div>
2978 </div>
2979 <input type="hidden" name="action" value="wpsc_it_set_edit_thread"/>
2980 <input type="hidden" name="ticket_id" value="<?php echo esc_attr( self::$ticket->id ); ?>"/>
2981 <input type="hidden" name="thread_id" value="<?php echo esc_attr( $thread->id ); ?>"/>
2982 <input type="hidden" name="_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'wpsc_it_set_edit_thread' ) ); ?>">
2983 </form>
2984 </div>
2985 <script><?php WPSC_Text_Editor::print_editor_init_scripts( $unique_id, $unique_id . '_body' ); ?></script>
2986 <?php
2987 wp_die();
2988 }
2989
2990 /**
2991 * Set edit thread
2992 *
2993 * @return void
2994 */
2995 public static function set_edit_thread() {
2996
2997 if ( check_ajax_referer( 'wpsc_it_set_edit_thread', '_ajax_nonce', false ) != 1 ) {
2998 wp_send_json_error( 'Unauthorized request!', 401 );
2999 }
3000
3001 self::load_current_ticket();
3002 self::load_actions();
3003
3004 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
3005 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3006 }
3007
3008 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3009 if ( ! $thread_id ) {
3010 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3011 }
3012
3013 $thread = new WPSC_Thread( $thread_id );
3014 if ( ! $thread->id ) {
3015 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3016 }
3017
3018 if ( self::$ticket != $thread->ticket ) {
3019 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
3020 }
3021
3022 $thread_content = isset( $_POST['thread_content'] ) ? wp_kses_post( wp_unslash( $_POST['thread_content'] ) ) : '';
3023 if ( ! $thread_content ) {
3024 wp_send_json_error( new WP_Error( '007', 'Bad request!' ), 400 );
3025 }
3026
3027 // add ticket auth url to tinymce attachments.
3028 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d+)(?!&auth_code=)([^"]*)/', $thread_content, $matches ) ) {
3029 foreach ( $matches[0] as $url ) {
3030 if ( strpos( $url, 'auth_code=' ) === false ) {
3031 $new_url = add_query_arg( 'auth_code', self::$ticket->auth_code, $url );
3032 $thread_content = str_replace( $url, $new_url, $thread_content );
3033 }
3034 }
3035 }
3036
3037 $thread_attachments = isset( $_POST['thread_attachments'] ) ? array_filter( array_map( 'intval', $_POST['thread_attachments'] ) ) : array();
3038 $validated_attachments = array();
3039 foreach ( $thread_attachments as $id ) {
3040
3041 $attachment = new WPSC_Attachment( $id );
3042 if (
3043 ! $attachment->id ||
3044 ( $attachment->ticket_id && $attachment->ticket_id != self::$ticket->id ) ||
3045 ( $attachment->source_id && $attachment->source_id != $thread->id )
3046 ) {
3047 continue;
3048 }
3049
3050 // Already part of this ticket, otherwise only the uploader may
3051 // bind a not-yet-bound attachment to it.
3052 if ( ! $attachment->ticket_id && ! WPSC_Attachment::can_claim( $attachment ) ) {
3053 continue;
3054 }
3055
3056 $validated_attachments[] = $id;
3057 }
3058 $thread_attachments = $validated_attachments;
3059
3060 $new_attachments = array();
3061 foreach ( $thread_attachments as $id ) {
3062 $attachment = new WPSC_Attachment( $id );
3063 $attachment->is_active = 1;
3064 $attachment->ticket_id = self::$ticket->id;
3065 $attachment->source = $thread->type;
3066 $attachment->source_id = $thread->id;
3067 $attachment->save();
3068 $new_attachments[] = $attachment;
3069 }
3070
3071 $is_modify = false;
3072
3073 // Set new thread.
3074 $prev_content = $thread->body;
3075 if ( $prev_content != $thread_content ) {
3076
3077 $thread->body = $thread_content;
3078
3079 // tinymce img attachments.
3080 if ( preg_match_all( '/' . preg_quote( home_url( '/' ), '/' ) . '\?wpsc_attachment=(\d*)(&auth_code=[^&\s]*)?/', $thread_content, $matches ) ) {
3081
3082 foreach ( $matches[1] as $id ) {
3083
3084 $attachment = new WPSC_Attachment( $id );
3085 // is_active is not an ownership test: a deleted thread
3086 // clears it while leaving ticket_id in place. Bind only
3087 // what already belongs to this ticket, or what the
3088 // requester uploaded and nothing owns yet.
3089 if ( ! $attachment->id ||
3090 ( $attachment->ticket_id && $attachment->ticket_id != self::$ticket->id ) ||
3091 ( ! $attachment->ticket_id && ! WPSC_Attachment::can_claim( $attachment ) ) ) {
3092 continue;
3093 }
3094
3095 $attachment->is_active = 1;
3096 $attachment->source_id = $thread->id;
3097 $attachment->ticket_id = self::$ticket->id;
3098 $attachment->source = 'img_editor';
3099 $attachment->save();
3100 }
3101 }
3102
3103 $is_modify = true;
3104 }
3105
3106 // Attachments.
3107 $prev_attachments = array_map( fn( $attachment ) => $attachment->id, $thread->attachments );
3108 if ( array_diff( $prev_attachments, $thread_attachments ) || array_diff( $thread_attachments, $prev_attachments ) ) {
3109 $thread->attachments = $new_attachments;
3110 $is_modify = true;
3111 }
3112
3113 // Return if there is no change.
3114 if ( ! $is_modify ) {
3115 self::print_thread( $thread );
3116 wp_die();
3117 }
3118
3119 // Set log for this change.
3120 WPSC_Log::insert(
3121 array(
3122 'type' => 'thread',
3123 'ref_id' => $thread->id,
3124 'modified_by' => WPSC_Current_User::$current_user->customer->id,
3125 'body' => wp_json_encode(
3126 array(
3127 'type' => 'modify',
3128 'prev' => array(
3129 'content' => str_replace( PHP_EOL, '', $prev_content ),
3130 'attachments' => $prev_attachments,
3131 ),
3132 'new' => array(
3133 'content' => str_replace( PHP_EOL, '', $thread_content ),
3134 'attachments' => $thread_attachments,
3135 ),
3136 )
3137 ),
3138 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
3139 )
3140 );
3141
3142 // Save changes to thread and print updated thread HTML.
3143 $thread->save();
3144 self::print_thread( $thread );
3145 wp_die();
3146 }
3147
3148 /**
3149 * Print HTML for a thread
3150 *
3151 * @return void
3152 */
3153 public static function get_thread_html() {
3154
3155 if ( check_ajax_referer( 'wpsc_it_get_thread', '_ajax_nonce', false ) != 1 ) {
3156 wp_send_json_error( 'Unauthorized request!', 401 );
3157 }
3158
3159 self::load_current_ticket();
3160 self::load_actions();
3161
3162 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'eth' ) ) ) {
3163 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3164 }
3165
3166 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3167 if ( ! $thread_id ) {
3168 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3169 }
3170
3171 $thread = new WPSC_Thread( $thread_id );
3172 if ( ! $thread->id ) {
3173 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3174 }
3175
3176 self::print_thread( $thread );
3177 wp_die();
3178 }
3179
3180 /**
3181 * Delete thread
3182 */
3183 public static function delete_thread() {
3184
3185 if ( check_ajax_referer( 'wpsc_it_thread_delete', '_ajax_nonce', false ) != 1 ) {
3186 wp_send_json_error( 'Unauthorized request!', 401 );
3187 }
3188
3189 if ( self::$is_restricted ) {
3190 wp_send_json_error( 'Unauthorized request!', 401 );
3191 }
3192
3193 self::load_current_ticket();
3194 self::load_actions();
3195
3196 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) ) ) {
3197 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3198 }
3199
3200 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3201 if ( ! $thread_id ) {
3202 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3203 }
3204
3205 $thread = new WPSC_Thread( $thread_id );
3206 if ( ! $thread->id ) {
3207 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3208 }
3209
3210 if ( self::$ticket != $thread->ticket ) {
3211 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
3212 }
3213
3214 $thread->is_active = 0;
3215 $thread->save();
3216
3217 // Set log for this change.
3218 WPSC_Log::insert(
3219 array(
3220 'type' => 'thread',
3221 'ref_id' => $thread->id,
3222 'modified_by' => WPSC_Current_User::$current_user->customer->id,
3223 'body' => wp_json_encode(
3224 array(
3225 'type' => 'delete',
3226 )
3227 ),
3228 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
3229 )
3230 );
3231
3232 self::print_thread( $thread );
3233 wp_die();
3234 }
3235
3236 /**
3237 * View thread log
3238 */
3239 public static function view_thread_log() {
3240
3241 if ( check_ajax_referer( 'wpsc_it_view_thread_log', '_ajax_nonce', false ) != 1 ) {
3242 wp_send_json_error( 'Unauthorized request!', 401 );
3243 }
3244
3245 self::load_current_ticket();
3246
3247 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
3248 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3249 }
3250
3251 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3252 if ( ! $thread_id ) {
3253 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3254 }
3255
3256 $thread = new WPSC_Thread( $thread_id );
3257 if ( ! $thread->id ) {
3258 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3259 }
3260
3261 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3262 wp_send_json_error( new WP_Error( '006', 'Unauthorized!' ), 401 );
3263 }
3264
3265 $log_id = isset( $_POST['log_id'] ) ? intval( $_POST['log_id'] ) : 0;
3266 if ( ! $log_id ) {
3267 wp_send_json_error( new WP_Error( '007', 'Bad request!' ), 400 );
3268 }
3269
3270 $log = new WPSC_Log( $log_id );
3271 if ( ! $log->id ) {
3272 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3273 }
3274
3275 $body = json_decode( $log->body );
3276 $title = esc_attr__( 'Thread log', 'supportcandy' );
3277
3278 ob_start();
3279 ?>
3280 <div class="wpsc-modal-thread-log">
3281
3282 <div class="wpsc-log-diff">
3283
3284 <div class="lhs">
3285 <div>
3286 <div class="thread-text" style="margin-bottom:0;">
3287 <?php echo wp_kses_post( $body->prev->content ); ?>
3288 </div>
3289 <?php
3290 if ( $body->prev->attachments ) :
3291 ?>
3292 <div class="wpsc-thread-attachments">
3293 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3294 <?php
3295 foreach ( $body->prev->attachments as $id ) :
3296 $attachment = new WPSC_Attachment( $id );
3297 if ( ! $attachment->id ) {
3298 continue;
3299 }
3300 ?>
3301 <div class="wpsc-attachment-item">
3302 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span>
3303 </div>
3304 <?php
3305 endforeach;
3306 ?>
3307 </div>
3308 <?php
3309 endif;
3310 ?>
3311 </div>
3312 </div>
3313
3314 <div class="transform-icon">
3315 <?php
3316 if ( is_rtl() ) {
3317 WPSC_Icons::get( 'arrow-left' );
3318 } else {
3319 WPSC_Icons::get( 'arrow-right' );
3320 }
3321 ?>
3322 </div>
3323
3324 <div class="rhs">
3325 <div>
3326 <div class="thread-text" style="margin-bottom:0;">
3327 <?php echo wp_kses_post( $body->new->content ); ?>
3328 </div>
3329 <?php
3330 if ( $body->new->attachments ) :
3331 ?>
3332 <div class="wpsc-thread-attachments">
3333 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3334 <?php
3335 foreach ( $body->new->attachments as $id ) :
3336 $attachment = new WPSC_Attachment( $id );
3337 if ( ! $attachment->id ) {
3338 continue;
3339 }
3340 ?>
3341 <div class="wpsc-attachment-item">
3342 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span>
3343 </div>
3344 <?php
3345 endforeach;
3346 ?>
3347 </div>
3348 <?php
3349 endif;
3350 ?>
3351 </div>
3352 </div>
3353
3354 </div>
3355
3356 </div>
3357 <?php
3358 $body = ob_get_clean();
3359
3360 ob_start();
3361 ?>
3362 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
3363 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
3364 </button>
3365 <?php
3366 $footer = ob_get_clean();
3367
3368 $response = array(
3369 'title' => $title,
3370 'body' => $body,
3371 'footer' => $footer,
3372 );
3373 wp_send_json( $response );
3374 }
3375
3376 /**
3377 * View deleted thread
3378 *
3379 * @return void
3380 */
3381 public static function view_deleted_thread() {
3382
3383 if ( check_ajax_referer( 'wpsc_it_view_deleted_thread', '_ajax_nonce', false ) != 1 ) {
3384 wp_send_json_error( 'Unauthorized request!', 401 );
3385 }
3386
3387 self::load_current_ticket();
3388
3389 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) ) {
3390 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3391 }
3392
3393 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3394 if ( ! $thread_id ) {
3395 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3396 }
3397
3398 $thread = new WPSC_Thread( $thread_id );
3399 if ( ! $thread->id ) {
3400 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3401 }
3402
3403 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3404 wp_send_json_error( new WP_Error( '006', 'Unauthorized!' ), 401 );
3405 }
3406
3407 if ( self::$ticket != $thread->ticket ) {
3408 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
3409 }
3410
3411 $title = $thread->customer->name;
3412
3413 ob_start();
3414 ?>
3415 <div class="wpsc-modal-thread-log">
3416 <div class="thread-text"><?php echo wp_kses_post( $thread->body ); ?></div>
3417 <?php
3418 if ( $thread->attachments ) :
3419 ?>
3420 <div class="wpsc-thread-attachments">
3421 <div class="wpsc-attachment-header"><?php esc_attr_e( 'Attachments:', 'supportcandy' ); ?></div>
3422 <?php
3423 foreach ( $thread->attachments as $attachment ) :
3424 ?>
3425 <div class="wpsc-attachment-item">
3426 <?php
3427 $download_url = home_url( '/' ) . '?wpsc_attachment=' . $attachment->id;
3428 ?>
3429 <a class="wpsc-link" href="<?php echo esc_attr( $download_url ); ?>" target="_blank">
3430 <span class="wpsc-attachment-name"><?php echo esc_attr( $attachment->name ); ?></span></a>
3431 </div>
3432 <?php
3433 endforeach;
3434 ?>
3435 </div>
3436 <?php
3437 endif;
3438 ?>
3439 </div>
3440 <?php
3441 $body = ob_get_clean();
3442
3443 ob_start();
3444 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) ) :
3445 ?>
3446 <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' ) ) . '\''; ?>);">
3447 <?php esc_attr_e( 'Restore', 'supportcandy' ); ?>
3448 </button>
3449 <?php
3450 endif;
3451 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'dth' ) && WPSC_Functions::is_site_admin() ) :
3452 ?>
3453 <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' ) ) . '\''; ?>);">
3454 <?php esc_attr_e( 'Delete Permanently', 'supportcandy' ); ?>
3455 </button>
3456 <?php
3457 endif;
3458 ?>
3459 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
3460 <?php esc_attr_e( 'Close', 'supportcandy' ); ?>
3461 </button>
3462 <?php
3463 $footer = ob_get_clean();
3464
3465 $response = array(
3466 'title' => $title,
3467 'body' => $body,
3468 'footer' => $footer,
3469 );
3470 wp_send_json( $response );
3471 }
3472
3473 /**
3474 * Restore thread
3475 *
3476 * @return void
3477 */
3478 public static function restore_thread() {
3479
3480 if ( check_ajax_referer( 'wpsc_it_restore_thread', '_ajax_nonce', false ) != 1 ) {
3481 wp_send_json_error( 'Unauthorized request!', 401 );
3482 }
3483
3484 self::load_current_ticket();
3485 self::load_actions();
3486
3487 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && self::has_ticket_cap( 'dth' ) ) ) {
3488 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3489 }
3490
3491 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3492 if ( ! $thread_id ) {
3493 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3494 }
3495
3496 $thread = new WPSC_Thread( $thread_id );
3497 if ( ! $thread->id ) {
3498 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3499 }
3500
3501 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3502 wp_send_json_error( new WP_Error( '007', 'Unauthorized!' ), 401 );
3503 }
3504
3505 if ( self::$ticket != $thread->ticket ) {
3506 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3507 }
3508
3509 $thread->is_active = 1;
3510 $thread->save();
3511
3512 // Set log for this change.
3513 WPSC_Log::insert(
3514 array(
3515 'type' => 'thread',
3516 'ref_id' => $thread->id,
3517 'modified_by' => WPSC_Current_User::$current_user->customer->id,
3518 'body' => wp_json_encode(
3519 array(
3520 'type' => 'restore',
3521 )
3522 ),
3523 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
3524 )
3525 );
3526
3527 self::print_thread( $thread );
3528 wp_die();
3529 }
3530
3531 /**
3532 * Delete thread permanently
3533 */
3534 public static function thread_delete_permanently() {
3535
3536 if ( check_ajax_referer( 'wpsc_it_thread_delete_permanently', '_ajax_nonce', false ) != 1 ) {
3537 wp_send_json_error( 'Unauthorized request!', 401 );
3538 }
3539
3540 self::load_current_ticket();
3541
3542 if ( ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) && self::has_ticket_cap( 'dth' ) && WPSC_Functions::is_site_admin() ) ) {
3543 wp_send_json_error( new WP_Error( '004', 'Unauthorized!' ), 401 );
3544 }
3545
3546 $thread_id = isset( $_POST['thread_id'] ) ? intval( $_POST['thread_id'] ) : 0;
3547 if ( ! $thread_id ) {
3548 wp_send_json_error( new WP_Error( '005', 'Bad request!' ), 400 );
3549 }
3550
3551 $thread = new WPSC_Thread( $thread_id );
3552 if ( ! $thread->id ) {
3553 wp_send_json_error( new WP_Error( '006', 'Bad request!' ), 400 );
3554 }
3555
3556 if ( $thread->type == 'note' && ! ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) ) {
3557 wp_send_json_error( new WP_Error( '007', 'Unauthorized!' ), 401 );
3558 }
3559
3560 if ( self::$ticket != $thread->ticket ) {
3561 wp_send_json_error( new WP_Error( '008', 'Bad request!' ), 400 );
3562 }
3563
3564 WPSC_Thread::destroy( $thread );
3565 wp_die();
3566 }
3567
3568 /**
3569 * Load older threads
3570 *
3571 * @return void
3572 */
3573 public static function load_older_threads() {
3574
3575 self::load_current_ticket();
3576 self::load_actions();
3577
3578 $last_thread = isset( $_POST['last_thread'] ) ? intval( $_POST['last_thread'] ) : 0; // phpcs:ignore
3579 if ( ! $last_thread ) {
3580 wp_send_json_error( new WP_Error( '004', 'Bad request!' ), 401 );
3581 }
3582
3583 $gs = get_option( 'wpsc-gs-general' );
3584 $filters = array(
3585 'meta_query' => array(
3586 'relation' => 'AND',
3587 array(
3588 'slug' => 'ticket',
3589 'compare' => '=',
3590 'val' => self::$ticket->id,
3591 ),
3592 array(
3593 'slug' => 'id',
3594 'compare' => '<',
3595 'val' => $last_thread,
3596 ),
3597 ),
3598 'orderby' => 'id',
3599 'order' => 'DESC',
3600 );
3601
3602 $thread_types = array(
3603 'slug' => 'type',
3604 'compare' => 'IN',
3605 'val' => array( 'report', 'reply' ),
3606 );
3607
3608 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'pn' ) ) {
3609 $thread_types['val'][] = 'note';
3610 }
3611 if ( self::$view_profile == 'agent' && self::has_ticket_cap( 'vl' ) ) {
3612 $thread_types['val'][] = 'log';
3613 }
3614
3615 $filters['meta_query'][] = $thread_types;
3616
3617 $filters = apply_filters( 'wpsc_it_load_older_threads_filters', $filters );
3618
3619 $response = WPSC_Thread::find( $filters );
3620 $last_id = $response['results'][ count( $response['results'] ) - 1 ]->id;
3621
3622 ob_start();
3623 if ( $gs['reply-form-position'] == 'top' ) {
3624 foreach ( $response['results'] as $thread ) {
3625 if ( $thread->type == 'log' ) {
3626 self::print_log( $thread );
3627 } else {
3628 self::print_thread( $thread );
3629 }
3630 }
3631 } else {
3632 for ( $i = count( $response['results'] ) - 1; $i >= 0; $i-- ) {
3633 $thread = $response['results'][ $i ];
3634 if ( $thread->type == 'log' ) {
3635 self::print_log( $thread );
3636 } else {
3637 self::print_thread( $thread );
3638 }
3639 }
3640 }
3641 $threads = ob_get_clean();
3642
3643 $html_response = array(
3644 'last_thread' => $last_id,
3645 'has_next_page' => $response['has_next_page'],
3646 'threads' => $threads,
3647 );
3648
3649 wp_send_json( $html_response );
3650 wp_die();
3651 }
3652 }
3653 endif;
3654
3655 WPSC_Individual_Ticket::init();
3656