PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.64
Timetics – Appointment Booking Calendar & Scheduling v1.0.64
1.0.64 1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 All 65 releases
timetics / utils / global-helper.php

global-helper.php in Timetics – Appointment Booking Calendar & Scheduling 1.0.64, at utils/global-helper.php

1,137 lines 35.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined( 'ABSPATH' ) || exit;
3
4 use function Timetics\timetics;
5 use Timetics\Core\Bookings\Booking;
6 use Timetics\Core\Integrations\Google\Client;
7 use Timetics\Core\Integrations\Google\Service\Calendar;
8
9 /**
10 * Get timezone list.
11 *
12 * @since 1.0.0
13 *
14 * @return array
15 */
16 if ( ! function_exists( 'timetics_get_timezone_list' ) ) {
17 function timetics_get_timezone_list() {
18 $tzlist = wp_timezone_choice( 'UTC' );
19 $doc = new DOMDocument();
20 $doc->loadHTML( $tzlist );
21
22 $elements = $doc->getElementsByTagName( 'option' );
23 $timezones = [];
24
25 if ( ! empty( $elements ) ) {
26 foreach ( $elements as $element ) {
27 $data = [
28 'value' => $element->getAttribute( 'value' ),
29 'name' => $element->textContent, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
30 ];
31
32 $timezones[] = $data;
33 }
34 }
35
36 return $timezones;
37 }
38 }
39
40 if ( ! function_exists( 'timetics_get_google_access_token' ) ) {
41 /**
42 * Get google access token
43 *
44 * @param integer $user_id Authenticate user id
45 *
46 * @since 1.0.0
47 *
48 * @return string
49 */
50 function timetics_get_google_access_token( $user_id = 0 ) {
51 $data = timetics_get_google_auth( $user_id );
52
53 if ( empty( $data ) || empty( $data['access_token'] ) ) {
54 return false;
55 }
56
57 $expires_in = isset( $data['expires_in'] ) ? (int) $data['expires_in'] : 0;
58
59 // Token still valid (30s safety margin) — use it as-is.
60 if ( ( $expires_in - 30 ) > time() ) {
61 return $data['access_token'];
62 }
63
64 // Needs a refresh.
65 $refresh_token = timetics_get_google_refresh_token( $user_id );
66
67 if ( empty( $refresh_token ) ) {
68 return false;
69 }
70
71 $client = timetics_get_google_client();
72 $response = $client->fetch_access_token_with_refresh_token( $refresh_token );
73
74 if ( is_wp_error( $response ) ) {
75 $error_data = $response->get_error_data();
76 $transient = ! is_array( $error_data ) || ! empty( $error_data['transient'] );
77
78 if ( $transient ) {
79 return false;
80 }
81
82 // Permanent failure (invalid_grant): the refresh token is dead and the account genuinely needs to be re-connected. Flag it so the UI can reflect the real state.
83 update_user_meta( $user_id, 'timetics_google_auth_error', $response->get_error_code() );
84
85 return false;
86 }
87
88 // Persisting a fresh credential also clears any stale auth-error flag
89 timetics_update_google_auth( $user_id, $response );
90
91 return $response['access_token'];
92 }
93 }
94
95 if ( ! function_exists( 'timetics_get_google_refresh_token' ) ) {
96
97 /**
98 * Get google auth refresh token
99 *
100 * @param integer $user_id Authenticate user id
101 *
102 * @return string
103 */
104 function timetics_get_google_refresh_token( $user_id ) {
105 return get_user_meta( $user_id, 'timetics_google_refresh_token', true );
106 }
107 }
108
109 if ( ! function_exists( 'timetics_get_auth_redirect_uri' ) ) {
110 /**
111 * Get auth redirect uri
112 *
113 * @param string $auth
114 *
115 * @return string
116 */
117 function timetics_get_auth_redirect_uri( $auth = '' ) {
118 return site_url( 'timetics-integration/' . $auth );
119 }
120 }
121
122 if ( ! function_exists( 'timetics_update_google_auth' ) ) {
123 /**
124 * Update google auth token
125 *
126 * @param integer $user_id
127 * @param array $data
128 *
129 * @return void
130 */
131 function timetics_update_google_auth( $user_id = 0, $data = [] ) {
132 if ( empty( $data ) || ! is_array( $data ) ) {
133 return;
134 }
135
136 if ( ! empty( $data['code'] ) ) {
137 update_user_meta( $user_id, 'timetics_google_auth_code', $data['code'] );
138 }
139
140 if ( ! empty( $data['refresh_token'] ) ) {
141 update_user_meta( $user_id, 'timetics_google_refresh_token', $data['refresh_token'] );
142 }
143
144 // Google returns expires_in as a lifetime in seconds (~3600).
145 // Guard against a missing/zero value: storing time() alone would make the
146 // token look permanently expired, forcing a refresh on every request and
147 // hammering Google's token endpoint until it rate-limits the app.
148 $lifetime = ! empty( $data['expires_in'] ) ? (int) $data['expires_in'] : 3600;
149 $data['expires_in'] = time() + $lifetime;
150
151 update_user_meta( $user_id, 'timetics_google_auth', $data );
152 delete_user_meta( $user_id, 'timetics_google_auth_error' );
153 }
154 }
155
156 if ( ! function_exists( 'timetics_get_google_auth' ) ) {
157 /**
158 * Get google auth
159 *
160 * @param integer $user_id
161 *
162 * @return array
163 */
164 function timetics_get_google_auth( $user_id = 0 ) {
165 return get_user_meta( $user_id, 'timetics_google_auth', true );
166 }
167 }
168
169 if ( ! function_exists( 'timetics_get_google_client' ) ) {
170 /**
171 * Get google auth client
172 *
173 * @return Timetics\Core\Integrations\Google\Client
174 */
175 function timetics_get_google_client() {
176 $client_id = timetics_get_option( 'google_app_client_id' );
177 $client_secret = timetics_get_option( 'google_app_client_secret' );
178
179 $redirect_uri = timetics_get_auth_redirect_uri( 'google-auth' );
180
181 $client = new Client();
182 $client->add_scope( Calendar::scope() );
183 $client->set_auth_config(
184 [
185 'client_id' => $client_id,
186 'client_secrete' => $client_secret,
187 ]
188 );
189
190 $client->set_redirect_uri( $redirect_uri );
191
192 return $client;
193 }
194 }
195
196 if ( ! function_exists( 'timetics_get_google_auth_url' ) ) {
197 /**
198 * Get google auth url
199 *
200 * @return string
201 */
202 function timetics_get_google_auth_url() {
203 $local_url = site_url( 'timetics-integration/google-auth' );
204
205 return wp_nonce_url( $local_url, 'timetics_auth_action', 'timetics_auth_nonce' );
206 }
207 }
208
209 if ( ! function_exists( 'timetics_get_posts' ) ) {
210 /**
211 * Get posts with date range
212 *
213 * @param array $args
214 *
215 * @return Object
216 */
217 function timetics_get_posts( $args = [] ) {
218 $defaults = [
219 'post_type' => 'post',
220 'post_status' => 'publish',
221 'nopaging' => true,
222 'date_range' => '',
223 ];
224
225 $args = wp_parse_args( $args, $defaults );
226
227 // Date range found.
228 if ( $args['date_range'] ) {
229 $from_date = $args['date_range']['start'];
230 $to_date = $args['date_range']['end'];
231
232 $args['date_query'] = [
233 'relation' => 'AND',
234 [
235 'before' => [
236 'year' => gmdate( 'Y', strtotime( $to_date ) ),
237 'month' => gmdate( 'm', strtotime( $to_date ) ),
238 'day' => gmdate( 'd', strtotime( $to_date ) ),
239 ],
240 'after' => [
241 'year' => gmdate( 'Y', strtotime( $from_date ) ),
242 'month' => gmdate( 'm', strtotime( $from_date ) ),
243 'day' => gmdate( 'd', strtotime( $from_date ) ),
244 ],
245 'inclusive' => true,
246 ],
247 ];
248 }
249
250 // The Query
251 $posts = new WP_Query( $args );
252
253 return $posts;
254 }
255 }
256
257 if ( ! function_exists( 'timetics_count_posts()' ) ) {
258 /**
259 * Count post with date range or without date range
260 *
261 * @param array $args Required query params
262 *
263 * @return integer
264 */
265 function timetics_count_posts( $args = [] ) {
266 $posts = timetics_get_posts( $args );
267
268 return $posts->post_count;
269 }
270 }
271
272 if ( ! function_exists( 'timetics_count_users' ) ) {
273 /**
274 * Count user by role and date range
275 *
276 * @param array $args Required query params
277 *
278 * @return integer
279 */
280 function timetics_count_users( $args = [] ) {
281 $defaults = [
282 'role' => '',
283 'date_range' => '',
284 'count_total' => true,
285 ];
286
287 $args = wp_parse_args( $args, $defaults );
288
289 // Date range found.
290 if ( $args['date_range'] ) {
291 $from_date = $args['date_range']['start'];
292 $to_date = $args['date_range']['end'];
293
294 $args['date_query'] = [
295 'relation' => 'AND',
296 [
297 'before' => [
298 'year' => gmdate( 'Y', strtotime( $to_date ) ),
299 'month' => gmdate( 'm', strtotime( $to_date ) ),
300 'day' => gmdate( 'd', strtotime( $to_date ) ),
301 ],
302 'after' => [
303 'year' => gmdate( 'Y', strtotime( $from_date ) ),
304 'month' => gmdate( 'm', strtotime( $from_date ) ),
305 'day' => gmdate( 'd', strtotime( $from_date ) ),
306 ],
307 'inclusive' => true,
308 ],
309 ];
310 }
311
312 // The Query
313 $user_query = new WP_User_Query( $args );
314
315 // Return total users.
316 return $user_query->total_users;
317 }
318 }
319
320 if ( ! function_exists( 'timetics_get_total_sale' ) ) {
321 /**
322 * Get total sale with date range or without date range
323 *
324 * @param array $args Sales Required Args
325 *
326 * @return integer
327 */
328 function timetics_get_total_sale( $args = [] ) {
329 $defaults = [
330 'post_type' => 'timetics-booking',
331 'post_status' => 'approved',
332 ];
333
334 $args = wp_parse_args( $args, $defaults );
335
336 $posts = timetics_get_posts( $args );
337
338 $total = 0;
339
340 foreach ( $posts->posts as $post ) {
341 $booking = new Booking( $post->ID );
342 $total += floatval( $booking->get_total() );
343 }
344
345 return $total;
346 }
347 }
348
349 if ( ! function_exists( 'timetics_can_view_all_data' ) ) {
350 /**
351 * Check the current user is allowed to see site wide data
352 *
353 * Staff hold `manage_timetics`, so that capability cannot be used to tell an
354 * administrator apart from a team member.
355 *
356 * @return bool
357 */
358 function timetics_can_view_all_data() {
359 return current_user_can( 'manage_options' );
360 }
361 }
362
363 if ( ! function_exists( 'timetics_get_visible_booking_ids' ) ) {
364 /**
365 * Get booking ids a user is allowed to see
366 *
367 * @param integer $user_id
368 *
369 * @return array Never empty, so it is always safe to pass to `post__in`.
370 */
371 function timetics_get_visible_booking_ids( $user_id = 0 ) {
372 if ( ! $user_id ) {
373 $user_id = get_current_user_id();
374 }
375
376 $booking_ids = Booking::get_visible_ids_for_user( $user_id );
377
378 // WP_Query ignores an empty post__in and returns everything, so fall back to a
379 // non-existent id to mean "nothing visible".
380 return ! empty( $booking_ids ) ? $booking_ids : [0];
381 }
382 }
383
384 if ( ! function_exists( 'timetics_get_visible_customer_ids' ) ) {
385 /**
386 * Get customer user ids a user is allowed to see, derived from their visible bookings
387 *
388 * @param integer $user_id
389 *
390 * @return array Never empty, so it is always safe to pass to `include`.
391 */
392 function timetics_get_visible_customer_ids( $user_id = 0 ) {
393 $booking_ids = timetics_get_visible_booking_ids( $user_id );
394 $customer_ids = [];
395
396 foreach ( $booking_ids as $booking_id ) {
397 $customer_id = (int) get_post_meta( $booking_id, '_tt_booking_customer', true );
398
399 if ( $customer_id ) {
400 $customer_ids[] = $customer_id;
401 }
402 }
403
404 $customer_ids = array_values( array_unique( $customer_ids ) );
405
406 // WP_User_Query ignores an empty include and returns everything.
407 return ! empty( $customer_ids ) ? $customer_ids : [0];
408 }
409 }
410
411 if ( ! function_exists( 'timetics_get_staff_integrations' ) ) {
412 /**
413 * Get all staff integrations
414 *
415 * @return array
416 */
417 function timetics_get_staff_integrations( $user_id = 0 ) {
418 timetics_get_google_access_token( $user_id );
419
420 $refresh_token = timetics_get_google_refresh_token( $user_id );
421 $auth_error = get_user_meta( $user_id, 'timetics_google_auth_error', true );
422 $is_connected = ! empty( $refresh_token ) && empty( $auth_error );
423 $needs_reauth = ! empty( $refresh_token ) && ! empty( $auth_error );
424
425 $google_credentials = timetics_get_google_credentials();
426 $is_setup = ! empty( $google_credentials['client_id'] ) && ! empty( $google_credentials['client_secret'] );
427 $current_user_id = ( $user_id == get_current_user_id() ) ? true : false;
428
429 $integrations = [
430 [
431 'id' => 'google-calendar-meet',
432 'name' => esc_html__( 'Google Meet', 'timetics' ),
433 'description' => esc_html__( 'Connect your Meet to sync your booked events.', 'timetics' ),
434 'auth_url' => timetics_get_google_auth_url(),
435 'connected' => $is_connected,
436 'needs_reauth' => $needs_reauth,
437 'setup' => $is_setup,
438 'current_user' => $current_user_id,
439 ],
440 [
441 'id' => 'google-calendar',
442 'name' => esc_html__( 'Google Calendar', 'timetics' ),
443 'description' => esc_html__( 'Connect your Meet to sync your booked events.', 'timetics' ),
444 'auth_url' => timetics_get_google_auth_url(),
445 'connected' => $is_connected,
446 'needs_reauth' => $needs_reauth,
447 'setup' => $is_setup,
448 'current_user' => $current_user_id,
449 ],
450 ];
451
452 $integrations = apply_filters( 'timetics_staff_integrations', $integrations, $user_id );
453
454 return $integrations;
455 }
456 }
457
458 if ( ! function_exists( 'timetics_get_google_credentials' ) ) {
459 /**
460 * Get google auth credentials
461 *
462 * @return array
463 */
464 function timetics_get_google_credentials() {
465 $client_id = timetics_get_option( 'google_app_client_id' );
466 $client_secret = timetics_get_option( 'google_app_client_secret' );
467
468 return [
469 'client_id' => $client_id,
470 'client_secret' => $client_secret,
471 ];
472 }
473 }
474
475 if ( ! function_exists( 'timetics_get_default_timezone' ) ) {
476 /**
477 * Get default timezone
478 *
479 * @return string
480 */
481 function timetics_get_default_timezone() {
482 $timezone = get_option( 'timezone_string' );
483 return $timezone;
484 }
485 }
486
487 if ( ! function_exists( 'timetics_get_currencies' ) ) {
488 /**
489 * Get currency list
490 *
491 * @return array
492 */
493 function timetics_get_currencies() {
494 $currencies = require_once dirname( __FILE__ ) . '/currency.php';
495
496 return $currencies;
497 }
498 }
499
500 if ( ! function_exists( 'timetics_update_default_settings' ) ) {
501
502 /**
503 * Update default settings
504 *
505 * @return void
506 */
507 function timetics_update_default_settings() {
508 $settings = [
509 'google_auth_redirect_uri' => timetics_get_auth_redirect_uri( 'google-auth' ),
510 'outlook_auth_redirect_uri'=> timetics_get_auth_redirect_uri( 'outlook-auth' ),
511 'default_booking_status' => 'pending',
512 'currency' => 'USD',
513 'primary_color' => '#3161F1',
514 'secondary_color' => '#6188ff',
515 'unpaid_booking_expiry_minutes' => 5,
516 ];
517
518 $settings = apply_filters( 'timetics_default_settings', $settings );
519
520 foreach ( $settings as $key => $value ) {
521 timetics_update_option( $key, $value );
522 }
523 }
524 }
525
526 if ( ! function_exists( 'timetics_google_setup' ) ) {
527
528 /**
529 * Checking google auth is configured or not
530 *
531 * @return bool
532 */
533 function timetics_google_setup() {
534 $client_id = timetics_get_option( 'google_app_client_id' );
535 $client_secret = timetics_get_option( 'google_app_client_secret' );
536
537 if ( $client_id && $client_secret ) {
538 return true;
539 }
540
541 return false;
542 }
543 }
544
545 if ( ! function_exists( 'timetics_get_post_status' ) ) {
546
547 /**
548 * Get post status
549 *
550 * @return array
551 */
552 function timetics_get_post_status() {
553 return [
554 'approved',
555 'pending',
556 'cancel',
557 'completed',
558 ];
559 }
560 }
561
562 if ( ! function_exists( 'timetics_get_payment_methods' ) ) {
563 /**
564 * Get payment methods
565 *
566 * @return array
567 */
568 function timetics_get_payment_methods() {
569 $payment_methods = [
570 [
571 'name' => esc_html__( 'Stripe', 'timetics' ),
572 'status' => timetics_get_option( 'stripe_status' ),
573 ],
574 [
575 'name' => esc_html__( 'Local Pay', 'timetics' ),
576 'status' => timetics_get_option( 'local_pay_status' ),
577 ],
578 [
579 'name' => esc_html__( 'Woocommerce', 'timetics' ),
580 'status' => timetics_is_woocommerce_active(),
581 ],
582 ];
583
584 return apply_filters( 'timetics_payment_methods', $payment_methods );
585 }
586 }
587
588 if ( ! function_exists( 'timetics_replace_placeholder' ) ) {
589 /**
590 * Replace string with placeholder
591 *
592 * @param array $placeholders
593 * @param string $text
594 *
595 * @return string
596 */
597 function timetics_replace_placeholder( $text, $placeholders = [] ) {
598
599 if ( ! $placeholders ) {
600 return $text;
601 }
602
603 foreach ( $placeholders as $placeholder => $replace_with ) {
604 $text = str_replace( $placeholder, $replace_with, $text );
605 }
606
607 return $text;
608 }
609 }
610
611 if ( ! function_exists( 'timetics_register_cron' ) ) {
612 /**
613 * Register timetics cron
614 *
615 * @return void
616 */
617 function timetics_register_cron() {
618 wp_schedule_event( time(), 'daily', 'timetics_booking_clear_schedule' );
619
620 // Guarded: Hooks::maybe_schedule_cleanup_cron() also calls this on
621 // every 'init', so without the guard it'd stack duplicate events.
622 if ( ! wp_next_scheduled( 'timetics_cleanup_unpaid_bookings' ) ) {
623 wp_schedule_event( time(), 'timetics_five_minutes', 'timetics_cleanup_unpaid_bookings' );
624 }
625 }
626 }
627
628 if ( ! function_exists( 'timetics_is_woocommerce_active' ) ) {
629
630 /**
631 * Check woocommerce is active or not
632 *
633 * @return bool
634 */
635 function timetics_is_woocommerce_active() {
636
637 if ( function_exists( 'WC' ) && timetics_get_option( 'wc_integration' ) ) {
638 return true;
639 }
640
641 return false;
642 }
643 }
644
645 if ( ! function_exists( 'timetics_add_woocommerce_product_cat' ) ) {
646 /**
647 * Add woocommerce product category
648 *
649 * @return void
650 */
651 function timetics_add_woocommerce_product_cat() {
652 $category_name = __( 'Timetics Meeting', 'timetics' );
653
654 // Prepare category arguments
655 $category_args = array(
656 'taxonomy' => 'product_cat',
657 'cat_name' => $category_name,
658 'category_description' => __( 'Timetics meeting', 'timetics' ),
659 'slug' => 'timetics-meeting',
660 );
661
662 // Insert the category
663 wp_insert_term( $category_args['cat_name'], $category_args['taxonomy'], $category_args );
664 }
665 }
666
667 if ( ! function_exists( 'timetics_is_valid_timezone' ) ) {
668 /**
669 * Check a timezone is valid or not
670 *
671 * @param string $timezone
672 *
673 * @return bool
674 */
675 function timetics_is_valid_timezone( $timezone ) {
676 try {
677 new DateTimeZone( $timezone );
678 } catch ( Exception $e ) {
679 return false;
680 }
681
682 return true;
683 }
684 }
685
686 if ( ! function_exists( 'timetics_datetime' ) ) {
687
688 /**
689 * Timetics date time with timezone
690 *
691 * @param int | string $date
692 * @param string $timezone
693 *
694 * @return string
695 */
696 function timetics_datetime( $format, $date, $timezone = '' ) {
697 $date = is_int( $date ) ? gmdate( 'Y-m-d g:ia', $date ) : $date;
698
699 if ( ! $timezone ) {
700 $timezone = 'UTC';
701 }
702
703 // Create a DateTime object with the provided timestamp and time zone
704 $datetime = new \DateTime( $date, new \DateTimeZone( $timezone ) );
705
706 // Get the converted timestamp
707 return $datetime->format( $format );
708 }
709 }
710
711 if ( ! function_exists( 'timetics_convert_timezone' ) ) {
712
713 /**
714 * Convert date and time fron timezone to another timezone
715 *
716 * @param string $format
717 * @param string $date_time
718 * @param string $from
719 * @param string $to
720 *
721 * @return \DateTime
722 */
723 function timetics_convert_timezone( $date_time_string, $from, $to ) {
724
725 if ( empty( $from ) || empty( $to ) ) {
726 return new DateTime( $date_time_string );
727 }
728
729 $date_time_string = is_int( $date_time_string ) ? gmdate( 'Y-m-d g:ia', $date_time_string ) : gmdate( 'Y-m-d g:ia', strtotime( $date_time_string ) );
730
731 $datetime = new DateTime( $date_time_string, new DateTimeZone( $from ) );
732 $datetime->setTimezone( new DateTimeZone( $to ) );
733
734 return $datetime;
735 }
736 }
737
738 if ( ! function_exists( 'timetics_generate_username' ) ) {
739 /**
740 * Generate unique username
741 *
742 * @param string $email
743 *
744 * @return string
745 */
746 function timetics_generate_username( $email ) {
747 $username = strtok( $email, '@' );
748
749 if ( username_exists( $username ) ) {
750 $username = $username . wp_rand( 10, 100 );
751 }
752
753 return $username;
754 }
755 }
756
757 if ( ! function_exists( 'timetics_is_json' ) ) {
758 /**
759 * Check a string is valid json or not
760 *
761 * @param string $json_string
762 *
763 * @return bool
764 */
765 function timetics_is_json( $json_string ) {
766 if ( ! is_string( $json_string ) ) {
767 return false;
768 }
769
770 $data = json_decode( $json_string, true );
771
772 return $data !== null && json_last_error() === JSON_ERROR_NONE;
773 }
774 }
775
776 if ( ! function_exists( 'timetics_is_google_meet_connected' ) ) {
777
778 /**
779 * Check google meet is connected or not
780 *
781 * @param integer $user_id
782 *
783 * @return bool
784 */
785 function timetics_is_google_meet_connected( $user_id = 0 ) {
786 if ( ! $user_id ) {
787 $user_id = get_current_user_id();
788 }
789
790 $google_auth = timetics_get_google_access_token( $user_id );
791
792 return ! empty( $google_auth );
793 }
794 }
795
796 if ( ! function_exists( 'timetics_is_zoom_connected' ) ) {
797
798 /**
799 * Check google meet is connected or not
800 *
801 * @param integer $user_id
802 *
803 * @return bool
804 */
805 function timetics_is_zoom_connected( $user_id = 0 ) {
806 if ( ! $user_id ) {
807 $user_id = get_current_user_id();
808 }
809
810 if ( ! function_exists( 'timetics_get_zoom_access_token' ) ) {
811 return false;
812 }
813
814 $zoom_auth = timetics_get_zoom_access_token( $user_id );
815
816 return ! empty( $zoom_auth );
817 }
818 }
819
820 if ( ! function_exists( 'timetics_get_current_user' ) ) {
821 /**
822 * Get current user data
823 *
824 * @return array
825 */
826 function timetics_get_current_user() {
827 $user_id = get_current_user_id();
828 $user = get_userdata( $user_id );
829
830 $user_data = [
831 'id' => $user->ID,
832 'username' => $user->user_login,
833 'email' => $user->user_email,
834 'roles' => $user-> roles,
835 ];
836 return $user_data;
837 }
838 }
839
840 if ( ! function_exists( 'timetics_connected_platforms' ) ) {
841
842 /**
843 * Get connected platforms
844 *
845 * @param integer $id
846 *
847 * @return array Connected platform lists
848 */
849 function timetics_connected_platforms( $id ) {
850 $integrations = timetics_get_staff_integrations( $id );
851 $integration_names = [];
852
853 if ( $integrations ) {
854 foreach ( $integrations as $integration ) {
855 if ( $integration['connected'] ) {
856 $integration_names[] = $integration['id'];
857 }
858 }
859 }
860
861 return $integration_names;
862 }
863 }
864
865 if ( ! function_exists( 'timetics_modules_list' ) ) {
866 /**
867 * Get the master list of Arraytics plugins for the About Us page.
868 *
869 * Each entry describes an external Arraytics plugin that can be
870 * installed/activated/deactivated from within the Timetics admin.
871 *
872 * @return array
873 */
874 function timetics_modules_list(): array {
875 return [
876 'eventin' => [
877 'name' => 'eventin',
878 'slug' => 'wp-event-solution',
879 'type' => 'plugin',
880 'upgrade' => false,
881 'upgrade_link' => 'https://themewinter.com/eventin',
882 'status' => 'on',
883 'is_pro' => false,
884 'title' => __( 'Eventin', 'timetics' ),
885 'description' => __( 'Best Free WordPress Event Calendar Plugin to manage booking, registration, RSVPs, schedule recurring events and sell event tickets with WooCommerce.', 'timetics' ),
886 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'eventin' ),
887 'notice' => '',
888 'demo_link' => 'https://product.themewinter.com/eventin',
889 'settings_link' => '',
890 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/eventin/',
891 ],
892 'booktics' => [
893 'name' => 'booktics',
894 'slug' => 'booktics',
895 'type' => 'plugin',
896 'upgrade' => false,
897 'upgrade_link' => 'https://arraytics.com/booktics',
898 'status' => 'on',
899 'is_pro' => false,
900 'title' => __( 'Booktics', 'timetics' ),
901 'description' => __( 'The ultimate online booking plugin for WordPress — services, staff, schedules, and payments in one place.', 'timetics' ),
902 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'booktics' ),
903 'notice' => '',
904 'demo_link' => 'https://arraytics.com/booktics',
905 'settings_link' => '',
906 'doc_link' => 'https://docs.arraytics.com/docs/booktics/',
907 ],
908 'poptics' => [
909 'name' => 'poptics',
910 'slug' => 'poptics',
911 'type' => 'plugin',
912 'upgrade' => false,
913 'upgrade_link' => 'https://arraytics.com/poptics',
914 'status' => 'on',
915 'is_pro' => false,
916 'title' => __( 'Poptics', 'timetics' ),
917 'description' => __( 'Discover the ultimate popup mix with Poptics Popup Builder to boost your lead generation and sales conversions.', 'timetics' ),
918 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'poptics' ),
919 'notice' => '',
920 'demo_link' => 'https://arraytics.com/poptics',
921 'settings_link' => '',
922 'doc_link' => 'https://docs.aethonic.com/docs/getting-started/intro/',
923 ],
924 'wpcafe' => [
925 'name' => 'wpcafe',
926 'slug' => 'wp-cafe',
927 'type' => 'plugin',
928 'upgrade' => false,
929 'upgrade_link' => 'https://themewinter.com/wpcafe',
930 'status' => 'on',
931 'is_pro' => false,
932 'title' => __( 'WPCafe', 'timetics' ),
933 'description' => __( 'WPCafe — Restaurant plugin with Online Ordering, Delivery, Pickup, Food Menu, Reservations, and Table Management.', 'timetics' ),
934 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'wpcafe' ),
935 'notice' => '',
936 'demo_link' => 'https://product.themewinter.com/wpcafe',
937 'settings_link' => '',
938 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/wp-cafe/',
939 ],
940 'aisentic' => [
941 'name' => 'aisentic',
942 'slug' => 'aisentic',
943 'type' => 'plugin',
944 'upgrade' => false,
945 'upgrade_link' => '',
946 'status' => 'on',
947 'is_pro' => false,
948 'title' => __( 'Aisentic', 'timetics' ),
949 'description' => __( 'Your AI assistant for Timetics. Manage bookings, analyze schedules and automate routine tasks using plain English — get work done in seconds instead of hours.', 'timetics' ),
950 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'aisentic' ),
951 'notice' => '',
952 'demo_link' => '',
953 'settings_link' => '',
954 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/aisentic/',
955 ],
956 'optiontics' => [
957 'name' => 'optiontics',
958 'slug' => 'optiontics',
959 'type' => 'plugin',
960 'upgrade' => false,
961 'upgrade_link' => '',
962 'status' => 'on',
963 'is_pro' => false,
964 'title' => __( 'Optiontics', 'timetics' ),
965 'description' => __( 'Offer paid add-ons and extra options with every booking — services, extras, packages or custom upgrades that customers can pick during checkout.', 'timetics' ),
966 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'optiontics' ),
967 'notice' => '',
968 'demo_link' => '',
969 'settings_link' => '',
970 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/optiontics/how-to-create-product-options/',
971 'download_url' => 'https://github.com/themewinter/optiontics-public/releases/download/release/optiontics.zip',
972 ],
973 ];
974 }
975 }
976
977 if ( ! function_exists( 'timetics_extension' ) ) {
978 /**
979 * Get the Timetics Extension manager instance (tools-sdk).
980 *
981 * @return \Arraytics\ToolsSdk\Extension
982 */
983 function timetics_extension(): \Arraytics\ToolsSdk\Extension {
984 return new \Arraytics\ToolsSdk\Extension( 'timetics_available_modules', timetics_modules_list() );
985 }
986 }
987
988 if ( ! function_exists( 'timetics_get_enabled_modules' ) ) {
989 /**
990 * Get list of enabled module slugs.
991 *
992 * @return array
993 */
994 function timetics_get_enabled_modules(): array {
995 $modules = get_option( 'timetics_available_modules', [] );
996
997 if ( empty( $modules ) || ! is_array( $modules ) ) {
998 return [];
999 }
1000
1001 $enabled = [];
1002
1003 foreach ( $modules as $slug => $data ) {
1004 if ( is_array( $data ) && isset( $data['status'] ) && 'on' === $data['status'] ) {
1005 $enabled[] = $slug;
1006 } elseif ( is_string( $data ) && 'on' === $data ) {
1007 $enabled[] = $slug;
1008 }
1009 }
1010
1011 return $enabled;
1012 }
1013 }
1014
1015 if ( ! function_exists( 'timetics_wp_timezone_string' ) ) {
1016 /**
1017 * wp_timezone_string() polyfill — core's version needs WP 5.3, plugin supports 5.2.
1018 *
1019 * @return string
1020 */
1021 function timetics_wp_timezone_string() {
1022 if ( function_exists( 'wp_timezone_string' ) ) {
1023 return wp_timezone_string();
1024 }
1025
1026 $timezone_string = get_option( 'timezone_string' );
1027
1028 if ( $timezone_string ) {
1029 return $timezone_string;
1030 }
1031
1032 $offset = (float) get_option( 'gmt_offset' );
1033 $hours = (int) $offset;
1034 $minutes = abs( ( $offset - $hours ) * 60 );
1035
1036 return sprintf( '%s%02d:%02d', ( $offset < 0 ) ? '-' : '+', abs( $hours ), $minutes );
1037 }
1038 }
1039
1040 if ( ! function_exists( 'timetics_reminder_fallback_timezone' ) ) {
1041 /**
1042 * Timezone to interpret a booking's stored wall clock in when the booking
1043 * itself carries no usable timezone.
1044 *
1045 * A booking normally stores the customer's timezone next to the customer's
1046 * local start time, and that pair must be read together. Bookings created
1047 * before that meta existed, or from the admin side, have no timezone — those
1048 * times were entered against the site clock, so the site timezone is the
1049 * closer reading. Falling back to UTC put every reminder on such a booking
1050 * out by the site's GMT offset.
1051 *
1052 * @return string A timezone identifier or ±hh:mm offset accepted by DateTimeZone.
1053 */
1054 function timetics_reminder_fallback_timezone() {
1055 $timezone = timetics_wp_timezone_string();
1056
1057 return $timezone ? $timezone : 'UTC';
1058 }
1059 }
1060
1061 if ( ! function_exists( 'timetics_format_email_datetime' ) ) {
1062 /**
1063 * Format date, time and day according to WordPress admin settings
1064 * Used in email templates to display consistent date/time formats
1065 *
1066 * @param string $start_date Start date (Y-m-d format)
1067 * @param string $start_time Start time (h:i a format)
1068 *
1069 * @return array Array with 'day', 'time', 'date' keys
1070 */
1071 function timetics_format_email_datetime( $start_date, $start_time = '' ) {
1072 $wp_date_format = get_option( 'date_format' );
1073 $wp_time_format = get_option( 'time_format' );
1074
1075 $datetime = $start_time
1076 ? $start_date . ' ' . $start_time
1077 : $start_date;
1078
1079 $timestamp = strtotime( $datetime );
1080
1081 return [
1082 'day' => date_i18n( 'l', $timestamp ),
1083 'time' => date_i18n( $wp_time_format, $timestamp ),
1084 'date' => date_i18n( $wp_date_format, $timestamp ),
1085 ];
1086 }
1087 }
1088
1089 if ( ! function_exists( 'timetics_aisentic_identity' ) ) {
1090 /**
1091 * Resolve the identity Timetics hands to Aisentic when the user connects.
1092 *
1093 * The consent UI shows these exact values before anything leaves the site,
1094 * so the connect request must read them from here too. Showing one email
1095 * and sending another would break the consent.
1096 *
1097 * The account belongs to the person who opts in, so it is the logged-in
1098 * user's name and email. The Ask AI setup dialog lets them edit the email,
1099 * which arrives here as $email. The site admin email is the last fallback.
1100 *
1101 * @param string $email Optional email the user typed in the consent UI.
1102 * @return array{name:string,email:string,site_url:string}
1103 */
1104 function timetics_aisentic_identity( $email = '' ) {
1105 $user = wp_get_current_user();
1106 $name = sanitize_text_field( (string) $user->display_name );
1107 $email = sanitize_email( (string) ( $email ?: $user->user_email ?: get_option( 'admin_email', '' ) ) );
1108
1109 return [
1110 'name' => $name,
1111 'email' => $email,
1112 'site_url' => site_url(),
1113 ];
1114 }
1115 }
1116
1117 if ( ! function_exists( 'timetics_aisentic_is_registered' ) ) {
1118 /**
1119 * Whether Aisentic already holds a provider api key.
1120 *
1121 * Reads Aisentic's own settings, so a site the user registered from
1122 * Aisentic's own screens counts as connected as well. Without that the
1123 * dashboard banner would keep nagging people who are already set up.
1124 *
1125 * @return bool
1126 */
1127 function timetics_aisentic_is_registered() {
1128 if ( function_exists( 'aisentic_get_option' ) ) {
1129 return ! empty( aisentic_get_option( 'llmProviders.aisentic.apiKey', '' ) );
1130 }
1131
1132 $settings = get_option( 'aisentic_settings', [] );
1133
1134 return ! empty( $settings['llmProviders']['aisentic']['apiKey'] );
1135 }
1136 }
1137