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 / models / class-wpsc-attachment.php

class-wpsc-attachment.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.5.4, at includes/models/class-wpsc-attachment.php

1,590 lines 51.3 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_Attachment' ) ) :
7
8 final class WPSC_Attachment {
9
10 /**
11 * Object data in key => val pair.
12 *
13 * @var array
14 */
15 private $data = array();
16
17 /**
18 * Set whether or not current object properties modified
19 *
20 * @var boolean
21 */
22 private $is_modified = false;
23
24 /**
25 * Schema for this model
26 *
27 * @var array
28 */
29 public static $schema = array();
30
31 /**
32 * Prevent fields to modify
33 *
34 * @var array
35 */
36 public static $prevent_modify = array();
37
38 /**
39 * Catche for search results for ticket list of attachments.
40 *
41 * @var array
42 */
43 private static $tl_search_items;
44
45 /**
46 * Customer id of the visitor this request actually belongs to, captured
47 * on 'init' before anything can reassign WPSC_Current_User::$current_user
48 * (change_current_user() is called from a number of places, one of them
49 * driven by request input). Used to record and check attachment ownership.
50 *
51 * @var int
52 */
53 private static $session_customer_id = 0;
54
55 /**
56 * Initialize this class
57 *
58 * @return void
59 */
60 public static function init() {
61
62 // Apply schema for this model.
63 add_action( 'init', array( __CLASS__, 'apply_schema' ), 2 );
64
65 // Capture whose session this request is, before anything reassigns it.
66 add_action( 'init', array( __CLASS__, 'snapshot_session_customer' ), 11 );
67
68 // Get object of this class.
69 add_filter( 'wpsc_load_ref_classes', array( __CLASS__, 'load_ref_class' ) );
70
71 // File upload.
72 add_action( 'wp_ajax_wpsc_file_upload', array( __CLASS__, 'file_upload' ) );
73 add_action( 'wp_ajax_nopriv_wpsc_file_upload', array( __CLASS__, 'file_upload' ) );
74
75 // Custom image in tinymce.
76 add_action( 'wp_ajax_wpsc_add_custom_image_tinymce', array( __CLASS__, 'add_custom_image_tinymce' ) );
77 add_action( 'wp_ajax_nopriv_wpsc_add_custom_image_tinymce', array( __CLASS__, 'add_custom_image_tinymce' ) );
78 add_action( 'wp_ajax_wpsc_edit_custom_image_tinymce', array( __CLASS__, 'edit_custom_image_tinymce' ) );
79 add_action( 'wp_ajax_nopriv_wpsc_edit_custom_image_tinymce', array( __CLASS__, 'edit_custom_image_tinymce' ) );
80
81 // Check download file.
82 add_action( 'init', array( __CLASS__, 'check_download_file' ), 100 );
83
84 // Upload tinymce image.
85 add_action( 'wp_ajax_wpsc_tinymce_upload_file', array( __CLASS__, 'tinymce_upload_file' ) );
86 add_action( 'wp_ajax_nopriv_wpsc_tinymce_upload_file', array( __CLASS__, 'tinymce_upload_file' ) );
87
88 // Attachment garbage collector.
89 add_action( 'wpsc_attach_garbage_collector', array( __CLASS__, 'garbage_collector' ) );
90 }
91
92 /**
93 * Change file upload path
94 *
95 * @param array $param - file upload array.
96 * @return array
97 */
98 public static function wpsc_upload_dir( $param ) {
99
100 $today = new DateTime();
101 $file_path = $param['basedir'] . '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' );
102 if ( ! file_exists( $file_path ) ) {
103 mkdir( $file_path, 0755, true );
104 }
105
106 $param['path'] = $file_path;
107
108 return $param;
109 }
110
111 /**
112 * Custom upload mime types
113 *
114 * @param array $existing_mimes - file upload array.
115 * @return array
116 */
117 public static function wpsc_custom_upload_mimes( $existing_mimes ) {
118
119 $settings = get_option( 'wpsc-gs-file-attachments', array() );
120
121 // Merge allowed-file-ext-mimes first.
122 if ( isset( $settings['allowed-file-ext-mimes'] ) ) {
123 $existing_mimes = array_merge( $existing_mimes, $settings['allowed-file-ext-mimes'] );
124 }
125
126 // Then apply mime-exceptions to give them priority.
127 if ( isset( $settings['mime-exceptions'] ) && is_array( $settings['mime-exceptions'] ) ) {
128 foreach ( $settings['mime-exceptions'] as $type ) {
129 $type_array = explode( ':', $type );
130 if ( count( $type_array ) === 2 ) {
131 $existing_mimes[ $type_array[0] ] = $type_array[1];
132 }
133 }
134 }
135
136 return $existing_mimes;
137 }
138
139 /**
140 * Apply schema for this model
141 *
142 * @return void
143 */
144 public static function apply_schema() {
145
146 $schema = array(
147 'id' => array(
148 'has_ref' => false,
149 'ref_class' => '',
150 'has_multiple_val' => false,
151 ),
152 'name' => array(
153 'has_ref' => false,
154 'ref_class' => '',
155 'has_multiple_val' => false,
156 ),
157 'file_path' => array(
158 'has_ref' => false,
159 'ref_class' => '',
160 'has_multiple_val' => false,
161 ),
162 'is_image' => array(
163 'has_ref' => false,
164 'ref_class' => '',
165 'has_multiple_val' => false,
166 ),
167 'is_active' => array(
168 'has_ref' => false,
169 'ref_class' => '',
170 'has_multiple_val' => false,
171 ),
172 'is_uploaded' => array(
173 'has_ref' => false,
174 'ref_class' => '',
175 'has_multiple_val' => false,
176 ),
177 'date_created' => array(
178 'has_ref' => true,
179 'ref_class' => 'datetime',
180 'has_multiple_val' => false,
181 ),
182 'source' => array(
183 'has_ref' => false,
184 'ref_class' => '',
185 'has_multiple_val' => false,
186 ),
187 'source_id' => array(
188 'has_ref' => false,
189 'ref_class' => '',
190 'has_multiple_val' => false,
191 ),
192 'ticket_id' => array(
193 'has_ref' => false,
194 'ref_class' => '',
195 'has_multiple_val' => false,
196 ),
197 'customer_id' => array(
198 'has_ref' => false,
199 'ref_class' => '',
200 'has_multiple_val' => false,
201 ),
202 'uploaded_by' => array(
203 'has_ref' => false,
204 'ref_class' => '',
205 'has_multiple_val' => false,
206 ),
207 );
208 self::$schema = apply_filters( 'wpsc_attachment_schema', $schema );
209
210 // Prevent modify.
211 $prevent_modify = array( 'id' );
212 self::$prevent_modify = apply_filters( 'wpsc_attachment_prevent_modify', $prevent_modify );
213 }
214
215 /**
216 * Model constructor
217 *
218 * @param int $id - Optional. Data record id to retrive object for.
219 */
220 public function __construct( $id = 0 ) {
221
222 global $wpdb;
223
224 $id = intval( $id );
225
226 if ( $id > 0 ) {
227
228 $attachment = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}psmsc_attachments WHERE id = " . $id, ARRAY_A );
229 if ( ! is_array( $attachment ) ) {
230 return;
231 }
232
233 foreach ( $attachment as $key => $val ) {
234 $this->data[ $key ] = $val !== null ? $val : '';
235 }
236 }
237 }
238
239 /**
240 * Convert object into an array
241 *
242 * @return array
243 */
244 public function to_array() {
245
246 return $this->data;
247 }
248
249 /**
250 * Magic get function to use with object arrow function
251 *
252 * @param string $var_name - variable name.
253 * @return mixed
254 */
255 public function __get( $var_name ) {
256
257 if ( ! isset( $this->data[ $var_name ] ) ||
258 $this->data[ $var_name ] == null ||
259 $this->data[ $var_name ] == ''
260 ) {
261 return self::$schema[ $var_name ]['has_multiple_val'] ? array() : '';
262 }
263
264 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
265
266 $response = array();
267 $values = $this->data[ $var_name ] ? explode( '|', $this->data[ $var_name ] ) : array();
268 foreach ( $values as $val ) {
269 $response[] = self::$schema[ $var_name ]['has_ref'] ?
270 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $val ) :
271 $val;
272 }
273 return $response;
274
275 } else {
276
277 return self::$schema[ $var_name ]['has_ref'] && $this->data[ $var_name ] ?
278 WPSC_Functions::get_object( self::$schema[ $var_name ]['ref_class'], $this->data[ $var_name ] ) :
279 $this->data[ $var_name ];
280 }
281 }
282
283 /**
284 * Magic function to use setting object field with arrow function
285 *
286 * @param string $var_name - (Required) property slug.
287 * @param mixed $value - (Required) value to set for a property.
288 * @return void
289 */
290 public function __set( $var_name, $value ) {
291
292 if (
293 ! isset( $this->data[ $var_name ] ) ||
294 in_array( $var_name, self::$prevent_modify )
295 ) {
296 return;
297 }
298
299 $data_val = '';
300 if ( self::$schema[ $var_name ]['has_multiple_val'] ) {
301
302 $data_vals = array_map(
303 fn( $val ) => is_object( $val ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $val ) : $val,
304 $value
305 );
306
307 $data_val = $data_vals ? implode( '|', $data_vals ) : '';
308
309 } else {
310
311 $data_val = is_object( $value ) ? WPSC_Functions::set_object( self::$schema[ $var_name ]['ref_class'], $value ) : $value;
312 }
313
314 if ( $this->data[ $var_name ] == $data_val ) {
315 return;
316 }
317
318 $this->data[ $var_name ] = $data_val;
319 $this->is_modified = true;
320 }
321
322 /**
323 * Save changes made
324 *
325 * @return boolean
326 */
327 public function save() {
328
329 global $wpdb;
330
331 if ( ! $this->is_modified ) {
332 return true;
333 }
334
335 $data = $this->data;
336 $success = true;
337
338 if ( ! isset( $data['id'] ) ) {
339
340 $at = self::insert( $data );
341 if ( $at ) {
342 $this->data = $at->data;
343 $success = true;
344 } else {
345 $success = false;
346 }
347 } else {
348
349 unset( $data['id'] );
350 $success = $wpdb->update(
351 $wpdb->prefix . 'psmsc_attachments',
352 $data,
353 array( 'id' => $this->data['id'] )
354 );
355 }
356 $this->is_modified = false;
357 return $success ? true : false;
358 }
359
360 /**
361 * Insert new attachment
362 *
363 * @param array $data - insert data.
364 * @return WPSC_Attachment
365 */
366 public static function insert( $data ) {
367
368 global $wpdb;
369
370 $data['is_active'] = isset( $data['is_active'] ) ? $data['is_active'] : 0;
371
372 $success = $wpdb->insert(
373 $wpdb->prefix . 'psmsc_attachments',
374 $data
375 );
376
377 if ( ! $success ) {
378 return false;
379 }
380
381 $attachment = new WPSC_Attachment( $wpdb->insert_id );
382 return $attachment;
383 }
384
385 /**
386 * Make it inactive so that garbage collector will delete files associated in
387 * background and then delete the record. This will improve its performance.
388 *
389 * @param WPSC_Attachment $attachment - attachment object.
390 * @return boolean
391 */
392 public static function destroy( $attachment ) {
393
394 $attachment->is_active = 0;
395 $attachment->save();
396 return true;
397 }
398
399 /**
400 * Set data to create new object using direct data. Used in find method
401 *
402 * @param array $data - data to set for object.
403 * @return void
404 */
405 private function set_data( $data ) {
406
407 foreach ( $data as $var_name => $val ) {
408 $this->data[ $var_name ] = $val !== null ? $val : '';
409 }
410 }
411
412 /**
413 * Find records based on given filters
414 *
415 * @param array $filter - array containing array items like search, where, orderby, order, page_no, items_per_page, etc.
416 * @param boolean $is_object - return data as array or object. Default object.
417 * @return mixed
418 */
419 public static function find( $filter = array(), $is_object = true ) {
420
421 global $wpdb;
422
423 $sql = 'SELECT * FROM ' . $wpdb->prefix . 'psmsc_attachments ';
424 $where = self::get_where( $filter );
425
426 $filter['items_per_page'] = isset( $filter['items_per_page'] ) ? $filter['items_per_page'] : 5;
427 $filter['page_no'] = isset( $filter['page_no'] ) ? $filter['page_no'] : 1;
428 $filter['orderby'] = isset( $filter['orderby'] ) ? $filter['orderby'] : 'id';
429 $filter['order'] = isset( $filter['order'] ) ? $filter['order'] : 'ASC';
430
431 $order = WPSC_Functions::parse_order( $filter );
432 $limit = WPSC_Functions::parse_limit( $filter );
433
434 $sql = $sql . $where . $order . $limit;
435 $results = $wpdb->get_results( $sql, ARRAY_A );
436
437 // total results.
438 $sql = 'SELECT count(id) FROM ' . $wpdb->prefix . 'psmsc_attachments ';
439 $total_items = $wpdb->get_var( $sql . $where );
440
441 $response = WPSC_Functions::parse_response( $results, $total_items, $filter );
442
443 // Return array.
444 if ( ! $is_object ) {
445 return $response;
446 }
447
448 // create and return array of objects.
449 $temp_results = array();
450 foreach ( $response['results'] as $attachment ) {
451
452 $ob = new WPSC_Attachment();
453 $data = array();
454 foreach ( $attachment as $key => $val ) {
455 $data[ $key ] = $val;
456 }
457 $ob->set_data( $data );
458 $temp_results[] = $ob;
459 }
460 $response['results'] = $temp_results;
461
462 return $response;
463 }
464
465 /**
466 * Get where for find method
467 *
468 * @param array $filter - user filter.
469 * @return array
470 */
471 private static function get_where( $filter ) {
472
473 $where = array( '1=1' );
474
475 // Set user defined filters.
476 $meta_query = isset( $filter['meta_query'] ) ? $filter['meta_query'] : array();
477 if ( $meta_query ) {
478 $where[] = WPSC_Functions::parse_user_filters( __CLASS__, $meta_query );
479 }
480
481 // Search.
482 $search = WPSC_Functions::get_filter_search_str( $filter );
483 if ( $search ) {
484 $search_query = array(
485 'CONVERT(name USING utf8) LIKE \'%' . $search . '%\'',
486 );
487 $search_query = apply_filters( 'wpsc_attachment_search_query', $search_query, $filter );
488 $where[] = '( ' . implode( ' OR ', $search_query ) . ' )';
489 }
490
491 return 'WHERE ' . implode( ' AND ', $where ) . ' ';
492 }
493
494 /**
495 * Load current class to reference classes
496 *
497 * @param array $classes - Associative array of class names indexed by its slug.
498 * @return array
499 */
500 public static function load_ref_class( $classes ) {
501
502 $classes['wpsc_attachment'] = array(
503 'class' => __CLASS__,
504 'save-key' => 'id',
505 );
506 return $classes;
507 }
508
509 /**
510 * Clear records and actual files for attachments that are not active for more than 24 hrs.
511 * This uses cron-job for background running to improve performace of load-time while deleting attachments.
512 *
513 * @return void
514 */
515 public static function garbage_collector() {
516
517 global $wpdb;
518 $d = ( new DateTime() )->sub( new DateInterval( 'P1D' ) );
519
520 $attachments = self::find(
521 array(
522 'items_per_page' => 20,
523 'meta_query' => array(
524 'relation' => 'AND',
525 array(
526 'slug' => 'date_created',
527 'compare' => '<',
528 'val' => $d->format( 'Y-m-d H:i:s' ),
529 ),
530 array(
531 'relation' => 'OR',
532 array(
533 'slug' => 'is_active',
534 'compare' => '=',
535 'val' => '0',
536 ),
537 array(
538 'slug' => 'source',
539 'compare' => '=',
540 'val' => 'img_editor_tmp',
541 ),
542 ),
543 ),
544 )
545 )['results'];
546 foreach ( $attachments as $attachment ) {
547
548 $upload_dir = wp_upload_dir();
549 $file_path = $upload_dir['basedir'] . $attachment->file_path;
550 if ( file_exists( $file_path ) ) {
551 wp_delete_file( $file_path );
552 }
553 $wpdb->query( "DELETE FROM {$wpdb->prefix}psmsc_attachments WHERE id=" . $attachment->id );
554 }
555 }
556
557 /**
558 * Ajax callback for attachment file upload
559 *
560 * @return void
561 */
562 public static function file_upload() {
563
564 if ( check_ajax_referer( 'wpsc_file_upload', '_ajax_nonce', false ) != 1 ) {
565 wp_send_json_error( 'Unauthorized request!', 401 );
566 }
567
568 if ( ! WPSC_Text_Editor::is_allow_attachments() ) {
569 wp_send_json_error( new WP_Error( '001', 'Unauthorized' ), 401 );
570 }
571
572 $recaptcha = get_option( 'wpsc-recaptcha-settings' );
573 if ( $recaptcha['captcha-provider'] === 'google-recaptcha' && $recaptcha['recaptcha-version'] == 3 && $recaptcha['recaptcha-site-key'] && $recaptcha['recaptcha-secret-key'] ) {
574 WPSC_MS_Recaptcha::validate( 'file_upload' );
575 }
576
577 $file = isset( $_FILES['wpscFileAttachment'] ) ? $_FILES['wpscFileAttachment'] : false; // phpcs:ignore
578 if ( ! $file ) {
579 wp_send_json_error( 'File not found!', 400 );
580 }
581
582 $file_settings = get_option( 'wpsc-gs-file-attachments' );
583 $original_name = sanitize_file_name( $file['name'] );
584 $filename = time() . '_' . sanitize_file_name( $file['name'] );
585 $extension = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
586 $today = new DateTime();
587 $upload_dir = wp_upload_dir();
588
589 // Allowed file extension.
590 $allowed_file_extensions = explode( ',', $file_settings['allowed-file-extensions'] );
591 $allowed_file_extensions = array_map( 'trim', $allowed_file_extensions );
592 $allowed_file_extensions = array_map( 'strtolower', $allowed_file_extensions );
593 if ( ! ( in_array( $extension, $allowed_file_extensions ) ) ) {
594 wp_send_json_error( 'File extension not allowed!', 400 );
595 }
596
597 // Allowed file size.
598 $allowed_file_size = intval( $file_settings['attachments-max-filesize'] ) * 1000000;
599 if ( ! ( isset( $file['size'] ) && $file['size'] <= $allowed_file_size ) ) {
600 wp_send_json_error( 'File size exceeds allowed limit!', 400 );
601 }
602
603 // Init attachment data.
604 $data = array(
605 'name' => $original_name,
606 'date_created' => $today->format( 'Y-m-d H:i:s' ),
607 );
608
609 // Check for image type. Add a ".txt" extension to non-image file to prevent executing uploaded files on server.
610 $img_extensions = array( 'png', 'jpeg', 'jpg', 'bmp', 'pdf', 'gif' );
611 if ( ! in_array( $extension, $img_extensions ) ) {
612 $data['is_image'] = 0;
613 } else {
614 $data['is_image'] = 1;
615 }
616
617 $ext = pathinfo( $filename, PATHINFO_EXTENSION );
618 $file_name = substr( $filename, 0, -( strlen( $ext ) + 1 ) );
619 $filename = $file_name . '.' . strtolower( $ext );
620 $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename;
621 $data['file_path'] = $filepath_short;
622
623 // Set up upload overrides.
624 $upload_overrides = array(
625 'test_form' => false,
626 );
627 $file['name'] = $filename;
628 add_filter( 'upload_dir', array( __CLASS__, 'wpsc_upload_dir' ) );
629 add_filter( 'upload_mimes', array( __CLASS__, 'wpsc_custom_upload_mimes' ) );
630 $uploaded_file = wp_handle_upload( $file, $upload_overrides );
631 // Insert record in database.
632 if ( $uploaded_file && empty( $uploaded_file['error'] ) ) {
633
634 $pos = strpos( $uploaded_file['file'], 'wpsc/' );
635 if ( $pos === false ) {
636 wp_send_json_error( 'Something went wrong!', 500 );
637 }
638 $new_path = substr( $uploaded_file['file'], $pos );
639 if ( $new_path[0] !== '/' ) {
640 $new_path = '/' . $new_path;
641 }
642 $data['file_path'] = $new_path;
643 $attachment = self::insert( $data );
644 if ( ! $attachment->id ) {
645 wp_send_json_error( 'Something went wrong!', 500 );
646 }
647
648 // Record who uploaded it, and hand them a per-attachment token,
649 // so that only they can later bind it to a ticket.
650 self::set_uploader( $attachment );
651
652 wp_send_json(
653 array(
654 'id' => $attachment->id,
655 'token' => wp_create_nonce( self::get_img_editor_tmp_nonce_action( $attachment->id ) ),
656 )
657 );
658
659 } else {
660
661 $error_message = isset( $uploaded_file['error'] ) ? $uploaded_file['error'] : 'Something went wrong!';
662 wp_send_json_error( $error_message, 500 );
663 }
664 }
665
666 /**
667 * Returns attachment ids to be used for search in ticket list
668 *
669 * @param string $search - search string.
670 * @return array
671 */
672 public static function get_tl_search_string( $search ) {
673
674 $search_items = array();
675 if ( is_array( self::$tl_search_items ) ) {
676 $search_items = self::$tl_search_items;
677 } else {
678 $attachments = self::find(
679 array(
680 'search' => $search,
681 'items_per_page' => 500,
682 'meta_query' => array(
683 'relation' => 'AND',
684 array(
685 'slug' => 'is_active',
686 'compare' => '=',
687 'val' => '1',
688 ),
689 ),
690 )
691 )['results'];
692 if ( $attachments ) {
693 foreach ( $attachments as $attachment ) {
694 $search_items[] = $attachment->id;
695 }
696 }
697 self::$tl_search_items = $search_items;
698 }
699
700 return $search_items;
701 }
702
703 /**
704 * Custom tinymce image popup
705 *
706 * @return void
707 */
708 public static function add_custom_image_tinymce() {
709
710 if ( check_ajax_referer( 'wpsc_add_custom_image_tinymce', '_ajax_nonce', false ) != 1 ) {
711 wp_send_json_error( 'Unauthorized request!', 401 );
712 }
713
714 $title = esc_attr__( 'Insert/edit image', 'supportcandy' );
715
716 $editor_id = isset( $_POST['editor_id'] ) ? sanitize_text_field( wp_unslash( $_POST['editor_id'] ) ) : '';
717 if ( ! $editor_id ) {
718 wp_send_json_error( 'Something went wrong!', 400 );
719 }
720
721 ob_start();?>
722 <form action="#" onsubmit="return false;" class="wpsc-insert-edit-image">
723 <div class="wpsc-input-group">
724 <div class="label-container">
725 <label for=""><?php esc_attr_e( 'Source', 'supportcandy' ); ?></label>
726 <span class="required-char">*</span>
727 </div>
728 <div style="display:flex;">
729 <input type="text" accept="image/*" id="wpsc-tinymce-image-url" class="wpsc-tinymce-image-url" style="flex-grow: 1;" autocomplete="off">
730 <button id="wpsc-tinymce-get-file-url" class="wpsc-button small secondary" style="margin-left: 2px; width:100px;" onclick="wpsc_tinymce_image_picker();"><?php esc_attr_e( 'Upload', 'supportcandy' ); ?></button>
731 <input type='file' accept="image/*" name='fileupload' id='wpsc-fileupload' style='display: none;'>
732 </div>
733 </div>
734 <div class="wpsc-input-group">
735 <div class="label-container">
736 <label for=""><?php esc_attr_e( 'Dimentions', 'supportcandy' ); ?></label>
737 <span class="required-char">*</span>
738 </div>
739 <div style="display: flex; align-items:center;">
740 <input type="text" id="wpsc-tinymce-image-width" aria-label="width" class="wpsc-image-dimention" style="width:70px; margin-right:5px;" autocomplete="off">
741 <span class="wpsc-image-dimention-x-sign" style="margin-right:5px;">x</span>
742 <input type="text" id="wpsc-tinymce-image-height" aria-label="height" class="wpsc-image-dimention" style="width:70px; margin-right:10px;" autocomplete="off">
743 <div class="checkbox-container">
744 <?php $unique_id = uniqid( 'wpsc_' ); ?>
745 <input id="<?php echo esc_attr( $unique_id ); ?>" type="checkbox" checked value="1"/>
746 <label for="<?php echo esc_attr( $unique_id ); ?>"><?php esc_attr_e( 'Constraint properties', 'supportcandy' ); ?></label>
747 </div>
748 </div>
749 </div>
750 <script>
751 jQuery(".wpsc-tinymce-image-url").change(function(){
752
753 var img = new Image();
754 img.src = jQuery(this).val();
755 img.onload = function() {
756 jQuery('#wpsc-tinymce-image-height').val(this.height);
757 jQuery('#wpsc-tinymce-image-width').val(this.width);
758 var aspectRatio = this.width/this.height;
759 //Get new height:
760 jQuery("#wpsc-tinymce-image-width").on("change", function(){
761
762 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
763 newWidth = jQuery(this).val();
764 newHeight = Math.round(newWidth/aspectRatio);
765 jQuery("#wpsc-tinymce-image-height").val(newHeight);
766 }
767 });
768 //Get new width:
769 jQuery("#wpsc-tinymce-image-height").on("change", function(){
770
771 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
772 newHeight = jQuery(this).val();
773 newWidth = Math.round(newHeight*aspectRatio);
774 jQuery("#wpsc-tinymce-image-width").val(newWidth);
775 }
776 });
777 }
778 });
779
780 /**
781 * Tinymce image picker
782 */
783 function wpsc_tinymce_image_picker() {
784
785 jQuery("#wpsc-fileupload").trigger("click");
786 jQuery("#wpsc-fileupload").unbind('change');
787
788 jQuery("#wpsc-fileupload").on("change", function() {
789 var file = this.files[0];
790 var dataform = new FormData();
791 dataform.append('file', file);
792 dataform.append('file_name', file.name);
793 dataform.append('action', 'wpsc_tinymce_upload_file');
794 dataform.append('_ajax_nonce', '<?php echo esc_attr( wp_create_nonce( 'wpsc_tinymce_upload_file' ) ); ?>');
795
796 jQuery.ajax({
797 url: supportcandy.ajax_url,
798 type: 'POST',
799 data: dataform,
800 processData: false,
801 contentType: false
802 }).done(function (res) {
803
804 jQuery('.wpsc-tinymce-image-url').val(res.imgURL);
805 var img = new Image();
806 img.src = res.imgURL;
807 img.onload = function() {
808
809 jQuery('#wpsc-tinymce-image-height').val(this.height);
810 jQuery('#wpsc-tinymce-image-width').val(this.width);
811 var aspectRatio = this.width/this.height;
812
813 //Get new height:
814 jQuery("#wpsc-tinymce-image-width").on("change", function() {
815
816 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
817 newWidth = jQuery(this).val();
818 newHeight = Math.round(newWidth/aspectRatio);
819 jQuery("#wpsc-tinymce-image-height").val(newHeight);
820 }
821 });
822
823 //Get new width:
824 jQuery("#wpsc-tinymce-image-height").on("change", function() {
825
826 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
827 newHeight = jQuery(this).val();
828 newWidth = Math.round(newHeight*aspectRatio);
829 jQuery("#wpsc-tinymce-image-width").val(newWidth);
830 }
831 });
832 };
833 });
834 });
835 }
836 </script>
837 </form>
838 <?php
839 $body = ob_get_clean();
840
841 ob_start();
842 ?>
843 <button class="wpsc-button small primary" onclick="wpsc_insert_editor_img('<?php echo esc_attr( $editor_id ); ?>');">
844 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
845 </button>
846 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
847 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
848 </button>
849 <?php
850 $footer = ob_get_clean();
851
852 $response = array(
853 'title' => $title,
854 'body' => $body,
855 'footer' => $footer,
856 );
857 wp_send_json( $response );
858 }
859
860 /**
861 * Custom tinymce image popup
862 *
863 * @return void
864 */
865 public static function edit_custom_image_tinymce() {
866
867 if ( check_ajax_referer( 'wpsc_edit_custom_image_tinymce', '_ajax_nonce', false ) != 1 ) {
868 wp_send_json_error( 'Unauthorized request!', 401 );
869 }
870
871 $title = esc_attr__( 'Edit image', 'supportcandy' );
872
873 $editor_id = isset( $_POST['editor_id'] ) ? sanitize_text_field( wp_unslash( $_POST['editor_id'] ) ) : '';
874 if ( ! $editor_id ) {
875 wp_send_json_error( 'Something went wrong!', 400 );
876 }
877
878 $height = isset( $_POST['height'] ) ? intval( $_POST['height'] ) : '';
879 if ( ! $height ) {
880 wp_send_json_error( 'Something went wrong!', 400 );
881 }
882
883 $width = isset( $_POST['width'] ) ? intval( $_POST['width'] ) : '';
884 if ( ! $width ) {
885 wp_send_json_error( 'Something went wrong!', 400 );
886 }
887
888 $src = isset( $_POST['src'] ) ? esc_url_raw( wp_unslash( $_POST['src'] ) ) : '';
889 if ( ! $src ) {
890 wp_send_json_error( 'Something went wrong!', 400 );
891 }
892
893 ob_start();
894 ?>
895 <form action="#" onsubmit="return false;" class="wpsc-insert-edit-image">
896 <div class="wpsc-input-group">
897 <div class="label-container">
898 <label for=""><?php esc_attr_e( 'Source', 'supportcandy' ); ?></label>
899 <span class="required-char">*</span>
900 </div>
901 <div style="display:flex;">
902 <input type="text" id="wpsc-tinymce-image-url" class="wpsc-tinymce-image-url" style="flex-grow: 1;" autocomplete="off" value="<?php echo esc_attr( $src ); ?>">
903 <button id="wpsc-tinymce-get-file-url" class="wpsc-button small secondary" style="margin-left: 2px; width:100px" onclick="wpsc_tinymce_image_picker();"><?php esc_attr_e( 'Upload', 'supportcandy' ); ?></button>
904 <input type='file' accept="image/*" name='fileupload' id='wpsc-fileupload' style='display: none;'>
905 </div>
906 </div>
907 <div class="wpsc-input-group">
908 <div class="label-container">
909 <label for=""><?php esc_attr_e( 'Dimentions', 'supportcandy' ); ?></label>
910 <span class="required-char">*</span>
911 </div>
912 <div style="display: flex; align-items:center;">
913 <input type="text" id="wpsc-tinymce-image-width" aria-label="width" class="wpsc-image-dimention" style="width:70px; margin-right:5px;" autocomplete="off" value="<?php echo esc_attr( $width ); ?>">
914 <span class="wpsc-image-dimention-x-sign" style="margin-right:5px;">x</span>
915 <input type="text" id="wpsc-tinymce-image-height" aria-label="height" class="wpsc-image-dimention" style="width:70px; margin-right:10px;" autocomplete="off" value="<?php echo esc_attr( $height ); ?>" >
916 <div class="checkbox-container">
917 <?php $unique_id = uniqid( 'wpsc_' ); ?>
918 <input id="<?php echo esc_attr( $unique_id ); ?>" type="checkbox" checked value="1"/>
919 <label for="<?php echo esc_attr( $unique_id ); ?>"><?php esc_attr_e( 'Constraint properties', 'supportcandy' ); ?></label>
920 </div>
921 </div>
922 </div>
923 <script>
924 jQuery(".wpsc-tinymce-image-url").change(function(){
925
926 var img = new Image();
927 img.src = jQuery(this).val();
928 img.onload = function() {
929 jQuery('#wpsc-tinymce-image-height').val(this.height);
930 jQuery('#wpsc-tinymce-image-width').val(this.width);
931 var aspectRatio = this.width/this.height;
932 //Get new height:
933 jQuery("#wpsc-tinymce-image-width").on("change", function(){
934
935 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
936 newWidth = jQuery(this).val();
937 newHeight = Math.round(newWidth/aspectRatio);
938 jQuery("#wpsc-tinymce-image-height").val(newHeight);
939 }
940 });
941 //Get new width:
942 jQuery("#wpsc-tinymce-image-height").on("change", function(){
943
944 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
945 newHeight = jQuery(this).val();
946 newWidth = Math.round(newHeight*aspectRatio);
947 jQuery("#wpsc-tinymce-image-width").val(newWidth);
948 }
949 });
950 }
951 });
952 var height = <?php echo esc_attr( $height ); ?>;
953 var width = <?php echo esc_attr( $width ); ?>;
954 var aspectRatio = width/height;
955 jQuery("#wpsc-tinymce-image-width").on("change", function(){
956
957 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
958 newWidth = jQuery(this).val();
959 newHeight = Math.round(newWidth/aspectRatio);
960 jQuery("#wpsc-tinymce-image-height").val(newHeight);
961 }
962 });
963 //Get new width:
964 jQuery("#wpsc-tinymce-image-height").on("change", function(){
965
966 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
967 newHeight = jQuery(this).val();
968 newWidth = Math.round(newHeight*aspectRatio);
969 jQuery("#wpsc-tinymce-image-width").val(newWidth);
970 }
971 });
972
973 /**
974 * Tinymce image picker
975 */
976 function wpsc_tinymce_image_picker() {
977
978 jQuery("#wpsc-fileupload").trigger("click");
979 jQuery("#wpsc-fileupload").unbind('change');
980
981 jQuery("#wpsc-fileupload").on("change", function() {
982 var file = this.files[0];
983 var dataform = new FormData();
984 dataform.append('file', file);
985 dataform.append('file_name', file.name);
986 dataform.append('action', 'wpsc_tinymce_upload_file');
987 dataform.append('_ajax_nonce', '<?php echo esc_attr( wp_create_nonce( 'wpsc_tinymce_upload_file' ) ); ?>');
988
989 jQuery.ajax({
990 url: supportcandy.ajax_url,
991 type: 'POST',
992 data: dataform,
993 processData: false,
994 contentType: false
995 }).done(function (res) {
996
997 jQuery('.wpsc-tinymce-image-url').val(res.imgURL);
998 var img = new Image();
999 img.src = res.imgURL;
1000 img.onload = function() {
1001
1002 jQuery('#wpsc-tinymce-image-height').val(this.height);
1003 jQuery('#wpsc-tinymce-image-width').val(this.width);
1004 var aspectRatio = this.width/this.height;
1005
1006 //Get new height:
1007 jQuery("#wpsc-tinymce-image-width").on("change", function() {
1008
1009 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
1010 newWidth = jQuery(this).val();
1011 newHeight = Math.round(newWidth/aspectRatio);
1012 jQuery("#wpsc-tinymce-image-height").val(newHeight);
1013 }
1014 });
1015
1016 //Get new width:
1017 jQuery("#wpsc-tinymce-image-height").on("change", function() {
1018
1019 if(jQuery("#<?php echo esc_attr( $unique_id ); ?>").prop('checked') == true){
1020 newHeight = jQuery(this).val();
1021 newWidth = Math.round(newHeight*aspectRatio);
1022 jQuery("#wpsc-tinymce-image-width").val(newWidth);
1023 }
1024 });
1025 };
1026 });
1027 });
1028 }
1029 </script>
1030 </form>
1031 <?php
1032 $body = ob_get_clean();
1033
1034 ob_start();
1035 ?>
1036 <button class="wpsc-button small primary" onclick="wpsc_insert_editor_img('<?php echo esc_attr( $editor_id ); ?>');">
1037 <?php esc_attr_e( 'Submit', 'supportcandy' ); ?>
1038 </button>
1039 <button class="wpsc-button small secondary" onclick="wpsc_close_modal();">
1040 <?php esc_attr_e( 'Cancel', 'supportcandy' ); ?>
1041 </button>
1042 <?php
1043 $footer = ob_get_clean();
1044
1045 $response = array(
1046 'title' => $title,
1047 'body' => $body,
1048 'footer' => $footer,
1049 );
1050 wp_send_json( $response );
1051 }
1052
1053 /**
1054 * Check download file
1055 *
1056 * @return void
1057 */
1058 public static function check_download_file() {
1059
1060 if ( isset( $_REQUEST['wpsc_attachment'] ) ) { // phpcs:ignore
1061
1062 if ( WPSC_Functions::is_site_admin() && ! extension_loaded( 'fileinfo' ) ) {
1063 echo 'The fileinfo extension is not available. Please enable it in your PHP configuration.';
1064 exit;
1065 }
1066
1067 $current_user = WPSC_Current_User::$current_user;
1068 $has_auth = true;
1069 if ( isset( $_REQUEST['user'] ) && ! $current_user ) { // phpcs:ignore
1070
1071 $customer = new WPSC_Customer( intval( $_REQUEST['user'] ) ); // phpcs:ignore
1072 if ( ! $customer->id ) {
1073 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1074 }
1075
1076 $current_user = WPSC_Current_User::change_current_user( $customer->email );
1077 $has_auth = false;
1078 }
1079
1080 $attachment_id = intval( $_REQUEST['wpsc_attachment'] ); // phpcs:ignore
1081 $attachment = new WPSC_Attachment( $attachment_id );
1082 if ( ! $attachment->id ) {
1083 return;
1084 }
1085 $auth_code = isset($_REQUEST['auth_code']) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
1086 switch ( $attachment->source ) {
1087
1088 case 'cf':
1089 $cf = new WPSC_Custom_Field( $attachment->source_id );
1090 if ( ! $cf->id ) {
1091 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1092 }
1093
1094 if ( in_array( $cf->field, array( 'ticket', 'agentonly' ) ) ) { // ticket field.
1095
1096 $ticket = new WPSC_Ticket( $attachment->ticket_id );
1097 if ( ! $ticket->id ) {
1098 $ticket = new WPSC_Archive_Ticket( $attachment->ticket_id );
1099 if ( ! $ticket->id ) {
1100 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1101 }
1102 }
1103
1104 if ( ! $has_auth ) {
1105 $auth_code = isset( $_REQUEST['auth_code'] ) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
1106 if ( ! $auth_code || ! $ticket->auth_code || ! hash_equals( (string) $ticket->auth_code, $auth_code ) ) {
1107 wp_send_json_error( 'Unauthorized!', 401 );
1108 }
1109 }
1110
1111 if ( is_a( $ticket, 'WPSC_Ticket' ) || is_a( $ticket, 'WPSC_Archive_Ticket' ) ) {
1112 // Determine correct class based on ticket type.
1113 $ticket_class = is_a( $ticket, 'WPSC_Ticket' )
1114 ? 'WPSC_Individual_Ticket'
1115 : 'WPSC_Individual_Archive_Ticket';
1116
1117 // Assign current ticket to class static property.
1118 $ticket_class::$ticket = $ticket;
1119 $has_access = (
1120 ( $current_user->is_agent && $ticket_class::has_ticket_cap( 'view' ) ) ||
1121 $ticket_class::is_customer() ||
1122 ( $ticket->auth_code && hash_equals( (string) $ticket->auth_code, $auth_code ) )
1123 );
1124
1125 if ( ! $has_access ) {
1126 wp_send_json_error( __( 'Unauthorized!', 'supportcandy' ), 401 );
1127 }
1128 }
1129 self::file_download( $attachment );
1130
1131 } else { // customer field.
1132
1133 $customer = new WPSC_Customer( intval( $attachment->customer_id ) );
1134 $ticket_widgets = get_option( 'wpsc-ticket-widget', array() );
1135 $raised_by = $ticket_widgets['raised-by'];
1136
1137 if (
1138 $current_user->customer->id == $customer->id ||
1139 (
1140 $current_user->is_agent &&
1141 in_array( $current_user->agent->role, $raised_by['allowed-agent-roles'] )
1142 )
1143 ) {
1144 self::file_download( $attachment );
1145 }
1146 }
1147 break;
1148
1149 case 'reply':
1150 case 'report':
1151 $ticket = new WPSC_Ticket( $attachment->ticket_id );
1152 if ( ! $ticket->id ) {
1153 $ticket = new WPSC_Archive_Ticket( $attachment->ticket_id );
1154 if ( ! $ticket->id ) {
1155 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1156 }
1157 }
1158
1159 if ( ! $has_auth ) {
1160 $auth_code = isset( $_REQUEST['auth_code'] ) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
1161 if ( ! $auth_code || ! $ticket->auth_code || ! hash_equals( (string) $ticket->auth_code, $auth_code ) ) {
1162 wp_send_json_error( 'Unauthorized!', 401 );
1163 }
1164 }
1165
1166 if ( is_a( $ticket, 'WPSC_Ticket' ) || is_a( $ticket, 'WPSC_Archive_Ticket' ) ) {
1167 // Determine correct class based on ticket type.
1168 $ticket_class = is_a( $ticket, 'WPSC_Ticket' )
1169 ? 'WPSC_Individual_Ticket'
1170 : 'WPSC_Individual_Archive_Ticket';
1171
1172 // Assign current ticket to class static property.
1173 $ticket_class::$ticket = $ticket;
1174 $has_access = (
1175 ( $current_user->is_agent && $ticket_class::has_ticket_cap( 'view' ) ) ||
1176 $ticket_class::is_customer() ||
1177 ( $ticket->auth_code && hash_equals( (string) $ticket->auth_code, $auth_code ) )
1178 );
1179
1180 if ( ! $has_access ) {
1181 wp_send_json_error( __( 'Unauthorized!', 'supportcandy' ), 401 );
1182 }
1183 }
1184 self::file_download( $attachment );
1185 break;
1186
1187 case 'note':
1188 $ticket = new WPSC_Ticket( $attachment->ticket_id );
1189 if ( ! $ticket->id ) {
1190 $ticket = new WPSC_Archive_Ticket( $attachment->ticket_id );
1191 if ( ! $ticket->id ) {
1192 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1193 }
1194 }
1195
1196 if ( ! $has_auth ) {
1197 $auth_code = isset( $_REQUEST['auth_code'] ) ? sanitize_text_field( $_REQUEST['auth_code'] ) : ''; // phpcs:ignore
1198 if ( ! $auth_code || ! $ticket->auth_code || ! hash_equals( (string) $ticket->auth_code, $auth_code ) ) {
1199 wp_send_json_error( 'Unauthorized!', 401 );
1200 }
1201 }
1202
1203 if ( is_a( $ticket, 'WPSC_Ticket' ) || is_a( $ticket, 'WPSC_Archive_Ticket' ) ) {
1204
1205 // Determine correct handler class dynamically.
1206 $ticket_class = is_a( $ticket, 'WPSC_Ticket' )
1207 ? 'WPSC_Individual_Ticket'
1208 : 'WPSC_Individual_Archive_Ticket';
1209
1210 // Assign the ticket.
1211 $ticket_class::$ticket = $ticket;
1212 if ( ! ( $current_user->is_agent && $ticket_class::has_ticket_cap( 'pn' ) ) ) {
1213 wp_send_json_error( __( 'Unauthorized!', 'supportcandy' ), 401 );
1214 }
1215 }
1216
1217 self::file_download( $attachment );
1218 break;
1219
1220 case 'img_editor':
1221 $ticket = new WPSC_Ticket( $attachment->ticket_id );
1222 if ( ! $ticket->id ) {
1223 $ticket = new WPSC_Archive_Ticket( $attachment->ticket_id );
1224 if ( ! $ticket->id ) {
1225 wp_send_json_error( __( 'Bad request!', 'supportcandy' ), 400 );
1226 }
1227 }
1228
1229 if ( is_a( $ticket, 'WPSC_Ticket' ) || is_a( $ticket, 'WPSC_Archive_Ticket' ) ) {
1230
1231 // Determine class dynamically.
1232 $ticket_class = is_a( $ticket, 'WPSC_Ticket' )
1233 ? 'WPSC_Individual_Ticket'
1234 : 'WPSC_Individual_Archive_Ticket';
1235
1236 $ticket_class::$ticket = $ticket;
1237 if (
1238 ! (
1239 ( $current_user->is_agent && $ticket_class::has_ticket_cap( 'view' ) ) ||
1240 $ticket_class::is_customer() ||
1241 ( $ticket->auth_code && $auth_code && hash_equals( (string) $ticket->auth_code, $auth_code ) )
1242 )
1243 ) {
1244 wp_send_json_error( 'Unauthorized!', 401 );
1245 }
1246 self::file_download( $attachment );
1247 } else {
1248 wp_send_json_error( __( 'Invalid ticket type.', 'supportcandy' ), 400 );
1249 }
1250 break;
1251 case 'img_editor_tmp':
1252 // Not yet attached to any ticket/thread, so there is no auth-code
1253 // to check it against. Serve it only to whoever uploaded it,
1254 // proven either by the recorded uploader identity or by the
1255 // per-attachment nonce handed out in the upload response.
1256 // The identity check also keeps the image loading in the editor
1257 // when a guest logs in or registers mid-compose, which changes
1258 // the user the nonce is bound to.
1259 $view_nonce = isset( $_REQUEST['wpsc_nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['wpsc_nonce'] ) ) : ''; // phpcs:ignore
1260 if ( ! self::is_uploaded_by_current_user( $attachment ) &&
1261 ( ! $view_nonce || ! wp_verify_nonce( $view_nonce, self::get_img_editor_tmp_nonce_action( $attachment->id ) ) ) ) {
1262 wp_send_json_error( 'Unauthorized!', 401 );
1263 }
1264 self::file_download( $attachment );
1265 }
1266 }
1267 }
1268
1269 /**
1270 * Download attachment
1271 *
1272 * @param WPSC_Attachment $attachment - attachment object.
1273 * @return void
1274 */
1275 public static function file_download( $attachment ) {
1276
1277 // Turn off all output buffering.
1278 while ( ob_get_level() ) {
1279 ob_end_clean();
1280 }
1281
1282 $upload_dir = wp_upload_dir();
1283 $file_path = $upload_dir['basedir'] . $attachment->file_path;
1284 if ( ! file_exists( $file_path ) ) {
1285 echo 'File does not exists!';
1286 exit;
1287 }
1288
1289 // Check whether attachment is of image type.
1290 $attach_settings = get_option( 'wpsc-gs-file-attachments' );
1291 if ( $attachment->is_image && $attach_settings['image-download-behaviour'] == 'open-browser' ) {
1292 header( 'Content-Type: ' . mime_content_type( $file_path ) );
1293 readfile( $file_path ); // phpcs:ignore
1294 exit( 0 );
1295 }
1296
1297 header( 'Content-Description: File Transfer' );
1298 header( 'Cache-Control: public' );
1299 header( 'Content-Type: application/force-download' );
1300 header( 'Content-Disposition: attachment;filename="' . $attachment->name . '"' );
1301 header( 'Content-Length: ' . filesize( $file_path ) );
1302 flush();
1303 readfile( $file_path ); // phpcs:ignore
1304 exit( 0 );
1305 }
1306
1307 /**
1308 * Upload tinymce image file to database
1309 *
1310 * @return void
1311 */
1312 public static function tinymce_upload_file() {
1313
1314 if ( check_ajax_referer( 'wpsc_tinymce_upload_file', '_ajax_nonce', false ) != 1 ) {
1315 wp_send_json_error( 'Unauthorized request!', 401 );
1316 }
1317
1318 $file = isset( $_FILES['file'] ) ? $_FILES['file'] : false; // phpcs:ignore
1319 if ( ! $file ) {
1320 wp_send_json_error( 'File not found!', 400 );
1321 }
1322
1323 $file_settings = get_option( 'wpsc-gs-file-attachments' );
1324 $original_name = sanitize_file_name( $file['name'] );
1325 $filename = time() . '_' . sanitize_file_name( $file['name'] );
1326 $extension = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
1327 $today = new DateTime();
1328 $upload_dir = wp_upload_dir();
1329
1330 // Check file extension.
1331 $img_extensions = array( 'jpg', 'jpeg', 'png', 'gif' );
1332 if ( ! ( in_array( $extension, $img_extensions ) ) ) {
1333 wp_send_json_error( 'Invalid file extension!', 400 );
1334 }
1335
1336 // Allowed file size.
1337 $allowed_file_size = intval( $file_settings['attachments-max-filesize'] ) * 1000000;
1338 if ( ! ( isset( $file['size'] ) && $file['size'] <= $allowed_file_size ) ) {
1339 wp_send_json_error( 'File size exceeds allowed limit!', 400 );
1340 }
1341
1342 $filepath_short = '/wpsc/' . $today->format( 'Y' ) . '/' . $today->format( 'm' ) . '/' . $filename;
1343
1344 // Init attachment data.
1345 $data = array(
1346 'name' => $original_name,
1347 'file_path' => $filepath_short,
1348 'is_image' => 1,
1349 'date_created' => $today->format( 'Y-m-d H:i:s' ),
1350 'is_active' => 0,
1351 'source' => 'img_editor_tmp',
1352 );
1353
1354 // Set up upload overrides.
1355 $upload_overrides = array(
1356 'test_form' => false,
1357 );
1358 $file['name'] = $filename;
1359 add_filter( 'upload_dir', array( __CLASS__, 'wpsc_upload_dir' ) );
1360 $uploaded_file = wp_handle_upload( $file, $upload_overrides );
1361 // Insert record in database.
1362 if ( $uploaded_file && empty( $uploaded_file['error'] ) ) {
1363
1364 $pos = strpos( $uploaded_file['file'], '/wpsc/' );
1365 if ( $pos === false ) {
1366 wp_send_json_error( 'Something went wrong!', 500 );
1367 }
1368 $new_path = substr( $uploaded_file['file'], $pos );
1369 $data['file_path'] = $new_path;
1370 $attachment = self::insert( $data );
1371 if ( ! $attachment->id ) {
1372 wp_send_json_error( 'Something went wrong!', 500 );
1373 }
1374
1375 // Temporary in-editor images have no ticket yet to check an auth-code
1376 // against, so gate access with a per-attachment nonce instead, known
1377 // only to whoever just uploaded it via this response. The uploader
1378 // is also recorded so the later claim can be checked server side.
1379 self::set_uploader( $attachment );
1380 $view_nonce = wp_create_nonce( self::get_img_editor_tmp_nonce_action( $attachment->id ) );
1381
1382 wp_send_json( array( 'imgURL' => home_url( '/' ) . '?wpsc_attachment=' . $attachment->id . '&wpsc_nonce=' . $view_nonce ) );
1383
1384 } else {
1385
1386 $error_message = isset( $uploaded_file['error'] ) ? $uploaded_file['error'] : 'Something went wrong!';
1387 wp_send_json_error( $error_message, 500 );
1388 }
1389 }
1390
1391 /**
1392 * Capture the customer this request's session genuinely belongs to.
1393 * Runs on 'init' right after the current user is resolved, so that a
1394 * later change_current_user() (used when acting on behalf of someone
1395 * else, and in one case driven by request input) cannot influence
1396 * attachment ownership checks.
1397 *
1398 * @return void
1399 */
1400 public static function snapshot_session_customer() {
1401
1402 $current_user = WPSC_Current_User::$current_user;
1403 self::$session_customer_id = ( $current_user && $current_user->is_customer && $current_user->customer && $current_user->customer->id )
1404 ? intval( $current_user->customer->id )
1405 : 0;
1406 }
1407
1408 /**
1409 * All identities that can legitimately be claimed by whoever is making
1410 * the current request. A visitor can hold more than one at a time, and
1411 * can move between them mid-compose (a guest who attaches a file and
1412 * then logs in or registers before submitting the ticket), so a claim
1413 * is accepted when the recorded uploader matches any one of them.
1414 *
1415 * Identities:
1416 * c:<customer id> anyone SupportCandy knows as a customer, which
1417 * covers both registered WordPress users (a customer
1418 * record is created for them on first sight) and
1419 * guest-login (OTP) customers;
1420 * g:<token> a random token in a cookie, issued on first upload,
1421 * for a plain guest who has no customer record yet -
1422 * a first time visitor only gets one when the ticket
1423 * is finally submitted.
1424 *
1425 * @param boolean $create - whether to issue a guest token if none exists.
1426 * @return array
1427 */
1428 public static function get_current_uploader_ids( $create = false ) {
1429
1430 $ids = array();
1431
1432 // Resolve the logged-in user's customer here rather than relying on
1433 // the 'init' snapshot alone: on REST requests an application
1434 // password is not authenticated until after 'init', so the snapshot
1435 // is still empty at that point. get_current_user_id() reflects
1436 // WordPress's own auth state and is unaffected by SupportCandy's
1437 // change_current_user(), so it stays safe to trust.
1438 $user_id = get_current_user_id();
1439 if ( $user_id ) {
1440
1441 $customer = WPSC_Customer::get_by_user_id( $user_id );
1442 if ( ! $customer || ! $customer->id ) {
1443 $user = get_userdata( $user_id );
1444 $customer = $user ? WPSC_Customer::get_by_email( $user->user_email ) : false;
1445 }
1446
1447 if ( $customer && $customer->id ) {
1448 $ids[] = 'c:' . $customer->id;
1449 }
1450 }
1451
1452 // Guest login (OTP) keeps a customer record without a WordPress
1453 // user, and is captured on 'init' before it can be reassigned.
1454 if ( self::$session_customer_id && ! in_array( 'c:' . self::$session_customer_id, $ids, true ) ) {
1455 $ids[] = 'c:' . self::$session_customer_id;
1456 }
1457
1458 $token = isset( $_COOKIE['wpsc_uploader'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['wpsc_uploader'] ) ) : '';
1459 $token = preg_match( '/^[a-f0-9]{32}$/', $token ) ? $token : '';
1460
1461 if ( ! $token && $create ) {
1462
1463 $token = md5( wp_generate_password( 32, false ) . microtime() );
1464 setcookie( 'wpsc_uploader', $token, time() + DAY_IN_SECONDS, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true );
1465 $_COOKIE['wpsc_uploader'] = $token;
1466 }
1467
1468 if ( $token ) {
1469 $ids[] = 'g:' . $token;
1470 }
1471
1472 return $ids;
1473 }
1474
1475 /**
1476 * Record the current requester as the uploader of the given attachment.
1477 *
1478 * @param WPSC_Attachment $attachment - attachment object.
1479 * @return void
1480 */
1481 public static function set_uploader( $attachment ) {
1482
1483 if ( ! $attachment || ! $attachment->id ) {
1484 return;
1485 }
1486
1487 // Always issue the cookie too, so a visitor who changes identity
1488 // mid-compose (guest who then logs in or registers) still holds the
1489 // identity that was recorded here.
1490 $uploader_ids = self::get_current_uploader_ids( true );
1491 if ( ! $uploader_ids ) {
1492 return;
1493 }
1494
1495 $attachment->uploaded_by = $uploader_ids[0];
1496 $attachment->save();
1497 }
1498
1499 /**
1500 * Whether the current requester is the one who uploaded this attachment.
1501 *
1502 * @param WPSC_Attachment $attachment - attachment object.
1503 * @return boolean
1504 */
1505 public static function is_uploaded_by_current_user( $attachment ) {
1506
1507 if ( ! $attachment || ! $attachment->id || ! $attachment->uploaded_by ) {
1508 return false;
1509 }
1510
1511 foreach ( self::get_current_uploader_ids() as $uploader ) {
1512 if ( hash_equals( (string) $attachment->uploaded_by, $uploader ) ) {
1513 return true;
1514 }
1515 }
1516
1517 return false;
1518 }
1519
1520 /**
1521 * Single gate for binding (claiming) an attachment to a ticket.
1522 *
1523 * An attachment may only be bound when all of the following hold:
1524 * - it exists;
1525 * - it does not already carry a ticket id. Note this is deliberately
1526 * independent of is_active: deleting a thread clears is_active but
1527 * leaves ticket_id in place, and such an attachment must still be
1528 * refused rather than becoming claimable again;
1529 * - the requester is the one who uploaded it, proven either by the
1530 * recorded uploader identity or by the per-attachment nonce handed
1531 * out in the upload response.
1532 *
1533 * Every path that binds an attachment to a ticket must go through this.
1534 *
1535 * @param WPSC_Attachment $attachment - attachment object.
1536 * @param string $nonce - optional per-attachment upload nonce.
1537 * @return boolean
1538 */
1539 public static function can_claim( $attachment, $nonce = '' ) {
1540
1541 if ( ! $attachment || ! $attachment->id || $attachment->ticket_id ) {
1542 return false;
1543 }
1544
1545 if ( self::is_uploaded_by_current_user( $attachment ) ) {
1546 return true;
1547 }
1548
1549 // Fall back to the upload nonce, which is only ever handed to the
1550 // uploader in the response to their own upload request.
1551 $nonce = $nonce ? $nonce : self::get_submitted_upload_nonce( $attachment->id );
1552 return $nonce && wp_verify_nonce( $nonce, self::get_img_editor_tmp_nonce_action( $attachment->id ) ) ? true : false;
1553 }
1554
1555 /**
1556 * Read the upload nonce submitted alongside an attachment id, if any.
1557 * Sent by the upload widgets as wpsc_attachment_token[<id>].
1558 *
1559 * @param int $attachment_id - attachment id.
1560 * @return string
1561 */
1562 public static function get_submitted_upload_nonce( $attachment_id ) {
1563
1564 if ( ! isset( $_REQUEST['wpsc_attachment_token'] ) || ! is_array( $_REQUEST['wpsc_attachment_token'] ) ) { // phpcs:ignore
1565 return '';
1566 }
1567
1568 $tokens = wp_unslash( $_REQUEST['wpsc_attachment_token'] ); // phpcs:ignore
1569 return isset( $tokens[ $attachment_id ] ) ? sanitize_text_field( $tokens[ $attachment_id ] ) : '';
1570 }
1571
1572 /**
1573 * Nonce action used to gate access to a not-yet-attached (img_editor_tmp)
1574 * in-editor image, since it isn't yet tied to any ticket/auth-code.
1575 * Also used, once the attachment is referenced from a reply/ticket body,
1576 * as proof that the requester is the one who uploaded it before it is
1577 * bound (claimed) to that ticket. Public: shared by reply/new-ticket
1578 * handlers in other classes that perform that claim.
1579 *
1580 * @param int $attachment_id - attachment id.
1581 * @return string
1582 */
1583 public static function get_img_editor_tmp_nonce_action( $attachment_id ) {
1584 return 'wpsc_view_img_editor_tmp_' . $attachment_id;
1585 }
1586 }
1587 endif;
1588
1589 WPSC_Attachment::init();
1590