PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.4.8
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.4.8
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 / class-wpsc-functions.php

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

1,426 lines 37.8 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_Functions' ) ) :
7
8 final class WPSC_Functions {
9
10 /**
11 * Referance classes. Used to provide objects on runtime.
12 *
13 * @var string
14 */
15 public static $ref_classes;
16
17 /**
18 * Initialize class
19 */
20 public static function init() {
21
22 // Load ref classes.
23 add_action( 'init', array( __CLASS__, 'load_ref_classes' ), 1 );
24
25 // Refresh paid customer option when plugins state changes.
26 add_action( 'activated_plugin', array( __CLASS__, 'update_paid_customer_status_option' ) );
27 add_action( 'deactivated_plugin', array( __CLASS__, 'update_paid_customer_status_option' ), 99 );
28 }
29
30 /**
31 * Load ref classes
32 */
33 public static function load_ref_classes() {
34
35 self::$ref_classes = apply_filters( 'wpsc_load_ref_classes', array() );
36 }
37
38 /**
39 * Return an object for ref class
40 *
41 * @param string $ref_class - reference class.
42 * @param string $value - value.
43 * @return object
44 */
45 public static function get_object( $ref_class, $value ) {
46
47 $object = null;
48
49 switch ( $ref_class ) {
50
51 case 'wp_user':
52 $object = $value ? get_user_by( 'ID', $value ) : null;
53 break;
54
55 case 'datetime':
56 $object = $value && $value !== '0000-00-00 00:00:00' ? DateTime::createFromFormat( 'Y-m-d H:i:s', $value ) : '';
57 break;
58
59 case 'dateinterval':
60 $object = $value ? new DateInterval( $value ) : new DateInterval( 'PT0M' );
61 break;
62
63 case 'wpsc_cft':
64 $object = isset( self::$ref_classes[ $value ] ) ? self::$ref_classes[ $value ]['class'] : null;
65 break;
66
67 case 'wpsc_customer':
68 if ( $value && ! is_object( $object ) && isset( self::$ref_classes[ $ref_class ] ) ) {
69 $class = self::$ref_classes[ $ref_class ]['class'];
70 $object = new $class( $value );
71 } else {
72 $object = WPSC_Customer::get_anonymous_customer();
73 }
74 break;
75
76 default:
77 $object = apply_filters( 'wpsc_fun_get_object', $value, $ref_class );
78 if ( ! is_object( $object ) && isset( self::$ref_classes[ $ref_class ] ) ) {
79 $class = self::$ref_classes[ $ref_class ]['class'];
80 $object = new $class( $value );
81 }
82 }
83
84 return $object ? $object : $value;
85 }
86
87 /**
88 * Return value to be saved in model db for an abject
89 *
90 * @param string $ref_class - reference class.
91 * @param string $value - value.
92 * @return object
93 */
94 public static function set_object( $ref_class, $value ) {
95
96 $save_val = '';
97 switch ( $ref_class ) {
98
99 case 'wp_user':
100 $save_val = $value->ID;
101 break;
102
103 case 'datetime':
104 $save_val = $value->format( 'Y-m-d H:i:s' );
105 break;
106
107 case 'dateinterval':
108 $save_val = self::date_interval_to_string( $value );
109 break;
110
111 default:
112 if ( isset( self::$ref_classes[ $ref_class ] ) ) {
113 $key = self::$ref_classes[ $ref_class ]['save-key'];
114 $save_val = $value->$key;
115 }
116 }
117
118 return $save_val ? $save_val : $value;
119 }
120
121 /**
122 * Return search string given in model filters array
123 *
124 * @param array $filter - filter array.
125 * @return string
126 */
127 public static function get_filter_search_str( $filter ) {
128
129 return isset( $filter['search'] ) ? addslashes( trim( $filter['search'] ) ) : '';
130 }
131
132 /**
133 * Parse user filter for models which has static schema
134 *
135 * @param string $class_name - Class name of the model.
136 * @param array $filters - User filters array.
137 * @return string
138 */
139 public static function parse_user_filters( $class_name, $filters ) {
140
141 global $wpdb;
142
143 // Invalid filter.
144 if ( ! isset( $filters['relation'] ) || count( $filters ) < 2 ) {
145 return '1=1';
146 }
147
148 $relation = $filters['relation'];
149 $filter_str = array();
150
151 foreach ( $filters as $key => $filter ) {
152
153 // Skip if current element is relation indicator.
154 if ( $key === 'relation' ) {
155 continue;
156 }
157
158 // Invalid filter if it is not an array.
159 if ( ! is_array( $filter ) ) {
160 return '1=1';
161 }
162
163 // Call recursively if there is multi-layer filter detected.
164 if ( isset( $filter['relation'] ) ) {
165 $filter_str[] = self::parse_user_filters( $class_name, $filter );
166 continue;
167 }
168
169 // Invalid filter if it does not contain slug, compare and val indexes.
170 $slug = isset( $filter['slug'] ) ? self::sanitize_sql_key( $filter['slug'] ) : false;
171 $compare = isset( $filter['compare'] ) ? $filter['compare'] : false;
172 $val = isset( $filter['val'] ) || $filter['val'] == null ? $filter['val'] : false;
173 if ( ! $slug || ! $compare || $val === false ) {
174 return '1=1';
175 }
176
177 // custom filter.
178 if ( $slug === 'custom_query' ) {
179
180 $filter_str[] = $val;
181
182 } else {
183 switch ( $compare ) {
184
185 case '<':
186 case '=':
187 case '>':
188 case '<=':
189 case '>=':
190 if ( $class_name::$schema[ $slug ]['has_multiple_val'] ) {
191 $filter_str[] = '1=1';
192 break;
193 }
194 $filter_str[] = $slug . ' ' . $compare . ' \'' . esc_sql( $val ) . '\'';
195 break;
196
197 case 'BETWEEN':
198 $filter_str[] = $slug . ' BETWEEN \'' . esc_sql( $val[0] ) . '\' AND \'' . esc_sql( $val[1] ) . '\'';
199 break;
200
201 case 'IN':
202 if ( $class_name::$schema[ $slug ]['has_multiple_val'] ) {
203
204 $rlike = array();
205 foreach ( $val as $match ) {
206
207 $rlike[] = $slug . ' RLIKE \'(^|[|])' . esc_sql( $match ) . '($|[|])\'';
208 }
209 $filter_str[] = '( ' . implode( ' OR ', $rlike ) . ' )';
210
211 } else {
212
213 $filter_str[] = $slug . ' IN ( \'' . implode( '\', \'', esc_sql( $val ) ) . '\' )';
214 }
215 break;
216
217 case 'NOT IN':
218 if ( $class_name::$schema[ $slug ]['has_multiple_val'] ) {
219
220 $rlike = array();
221 foreach ( $val as $match ) {
222
223 $rlike[] = $slug . ' NOT RLIKE \'(^|[|])' . esc_sql( $match ) . '($|[|])\'';
224 }
225 $filter_str[] = '( ' . implode( ' OR ', $rlike ) . ' )';
226
227 } else {
228
229 $filter_str[] = $slug . ' NOT IN ( \'' . implode( '\', \'', esc_sql( $val ) ) . '\' )';
230 }
231 break;
232
233 case 'IS':
234 $filter_str[] = $slug . ' IS NULL';
235 break;
236
237 case 'IS NOT':
238 $filter_str[] = $slug . ' IS NOT NULL';
239 break;
240
241 case 'LIKE':
242 $filter_str[] = $slug . ' ' . $compare . ' \'%' . esc_sql( $wpdb->esc_like( $val ) ) . '%\'';
243 break;
244 }
245 }
246 }
247
248 return count( $filter_str ) > 1 ?
249 '( ' . implode( ' ' . $relation . ' ', $filter_str ) . ' )' :
250 $filter_str[0];
251 }
252
253 /**
254 * Get order for find method of models
255 *
256 * @param array $filter - filter array.
257 * @return string
258 */
259 public static function parse_order( $filter ) {
260
261 $orderby = isset( $filter['orderby'] ) && $filter['orderby'] ?
262 $filter['orderby'] : '';
263
264 if ( ! $orderby ) {
265 return '';
266 }
267
268 $order = isset( $filter['order'] ) && $filter['order'] && in_array( $filter['order'], array( 'ASC', 'DESC' ) ) ?
269 $filter['order'] : 'ASC';
270
271 $orderby_slug = isset( $filter['orderby_slug'] ) && $filter['orderby_slug'] ? $filter['orderby_slug'] : '';
272 $cf = WPSC_Custom_Field::get_cf_by_slug( $orderby_slug );
273
274 if ( $cf && $cf->type::$slug == 'cf_number' ) {
275
276 return 'ORDER BY CAST(' . $orderby . ' AS SIGNED ) ' . $order . ' ';
277 } else {
278
279 return 'ORDER BY ' . $orderby . ' ' . $order . ' ';
280 }
281 }
282
283 /**
284 * Get order for find method of models
285 *
286 * @param array $filter - filter array.
287 * @return string
288 */
289 public static function parse_limit( $filter ) {
290
291 $items_per_page = isset( $filter['items_per_page'] ) ?
292 intval( $filter['items_per_page'] ) : 0;
293
294 if ( $items_per_page == 0 ) {
295 return '';
296 }
297
298 $page_no = isset( $filter['page_no'] ) && is_numeric( $filter['page_no'] ) ?
299 intval( $filter['page_no'] ) : 1;
300
301 $offset = ( $page_no - 1 ) * $items_per_page;
302
303 return 'LIMIT ' . $offset . ', ' . $items_per_page;
304 }
305
306 /**
307 * Calculate total pages, has_next_page, results, etc. for models
308 *
309 * @param string $results - page result.
310 * @param int $total_items - total page items.
311 * @param array $filter - filter items.
312 * @return array
313 */
314 public static function parse_response( $results, int $total_items, $filter ) {
315
316 $items_per_page = isset( $filter['items_per_page'] ) ?
317 intval( $filter['items_per_page'] ) : 0;
318
319 if ( ! $items_per_page ) {
320 return array(
321 'total_items' => $total_items,
322 'results' => $results,
323 );
324 }
325
326 $page_no = isset( $filter['page_no'] ) && is_numeric( $filter['page_no'] ) ?
327 intval( $filter['page_no'] ) : 1;
328
329 $total_pages = ceil( $total_items / $items_per_page );
330
331 $has_next_page = $page_no < $total_pages ? true : false;
332
333 return array(
334 'total_items' => $total_items,
335 'items_per_page' => $items_per_page,
336 'current_page' => $page_no,
337 'total_pages' => $total_pages,
338 'has_next_page' => $has_next_page,
339 'results' => $results,
340 );
341 }
342
343 /**
344 * Check whether current page is supportcandy page or not
345 * Used for loading framework.
346 * Pages like dashboard pages and where wpsc shortcode is present are considered true.
347 *
348 * @return boolean
349 */
350 public static function is_wpsc_page() {
351
352 if ( is_admin() ) {
353
354 return isset( $_REQUEST['page'] ) && preg_match( '/wpsc-/', $_REQUEST['page'] ) ? true : false; // phpcs:ignore
355
356 } else {
357
358 return true;
359 }
360 }
361
362 /**
363 * Check whether current use is site admin or not
364 *
365 * @return boolean
366 */
367 public static function is_site_admin() {
368
369 global $current_user;
370 return $current_user && $current_user->ID && $current_user->has_cap( 'manage_options' ) ? true : false;
371 }
372
373 /**
374 * Get default filter auto-increament number
375 *
376 * @return integer
377 */
378 public static function get_tl_df_auto_increament() {
379
380 $index = intval( get_option( 'wpsc-tl-df-auto-increament', 0 ) );
381 update_option( 'wpsc-tl-df-auto-increament', ++$index );
382 return $index;
383 }
384
385 /**
386 * Get user custom filter auto-increament number
387 *
388 * @return integer
389 */
390 public static function get_tl_cf_auto_increament() {
391
392 global $current_user;
393 $index = intval( get_user_meta( $current_user->ID, get_current_blog_id() . '-wpsc-tl-cf-auto-increament', true ) );
394 update_user_meta( $current_user->ID, get_current_blog_id() . '-wpsc-tl-cf-auto-increament', ++$index );
395 return $index;
396 }
397
398 /**
399 * Return css classes string as per size
400 *
401 * @param WPSC_custom_field $cf - custom field.
402 * @param WPSC_TFF $tff - ticket form field.
403 * @return string
404 */
405 public static function get_tff_classes( $cf, $tff ) {
406
407 $classes = 'wpsc-tff ' . $cf->slug . ' wpsc-xs-12 ';
408 switch ( $tff['width'] ) {
409 case '1/3':
410 $classes .= 'wpsc-sm-4 wpsc-md-4 wpsc-lg-4 ';
411 break;
412
413 case 'half':
414 $classes .= 'wpsc-sm-6 wpsc-md-6 wpsc-lg-6 ';
415 break;
416
417 case 'full':
418 $classes .= 'wpsc-sm-12 wpsc-md-12 wpsc-lg-12 ';
419 break;
420 }
421 $classes .= $tff['is-required'] ? 'required ' : '';
422 $visibility = WPSC_TFF::get_visibility( $tff, true );
423 $classes .= $visibility ? 'wpsc-hidden conditional' : 'wpsc-visible';
424
425 return $classes;
426 }
427
428 /**
429 * Sort an array with key
430 *
431 * @param array $array_name - macro array.
432 * @param array $on - title.
433 * @param array $order - SORT_ASC.
434 * @return array
435 */
436 public static function array_sort( $array_name, $on, $order = SORT_ASC ) {
437
438 $new_array = array();
439 $sortable_array = array();
440
441 if ( count( $array_name ) > 0 ) {
442 foreach ( $array_name as $k => $v ) {
443 if ( is_array( $v ) ) {
444 foreach ( $v as $k2 => $v2 ) {
445 if ( $k2 == $on ) {
446 $sortable_array[ $k ] = $v2;
447 }
448 }
449 } else {
450 $sortable_array[ $k ] = $v;
451 }
452 }
453 switch ( $order ) {
454 case SORT_ASC:
455 asort( $sortable_array );
456 break;
457 case SORT_DESC:
458 arsort( $sortable_array );
459 break;
460 }
461 foreach ( $sortable_array as $k => $v ) {
462 $new_array[ $k ] = $array_name[ $k ];
463 }
464 }
465 return $new_array;
466 }
467
468 /**
469 * Convert date from WP timezone date to UTC equivalant date
470 *
471 * @param string $date_str - date to UTC equivalant date.
472 * @return DateTime
473 */
474 public static function get_utc_date_str( $date_str ) {
475
476 $tz = wp_timezone();
477 $date = DateTime::createFromFormat( 'Y-m-d H:i:s', $date_str, $tz );
478 $date->setTimezone( new DateTimeZone( '+0000' ) );
479 return $date->format( 'Y-m-d H:i:s' );
480 }
481
482 /**
483 * Check whether given date is valid or not
484 *
485 * @param string $date - date string.
486 * @param string $format - date format.
487 * @return boolean
488 */
489 public static function is_valid_date( $date, $format = 'Y-m-d' ) {
490 if ( preg_match( '/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}(:\d{2})?$/', $date ) ) {
491 return self::is_valid_datetime( $date );
492 }
493 $d = DateTime::createFromFormat( $format, $date );
494 return $d && $d->format( $format ) === $date;
495 }
496
497 /**
498 * Check whether given datetime is valid or not
499 *
500 * @param string $datetime - datetime string.
501 * @return boolean
502 */
503 public static function is_valid_datetime( $datetime ) {
504
505 if ( ! is_string( $datetime ) ) {
506 return false;
507 }
508 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $datetime ) ) {
509 return self::is_valid_date( $datetime );
510 }
511 $datetime = trim( $datetime );
512 $formats = array(
513 'Y-m-d H:i',
514 'Y-m-d H:i:s',
515 );
516
517 foreach ( $formats as $format ) {
518 $dt = DateTime::createFromFormat( $format, $datetime );
519 if (
520 $dt !== false &&
521 $dt->format( $format ) === $datetime
522 ) {
523 return true;
524 }
525 }
526 return false;
527 }
528
529 /**
530 * Check whether given time is valid or not
531 *
532 * @param string $time - time string.
533 * @return boolean
534 */
535 public static function is_valid_time( $time ) {
536 if ( ! is_string( $time ) ) {
537 return false;
538 }
539
540 // Accept HH:MM or HH:MM:SS (24-hour).
541 return (bool) preg_match(
542 '/^(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/',
543 $time
544 );
545 }
546
547 /**
548 * Normalize date string to UTC date string
549 *
550 * @param string $date - date string.
551 * @param boolean $end_of_day - end of day flag.
552 * @return string|false
553 */
554 public static function normalize_date( $date, $end_of_day = false ) {
555 if ( ! is_string( $date ) ) {
556 return false;
557 }
558
559 $date = trim( $date );
560
561 $formats = array(
562 'Y-m-d H:i:s',
563 'Y-m-d',
564 );
565
566 foreach ( $formats as $format ) {
567 $dt = DateTime::createFromFormat( $format, $date );
568 if ( $dt instanceof DateTime ) {
569 if ( $format === 'Y-m-d' ) {
570 $dt->setTime(
571 $end_of_day ? 23 : 0,
572 $end_of_day ? 59 : 0,
573 $end_of_day ? 59 : 0
574 );
575 }
576 return self::get_utc_date_str( $dt->format( 'Y-m-d H:i:s' ) );
577 }
578 }
579
580 return false;
581 }
582
583 /**
584 * Return SQL BETWEEN string
585 *
586 * @param string $column - column name.
587 * @param string $from - from value.
588 * @param string $to - to value.
589 * @return string
590 */
591 public static function sql_between( $column, $from, $to ) {
592 return sprintf(
593 "%s BETWEEN '%s' AND '%s'",
594 $column,
595 esc_sql( $from ),
596 esc_sql( $to )
597 );
598 }
599
600
601 /**
602 * Get new ticket url
603 *
604 * @return string
605 */
606 public static function get_new_ticket_url() {
607
608 $page_settings = get_option( 'wpsc-gs-page-settings' );
609 $url = '';
610
611 if ( $page_settings['new-ticket-page'] == 'default' && $page_settings['support-page'] ) {
612
613 $url = get_permalink( $page_settings['support-page'] );
614 $url = add_query_arg( array( 'wpsc-section' => 'new-ticket' ), $url );
615
616 } elseif ( $page_settings['new-ticket-page'] == 'custom' && $page_settings['new-ticket-url'] ) {
617
618 $url = $page_settings['new-ticket-url'];
619 }
620
621 return apply_filters( 'wpsc_get_new_ticket_url', $url );
622 }
623
624 /**
625 * Load package for third-party php library without composer
626 *
627 * @param string $dir - directory path of package.
628 * @return void
629 */
630 public static function load_library( $dir ) {
631
632 $composer = json_decode( file_get_contents( "$dir/composer.json" ), 1 ); // phpcs:ignore
633 $namespaces = $composer['autoload']['psr-4'];
634
635 // Foreach namespace specified in the composer, load the given classes.
636 foreach ( $namespaces as $namespace => $classpaths ) {
637 if ( ! is_array( $classpaths ) ) {
638 $classpaths = array( $classpaths );
639 }
640 spl_autoload_register(
641 function ( $classname ) use ( $namespace, $classpaths, $dir ) {
642 // Check if the namespace matches the class we are looking for.
643 if ( preg_match( '#^' . preg_quote( $namespace, '/' ) . '#', $classname ) ) {
644 // Remove the namespace from the file path since it's psr4.
645 $classname = str_replace( $namespace, '', $classname );
646 $filename = preg_replace( '#\\\\#', '/', $classname ) . '.php';
647 foreach ( $classpaths as $classpath ) {
648 $fullpath = $dir . '/' . $classpath . "/$filename";
649 if ( file_exists( $fullpath ) ) {
650 include_once $fullpath;
651 }
652 }
653 }
654 }
655 );
656 }
657 }
658
659 /**
660 * Create a random string
661 *
662 * @param integer $length - random strig lenght.
663 * @return string
664 */
665 public static function get_random_string( $length = 8 ) {
666
667 $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
668 $string = '';
669 for ( $i = 0; $i < $length; $i++ ) {
670 $string .= $characters[ wp_rand( 0, strlen( $characters ) - 1 ) ];
671 }
672 return $string;
673 }
674
675 /**
676 * Perform sum of date intervals
677 *
678 * @param array $arr - date interval array.
679 * @return DateInterval
680 */
681 public static function date_interval_sum( $arr ) {
682
683 $response = $arr[0];
684 $arrau_count = count( $arr );
685 for ( $i = 1; $i < $arrau_count; $i++ ) {
686 $today = new DateTime();
687 $sum_today = clone $today;
688 $sum_today->add( $response );
689 $sum_today->add( $arr[ $i ] );
690 $response = $today->diff( $sum_today );
691 }
692 return $response;
693 }
694
695 /**
696 * Return string representation of date interval
697 *
698 * @param DateInterval $diff - date interval.
699 * @return string
700 */
701 public static function date_interval_to_string( $diff ) {
702
703 $str = 'P';
704
705 if ( $diff->days ) {
706
707 $str .= $diff->format( '%aD' );
708
709 } elseif ( $diff->d ) {
710
711 $str .= $diff->format( '%dD' );
712
713 }
714
715 if ( $diff->h || $diff->i ) {
716
717 $str .= 'T';
718 $str .= $diff->h ? $diff->format( '%hH' ) : '';
719 $str .= $diff->i ? $diff->format( '%iM' ) : '';
720 }
721
722 if ( $str === 'P' ) {
723 $str = 'PT0M';
724 }
725
726 return $str;
727 }
728
729 /**
730 * Return readable time interval so that we can print this on user interface.
731 *
732 * @param DateInterval $diff - time interval.
733 * @return string
734 */
735 public static function date_interval_to_readable( $diff ) {
736
737 $arr = array();
738
739 // days.
740 if ( $diff->days ) {
741
742 $arr[] = sprintf(
743 /* translators: %d: number of days */
744 __( '%dd', 'supportcandy' ),
745 $diff->format( '%a' )
746 );
747
748 } elseif ( $diff->d ) {
749
750 $arr[] = sprintf(
751 /* translators: %d: number of days */
752 __( '%dd', 'supportcandy' ),
753 $diff->format( '%d' )
754 );
755
756 }
757
758 // hours.
759 if ( $diff->h ) {
760 $arr[] = sprintf(
761 /* translators: %d: number of hours */
762 __( '%dh', 'supportcandy' ),
763 $diff->format( '%h' )
764 );
765 }
766
767 // minutes.
768 if ( $diff->i ) {
769 $arr[] = sprintf(
770 /* translators: %d: number of minutes */
771 __( '%dm', 'supportcandy' ),
772 $diff->format( '%i' )
773 );
774 }
775
776 return $arr ? implode( ' ', $arr ) : '0m';
777 }
778
779 /**
780 * Return time interval object from readable string (usually comes from user interface)
781 *
782 * @param string $str - string to date format.
783 * @return DateInterval
784 */
785 public static function readable_to_date_interval( $str ) {
786
787 // remove spaces in between.
788 $str = str_replace( ' ', '', $str );
789
790 // invalid if not given.
791 if ( ! $str ) {
792 return false;
793 }
794
795 // validate format.
796 $flag = preg_match( '/^(\d*d)?(\d*h)?(\d*m)?$/', $str, $matches );
797 if ( ! $flag ) {
798 return false;
799 }
800
801 // build interval string.
802 $str = 'P';
803 $str .= $matches[1] ? strtoupper( $matches[1] ) : '';
804 if ( $matches[2] || $matches[3] ) {
805 $str .= 'T';
806 $str .= $matches[2] ? strtoupper( $matches[2] ) : '';
807 $str .= $matches[3] ? strtoupper( $matches[3] ) : '';
808 }
809
810 // return dateinterval object.
811 return new DateInterval( $str );
812 }
813
814 /**
815 * Return time ago string for highest unit. For example, if difference is 2hr 30min 34sec then return 2 hour ago.
816 *
817 * @param DateInterval $diff - date interval object.
818 * @return string
819 */
820 public static function date_interval_highest_unit_ago( $diff ) {
821
822 // return years if any.
823 if ( $diff->y ) {
824 return sprintf(
825 /* translators: %d: number of years */
826 __( '%d years ago', 'supportcandy' ),
827 intval( $diff->format( '%y' ) )
828 );
829 }
830
831 // return months if any.
832 if ( $diff->m ) {
833 return sprintf(
834 /* translators: %d: number of months */
835 __( '%d months ago', 'supportcandy' ),
836 intval( $diff->format( '%m' ) )
837 );
838 }
839
840 // return days if any.
841 $days = $diff->days ? intval( $diff->format( '%a' ) ) : intval( $diff->format( '%d' ) );
842 if ( $days ) {
843 return sprintf(
844 /* translators: %d: number of days */
845 __( '%d days ago', 'supportcandy' ),
846 $days
847 );
848 }
849
850 // return hours if any.
851 if ( $diff->h ) {
852 return sprintf(
853 /* translators: %d: number of hours */
854 __( '%d hours ago', 'supportcandy' ),
855 intval( $diff->format( '%h' ) )
856 );
857 }
858
859 // return minutes if any.
860 if ( $diff->i ) {
861 return sprintf(
862 /* translators: %d: number of minutes */
863 __( '%d minutes ago', 'supportcandy' ),
864 intval( $diff->format( '%i' ) )
865 );
866 }
867
868 // return seconds if any.
869 if ( $diff->s ) {
870 return sprintf(
871 /* translators: %d: number of seconds */
872 __( '%d seconds ago', 'supportcandy' ),
873 intval( $diff->format( '%s' ) )
874 );
875 }
876
877 return __( 'Just now', 'supportcandy' );
878 }
879
880 /**
881 * Return day name
882 *
883 * @param int $day - week days.
884 * @return string
885 */
886 public static function get_day_name( $day ) {
887
888 $days = array(
889 1 => wpsc__( 'Monday' ),
890 2 => wpsc__( 'Tuesday' ),
891 3 => wpsc__( 'Wednesday' ),
892 4 => wpsc__( 'Thursday' ),
893 5 => wpsc__( 'Friday' ),
894 6 => wpsc__( 'Saturday' ),
895 7 => wpsc__( 'Sunday' ),
896 );
897 return isset( $days[ $day ] ) ? $days[ $day ] : '';
898 }
899
900 /**
901 * Return month name
902 *
903 * @param int $month - month names.
904 * @return string
905 */
906 public static function get_month_name( $month ) {
907
908 $months = array(
909 1 => wpsc__( 'January' ),
910 2 => wpsc__( 'February' ),
911 3 => wpsc__( 'March' ),
912 4 => wpsc__( 'April' ),
913 5 => wpsc__( 'May' ),
914 6 => wpsc__( 'June' ),
915 7 => wpsc__( 'July' ),
916 8 => wpsc__( 'August' ),
917 9 => wpsc__( 'September' ),
918 10 => wpsc__( 'October' ),
919 11 => wpsc__( 'November' ),
920 12 => wpsc__( 'December' ),
921 );
922 return isset( $months[ $month ] ) ? $months[ $month ] : '';
923 }
924
925 /**
926 * Sanitize SQL key to allow only possible lowercase keys with joins.
927 *
928 * @param string $key - key to sanitize.
929 * @return string
930 */
931 public static function sanitize_sql_key( $key ) {
932
933 $sanitized_key = '';
934
935 if ( is_scalar( $key ) ) {
936 $key = strtolower( $key );
937 if ( preg_match( '/^([a-z]{1,2}\.)?[a-z0-9_]+$/', $key ) ) {
938 $sanitized_key = $key;
939 }
940 }
941
942 return $sanitized_key;
943 }
944
945 /**
946 * Sanitize date string e.g. "2022-12-25" and return with datetime format
947 *
948 * @param string $date - date string to be sanitized.
949 * @return string
950 */
951 public static function sanitize_date( $date ) {
952
953 if ( ! $date ) {
954 return $date;
955 }
956
957 if ( ! preg_match( '/\d{4}-\d{2}-\d{2}/', $date ) ) {
958 return '';
959 }
960
961 $format = 'Y-m-d';
962 $d = DateTime::createFromFormat( $format, $date );
963 return $d && $d->format( $format ) == $date ? $date . ' 00:00:00' : '';
964 }
965
966 /**
967 * Sanitize date string e.g. "2022-12-25 12:00:00"
968 *
969 * @param string $date - date string to be sanitized.
970 * @return string
971 */
972 public static function sanitize_datetime( $date ) {
973
974 if ( ! $date ) {
975 return $date;
976 }
977
978 if ( ! preg_match( '/\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}/', $date ) ) {
979 return '';
980 }
981
982 $format = 'Y-m-d H:i';
983 $d = DateTime::createFromFormat( $format, $date );
984 return $d && $d->format( $format ) == $date ? $date . ':00' : '';
985 }
986
987 /**
988 * Sanitize time string e.g. "12:31"
989 *
990 * @param string $time - time string to be sanitized.
991 * @return string
992 */
993 public static function sanitize_time( $time ) {
994
995 if ( ! $time ) {
996 return $time;
997 }
998
999 if ( ! preg_match( '/(\d{2}):(\d{2})/', $time, $matches ) ) {
1000 return '';
1001 }
1002
1003 return intval( $matches[1] ) < 24 && intval( $matches[2] ) < 60 ? $time : '';
1004 }
1005
1006 /**
1007 * Sanitiize email string
1008 *
1009 * @param string $email - email string to be sanitized.
1010 * @return string
1011 */
1012 public static function sanitize_email( $email ) {
1013
1014 return filter_var( $email, FILTER_VALIDATE_EMAIL ) ? $email : '';
1015 }
1016
1017 /**
1018 * Sanitiize url string
1019 *
1020 * @param string $url - email string to be sanitized.
1021 * @return string
1022 */
1023 public static function sanitize_url( $url ) {
1024
1025 return filter_var( $url, FILTER_VALIDATE_URL ) ? $url : '';
1026 }
1027
1028 /**
1029 * Sanitize attachment id came as user input
1030 *
1031 * @param integer $id - attachment id.
1032 * @return integer|boolean
1033 */
1034 public static function sanitize_attachment( $id ) {
1035
1036 if ( ! $id ) {
1037 return false;
1038 }
1039
1040 $attachment = new WPSC_Attachment( $id );
1041 return $attachment->id ? $id : false;
1042 }
1043
1044 /**
1045 * Sanitize option id provided by user
1046 *
1047 * @param integer $id - option id.
1048 * @param WPSC_Custom_Field $cf - custom field object.
1049 * @param array $options - array of option objects.
1050 * @return integer|boolean
1051 */
1052 public static function sanitize_option( $id, $cf, $options ) {
1053
1054 $option = new WPSC_Option( $id );
1055 $options = array_filter(
1056 array_map(
1057 fn( $option ) => $option->id ? $option->id : false,
1058 $options
1059 )
1060 );
1061 return $option->id && in_array( $option->id, $options ) ? $id : false;
1062 }
1063
1064 /**
1065 * Get current langauge iso code. Use for datepicker library.
1066 *
1067 * @return string
1068 */
1069 public static function get_locale_iso() {
1070
1071 $locale = substr( get_locale(), 0, 2 );
1072 if ( $locale == 'el' ) {
1073 $locale = 'gr';
1074 }
1075 return $locale;
1076 }
1077
1078 /**
1079 * Get anonymous customer id.
1080 *
1081 * @return object
1082 */
1083 public static function anonymous_customer() {
1084
1085 global $wpdb;
1086 $id = get_option( 'wpsc-anonymous-user-id' );
1087 if ( ! $id ) {
1088
1089 // add anonymuos customer to customer table.
1090 $success = $wpdb->insert(
1091 $wpdb->prefix . 'psmsc_customers',
1092 array(
1093 'name' => 'Anonymous',
1094 'email' => 'anonymous@anonymous.anonymous',
1095 )
1096 );
1097 if ( ! $success ) {
1098 return false;
1099 }
1100
1101 // Store anonymous customer ID in option.
1102 $id = $wpdb->insert_id;
1103 update_option( 'wpsc-anonymous-user-id', $id );
1104 }
1105
1106 $anonymous = new WPSC_Customer( $id );
1107 return $anonymous;
1108 }
1109
1110 /**
1111 * Calculate date range
1112 *
1113 * @param string $date - date string.
1114 * @return array
1115 */
1116 public static function get_dashboard_date_range( $date ) {
1117
1118 $today = new DateTime();
1119 switch ( $date ) {
1120 case 'today':
1121 // Start of today at 00:00:00.
1122 $today_start = clone $today;
1123 $today_start->setTime( 0, 0, 0 );
1124 // End of today with the current time.
1125 $today_end = clone $today;
1126 return array( $today_start->format( 'Y-m-d H:i:s' ), $today_end->format( 'Y-m-d H:i:s' ) );
1127
1128 case 'yesterday':
1129 // Start of yesterday at 00:00:00.
1130 $yesterday_start = clone $today;
1131 $yesterday_start->modify( '-1 day' )->setTime( 0, 0, 0 );
1132 // End of yesterday at 23:59:59.
1133 $yesterday_end = clone $yesterday_start;
1134 $yesterday_end->setTime( 23, 59, 59 );
1135 return array( $yesterday_start->format( 'Y-m-d H:i:s' ), $yesterday_end->format( 'Y-m-d H:i:s' ) );
1136
1137 case 'last-7':
1138 // Calculate last 7 days from today's date.
1139 $last7_days_start = clone $today;
1140 $last7_days_start->modify( '-6 days' )->setTime( 0, 0, 0 );
1141 $last7_days_end = clone $today;
1142 $last7_days_end->setTime( 23, 59, 59 );
1143 return array( $last7_days_start->format( 'Y-m-d H:i:s' ), $last7_days_end->format( 'Y-m-d H:i:s' ) );
1144
1145 case 'this-week':
1146 // Calculate this week's start and end (Sunday to Saturday).
1147 $week_start = clone $today;
1148 $week_start->modify( 'last Sunday' )->setTime( 0, 0, 0 );
1149 $week_end = clone $week_start;
1150 $week_end->modify( 'next Saturday' )->setTime( 23, 59, 59 );
1151 return array( $week_start->format( 'Y-m-d H:i:s' ), $week_end->format( 'Y-m-d H:i:s' ) );
1152
1153 case 'last-week':
1154 // Calculate last week's start and end (Sunday to Saturday).
1155 $last_week_start = clone $today;
1156 $last_week_start->modify( 'last Sunday' )->sub( new DateInterval( 'P7D' ) )->setTime( 0, 0, 0 );
1157 $last_week_end = clone $last_week_start;
1158 $last_week_end->modify( 'next Saturday' )->setTime( 23, 59, 59 );
1159 return array( $last_week_start->format( 'Y-m-d H:i:s' ), $last_week_end->format( 'Y-m-d H:i:s' ) );
1160
1161 case 'last-30-days':
1162 // Calculate last 30 days from today's date.
1163 $last30_days_start = clone $today;
1164 $last30_days_start->modify( '-29 days' )->setTime( 0, 0, 0 );
1165 return array( $last30_days_start->format( 'Y-m-d H:i:s' ), $today->format( 'Y-m-d H:i:s' ) );
1166
1167 case 'this-month':
1168 // Calculate this month's start and end date.
1169 $start_month = clone $today;
1170 $start_month->modify( 'first day of this month' )->setTime( 0, 0, 0 );
1171 $end_month = clone $today;
1172 $end_month->setTime( 23, 59, 59 );
1173 return array( $start_month->format( 'Y-m-d H:i:s' ), $end_month->format( 'Y-m-d H:i:s' ) );
1174
1175 case 'this-quarter':
1176 // Calculate this quarter's start and end date.
1177 $month = (int) $today->format( 'n' );
1178 $start_quarter = clone $today;
1179 $start_quarter->setDate( $today->format( 'Y' ), floor( ( $month - 1 ) / 3 ) * 3 + 1, 1 )->setTime( 0, 0, 0 );
1180 return array( $start_quarter->format( 'Y-m-d H:i:s' ), $today->format( 'Y-m-d H:i:s' ) );
1181
1182 case 'this-year':
1183 // Calculate this year's start date to today.
1184 $start_year = clone $today;
1185 $start_year->setDate( $today->format( 'Y' ), 1, 1 )->setTime( 0, 0, 0 );
1186 return array( $start_year->format( 'Y-m-d H:i:s' ), $today->format( 'Y-m-d H:i:s' ) );
1187
1188 case 'last-month':
1189 // Calculate last month's start and end date.
1190 $last_month_start = clone $today;
1191 $last_month_start->modify( 'first day of last month' )->setTime( 0, 0, 0 );
1192 $last_month_end = clone $today;
1193 $last_month_end->modify( 'last day of last month' )->setTime( 23, 59, 59 );
1194 return array( $last_month_start->format( 'Y-m-d H:i:s' ), $last_month_end->format( 'Y-m-d H:i:s' ) );
1195
1196 case 'last-quarter':
1197 // Last quarter: from the first to the last day of the previous quarter.
1198 $current_month = (int) $today->format( 'n' );
1199
1200 // Calculate the first month of the previous quarter.
1201 $last_quarter_start_month = 3 * ( floor( ( $current_month - 1 ) / 3 ) ) - 2;
1202 if ( $last_quarter_start_month <= 0 ) {
1203 // If the calculated month is 0 or negative, it means we are in Q1, so the last quarter is Q4 of the previous year.
1204 $last_quarter_start_month += 12;
1205 $start_last_quarter = ( clone $today )->setDate( $today->format( 'Y' ) - 1, $last_quarter_start_month, 1 )->setTime( 0, 0, 0 );
1206 } else {
1207 $start_last_quarter = ( clone $today )->setDate( $today->format( 'Y' ), $last_quarter_start_month, 1 )->setTime( 0, 0, 0 );
1208 }
1209
1210 // Calculate the end of the last quarter.
1211 $end_last_quarter = clone $start_last_quarter;
1212 $end_last_quarter->modify( '+2 months' )->modify( 'last day of this month' )->setTime( 23, 59, 59 );
1213
1214 return array( $start_last_quarter->format( 'Y-m-d H:i:s' ), $end_last_quarter->format( 'Y-m-d H:i:s' ) );
1215
1216 case 'last-year':
1217 // Calculate last year's start and end date.
1218 $start_last_year = clone $today;
1219 $start_last_year->setDate( $today->format( 'Y' ) - 1, 1, 1 )->setTime( 0, 0, 0 );
1220 $end_last_year = clone $start_last_year;
1221 $end_last_year->setDate( $today->format( 'Y' ) - 1, 12, 31 )->setTime( 23, 59, 59 );
1222 return array( $start_last_year->format( 'Y-m-d H:i:s' ), $end_last_year->format( 'Y-m-d H:i:s' ) );
1223 }
1224 }
1225
1226 /**
1227 * Generate random colors.
1228 *
1229 * @return string
1230 */
1231 public static function generate_random_color() {
1232 $min_luminance = 0.7; // Adjust this value based on your preference.
1233
1234 do {
1235 $color = wp_rand( 0x000000, 0xFFFFFF );
1236 $red = ( $color >> 16 ) & 0xFF;
1237 $green = ( $color >> 8 ) & 0xFF;
1238 $blue = $color & 0xFF;
1239
1240 // Calculate luminance (brightness).
1241 $luminance = ( 0.299 * $red + 0.587 * $green + 0.114 * $blue ) / 255;
1242
1243 } while ( $luminance < $min_luminance );
1244
1245 return '#' . dechex( $color );
1246 }
1247
1248 /**
1249 * Get ticket url depending on view using ticket id.
1250 *
1251 * @param int $ticket_id - ticket id.
1252 * @param string $view - view - frontend/backend.
1253 * @return string
1254 */
1255 public static function get_ticket_url( $ticket_id, $view ) {
1256
1257 $ticket_id = absint( $ticket_id );
1258 $view = (int) $view;
1259 $url = '';
1260
1261 if ( ! $ticket_id ) {
1262 return $url;
1263 }
1264
1265 $page_settings = get_option( 'wpsc-gs-page-settings', array() );
1266
1267 // Detect ticket type.
1268 $is_archive = false;
1269 $ticket = new WPSC_Ticket( $ticket_id );
1270 if ( ! $ticket->id ) {
1271 $ticket = new WPSC_Archive_Ticket( $ticket_id );
1272 if ( ! $ticket->id ) {
1273 return $url;
1274 }
1275 $is_archive = true;
1276 }
1277
1278 $type = $is_archive
1279 ? 'wpsc-archive-tickets&section=archive-ticket-list'
1280 : 'wpsc-tickets&section=ticket-list';
1281
1282 $admin_url = admin_url( 'admin.php?page=' . $type . '&id=' . $ticket_id );
1283
1284 // Backend view always wins.
1285 if ( $view === 0 || $is_archive ) {
1286 return apply_filters( 'wpsc_get_ticket_url_by_view', $admin_url, $ticket_id, $view );
1287 }
1288
1289 // Frontend view (normal tickets only).
1290 if ( $view === 1 ) {
1291
1292 $ticket_url_page = $page_settings['ticket-url-page'] ?? '';
1293 $support_page = absint( $page_settings['support-page'] ?? 0 );
1294 $open_page = absint( $page_settings['open-ticket-page'] ?? 0 );
1295
1296 if ( $ticket_url_page === 'support-page' && $support_page ) {
1297
1298 $url = add_query_arg(
1299 array(
1300 'wpsc-section' => 'ticket-list',
1301 'ticket-id' => $ticket_id,
1302 ),
1303 get_permalink( $support_page )
1304 );
1305
1306 } elseif ( $ticket_url_page === 'open-ticket-page' && $open_page && ! empty( $ticket->auth_code ) ) {
1307
1308 $url = add_query_arg(
1309 array(
1310 'ticket-id' => $ticket_id,
1311 'auth-code' => $ticket->auth_code,
1312 ),
1313 get_permalink( $open_page )
1314 );
1315 }
1316 }
1317
1318 if ( empty( $url ) ) {
1319 $url = $admin_url;
1320 }
1321
1322 return apply_filters( 'wpsc_get_ticket_url_by_view', $url, $ticket_id, $view );
1323 }
1324
1325 /**
1326 * Get unique, non-empty closed statuses from merged settings.
1327 *
1328 * Merges advanced and general settings, filters out empty values,
1329 * ensures uniqueness, and returns an indexed array.
1330 *
1331 * @return array Unique, non-empty closed statuses.
1332 */
1333 public static function get_closed_statuses() {
1334
1335 $tl_ms_advance_settings = get_option( 'wpsc-tl-ms-advanced' );
1336 $general_settings = get_option( 'wpsc-gs-general' );
1337
1338 // Merge, filter non-empty, get unique values, and convert all to string.
1339 $closed_statuses = array_unique(
1340 array_filter(
1341 array_merge(
1342 $tl_ms_advance_settings['closed-ticket-statuses'],
1343 (array) $general_settings['close-ticket-status']
1344 ),
1345 function ( $statuses ) {
1346 return ! empty( $statuses );
1347 }
1348 )
1349 );
1350 return array_map( 'strval', $closed_statuses ); // Ensure indexed array.
1351 }
1352
1353 /**
1354 * Check whether any SupportCandy addon is active.
1355 *
1356 * @return bool
1357 */
1358 public static function is_paid_customer() {
1359
1360 $cache_key = 'wpsc_is_paid_customer';
1361 $cached_status = get_option( $cache_key, null );
1362
1363 if ( null !== $cached_status ) {
1364 return (bool) $cached_status;
1365 }
1366
1367 return self::update_paid_customer_status_option();
1368 }
1369
1370 /**
1371 * Update the paid customer status option based on active addons.
1372 *
1373 * @param string $plugin - Optional plugin path to exclude from check (used during activation/deactivation).
1374 * @return bool Updated paid customer status.
1375 */
1376 public static function update_paid_customer_status_option( $plugin = '' ) {
1377 $cache_key = 'wpsc_is_paid_customer';
1378
1379 $addon_plugins = array(
1380 'wpsc-agentgroup/wpsc-agentgroup.php',
1381 'wpsc-assign-agent-rules/wpsc-assign-agent-rules.php',
1382 'wpsc-automatic-close-ticket/wpsc-automatic-close-ticket.php',
1383 'wpsc-canned-reply/wpsc-canned-reply.php',
1384 'wpsc-edd/wpsc-edd.php',
1385 'wpsc-email-marketing-tools/wpsc-email-marketing-tools-integration.php',
1386 'wpsc-email-piping/wpsc-email-piping.php',
1387 'wpsc-export-ticket/wpsc-export-ticket.php',
1388 'wpsc-gravity-forms/wpsc-gravity-form-integration.php',
1389 'wpsc-lms/wpsc-lms.php',
1390 'wpsc-pressapps-knowledge-base/wpsc-pressapps-knowledge-base.php',
1391 'wpsc-print-ticket/wpsc-print-ticket.php',
1392 'wpsc-private-credentials/wpsc_private_credentials.php',
1393 'wpsc-productivity-suite/wpsc-productivity-suite.php',
1394 'wpsc-reports/wpsc-reports.php',
1395 'wpsc-satisfaction-survey/wpsc-satisfaction-survey.php',
1396 'wpsc-schedule-tickets/wpsc-schedule-tickets.php',
1397 'wpsc-sla/wpsc-sla.php',
1398 'wpsc-slack/wpsc-slack.php',
1399 'wpsc-timer/wpsc-timer.php',
1400 'wpsc-ultimate-faq/wpsc-ultimate-faq.php',
1401 'wpsc-usergroup/wpsc-usergroup.php',
1402 'wpsc-webhooks/wpsc-webhooks.php',
1403 'wpsc-woocommerce/wpsc-woocommerce.php',
1404 'wpsc-workflows/wpsc-workflows.php',
1405 );
1406
1407 $active_plugins = (array) get_option( 'active_plugins', array() );
1408
1409 if ( current_filter() === 'deactivated_plugin' && ! empty( $plugin ) ) {
1410 $active_plugins = array_diff( $active_plugins, array( $plugin ) );
1411 }
1412
1413 if ( is_multisite() ) {
1414 $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
1415 $active_plugins = array_merge( $active_plugins, array_keys( $active_network_plugins ) );
1416 }
1417
1418 $is_paid_customer = ! empty( array_intersect( $addon_plugins, $active_plugins ) );
1419 update_option( $cache_key, $is_paid_customer ? 1 : 0 );
1420 return (bool) $is_paid_customer;
1421 }
1422 }
1423 endif;
1424
1425 WPSC_Functions::init();
1426