PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.4.7
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.4.7
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.7, at includes/class-wpsc-functions.php

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