PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.61
Timetics – Appointment Booking Calendar & Scheduling v1.0.61
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.61, at utils/global-helper.php

995 lines 31.0 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_get_staff_integrations' ) ) {
350 /**
351 * Get all staff integrations
352 *
353 * @return array
354 */
355 function timetics_get_staff_integrations( $user_id = 0 ) {
356 timetics_get_google_access_token( $user_id );
357
358 $refresh_token = timetics_get_google_refresh_token( $user_id );
359 $auth_error = get_user_meta( $user_id, 'timetics_google_auth_error', true );
360 $is_connected = ! empty( $refresh_token ) && empty( $auth_error );
361 $needs_reauth = ! empty( $refresh_token ) && ! empty( $auth_error );
362
363 $google_credentials = timetics_get_google_credentials();
364 $is_setup = ! empty( $google_credentials['client_id'] ) && ! empty( $google_credentials['client_secret'] );
365 $current_user_id = ( $user_id == get_current_user_id() ) ? true : false;
366
367 $integrations = [
368 [
369 'id' => 'google-calendar-meet',
370 'name' => esc_html__( 'Google Meet', 'timetics' ),
371 'description' => esc_html__( 'Connect your Meet to sync your booked events.', 'timetics' ),
372 'auth_url' => timetics_get_google_auth_url(),
373 'connected' => $is_connected,
374 'needs_reauth' => $needs_reauth,
375 'setup' => $is_setup,
376 'current_user' => $current_user_id,
377 ],
378 [
379 'id' => 'google-calendar',
380 'name' => esc_html__( 'Google Calendar', 'timetics' ),
381 'description' => esc_html__( 'Connect your Meet to sync your booked events.', 'timetics' ),
382 'auth_url' => timetics_get_google_auth_url(),
383 'connected' => $is_connected,
384 'needs_reauth' => $needs_reauth,
385 'setup' => $is_setup,
386 'current_user' => $current_user_id,
387 ],
388 ];
389
390 $integrations = apply_filters( 'timetics_staff_integrations', $integrations, $user_id );
391
392 return $integrations;
393 }
394 }
395
396 if ( ! function_exists( 'timetics_get_google_credentials' ) ) {
397 /**
398 * Get google auth credentials
399 *
400 * @return array
401 */
402 function timetics_get_google_credentials() {
403 $client_id = timetics_get_option( 'google_app_client_id' );
404 $client_secret = timetics_get_option( 'google_app_client_secret' );
405
406 return [
407 'client_id' => $client_id,
408 'client_secret' => $client_secret,
409 ];
410 }
411 }
412
413 if ( ! function_exists( 'timetics_get_default_timezone' ) ) {
414 /**
415 * Get default timezone
416 *
417 * @return string
418 */
419 function timetics_get_default_timezone() {
420 $timezone = get_option( 'timezone_string' );
421 return $timezone;
422 }
423 }
424
425 if ( ! function_exists( 'timetics_get_currencies' ) ) {
426 /**
427 * Get currency list
428 *
429 * @return array
430 */
431 function timetics_get_currencies() {
432 $currencies = require_once dirname( __FILE__ ) . '/currency.php';
433
434 return $currencies;
435 }
436 }
437
438 if ( ! function_exists( 'timetics_update_default_settings' ) ) {
439
440 /**
441 * Update default settings
442 *
443 * @return void
444 */
445 function timetics_update_default_settings() {
446 $settings = [
447 'google_auth_redirect_uri' => timetics_get_auth_redirect_uri( 'google-auth' ),
448 'outlook_auth_redirect_uri'=> timetics_get_auth_redirect_uri( 'outlook-auth' ),
449 'default_booking_status' => 'pending',
450 'currency' => 'USD',
451 'primary_color' => '#3161F1',
452 'secondary_color' => '#6188ff',
453 ];
454
455 $settings = apply_filters( 'timetics_default_settings', $settings );
456
457 foreach ( $settings as $key => $value ) {
458 timetics_update_option( $key, $value );
459 }
460 }
461 }
462
463 if ( ! function_exists( 'timetics_google_setup' ) ) {
464
465 /**
466 * Checking google auth is configured or not
467 *
468 * @return bool
469 */
470 function timetics_google_setup() {
471 $client_id = timetics_get_option( 'google_app_client_id' );
472 $client_secret = timetics_get_option( 'google_app_client_secret' );
473
474 if ( $client_id && $client_secret ) {
475 return true;
476 }
477
478 return false;
479 }
480 }
481
482 if ( ! function_exists( 'timetics_get_post_status' ) ) {
483
484 /**
485 * Get post status
486 *
487 * @return array
488 */
489 function timetics_get_post_status() {
490 return [
491 'approved',
492 'pending',
493 'cancel',
494 'completed',
495 ];
496 }
497 }
498
499 if ( ! function_exists( 'timetics_get_payment_methods' ) ) {
500 /**
501 * Get payment methods
502 *
503 * @return array
504 */
505 function timetics_get_payment_methods() {
506 $payment_methods = [
507 [
508 'name' => esc_html__( 'Stripe', 'timetics' ),
509 'status' => timetics_get_option( 'stripe_status' ),
510 ],
511 [
512 'name' => esc_html__( 'Local Pay', 'timetics' ),
513 'status' => timetics_get_option( 'local_pay_status' ),
514 ],
515 [
516 'name' => esc_html__( 'Woocommerce', 'timetics' ),
517 'status' => timetics_is_woocommerce_active(),
518 ],
519 ];
520
521 return apply_filters( 'timetics_payment_methods', $payment_methods );
522 }
523 }
524
525 if ( ! function_exists( 'timetics_replace_placeholder' ) ) {
526 /**
527 * Replace string with placeholder
528 *
529 * @param array $placeholders
530 * @param string $text
531 *
532 * @return string
533 */
534 function timetics_replace_placeholder( $text, $placeholders = [] ) {
535
536 if ( ! $placeholders ) {
537 return $text;
538 }
539
540 foreach ( $placeholders as $placeholder => $replace_with ) {
541 $text = str_replace( $placeholder, $replace_with, $text );
542 }
543
544 return $text;
545 }
546 }
547
548 if ( ! function_exists( 'timetics_register_cron' ) ) {
549 /**
550 * Register timetics cron
551 *
552 * @return void
553 */
554 function timetics_register_cron() {
555 wp_schedule_event( time(), 'daily', 'timetics_booking_clear_schedule' );
556 }
557 }
558
559 if ( ! function_exists( 'timetics_is_woocommerce_active' ) ) {
560
561 /**
562 * Check woocommerce is active or not
563 *
564 * @return bool
565 */
566 function timetics_is_woocommerce_active() {
567
568 if ( function_exists( 'WC' ) && timetics_get_option( 'wc_integration' ) ) {
569 return true;
570 }
571
572 return false;
573 }
574 }
575
576 if ( ! function_exists( 'timetics_add_woocommerce_product_cat' ) ) {
577 /**
578 * Add woocommerce product category
579 *
580 * @return void
581 */
582 function timetics_add_woocommerce_product_cat() {
583 $category_name = __( 'Timetics Meeting', 'timetics' );
584
585 // Prepare category arguments
586 $category_args = array(
587 'taxonomy' => 'product_cat',
588 'cat_name' => $category_name,
589 'category_description' => __( 'Timetics meeting', 'timetics' ),
590 'slug' => 'timetics-meeting',
591 );
592
593 // Insert the category
594 wp_insert_term( $category_args['cat_name'], $category_args['taxonomy'], $category_args );
595 }
596 }
597
598 if ( ! function_exists( 'timetics_is_valid_timezone' ) ) {
599 /**
600 * Check a timezone is valid or not
601 *
602 * @param string $timezone
603 *
604 * @return bool
605 */
606 function timetics_is_valid_timezone( $timezone ) {
607 try {
608 new DateTimeZone( $timezone );
609 } catch ( Exception $e ) {
610 return false;
611 }
612
613 return true;
614 }
615 }
616
617 if ( ! function_exists( 'timetics_datetime' ) ) {
618
619 /**
620 * Timetics date time with timezone
621 *
622 * @param int | string $date
623 * @param string $timezone
624 *
625 * @return string
626 */
627 function timetics_datetime( $format, $date, $timezone = '' ) {
628 $date = is_int( $date ) ? gmdate( 'Y-m-d g:ia', $date ) : $date;
629
630 if ( ! $timezone ) {
631 $timezone = 'UTC';
632 }
633
634 // Create a DateTime object with the provided timestamp and time zone
635 $datetime = new \DateTime( $date, new \DateTimeZone( $timezone ) );
636
637 // Get the converted timestamp
638 return $datetime->format( $format );
639 }
640 }
641
642 if ( ! function_exists( 'timetics_convert_timezone' ) ) {
643
644 /**
645 * Convert date and time fron timezone to another timezone
646 *
647 * @param string $format
648 * @param string $date_time
649 * @param string $from
650 * @param string $to
651 *
652 * @return \DateTime
653 */
654 function timetics_convert_timezone( $date_time_string, $from, $to ) {
655
656 if ( empty( $from ) || empty( $to ) ) {
657 return new DateTime( $date_time_string );
658 }
659
660 $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 ) );
661
662 $datetime = new DateTime( $date_time_string, new DateTimeZone( $from ) );
663 $datetime->setTimezone( new DateTimeZone( $to ) );
664
665 return $datetime;
666 }
667 }
668
669 if ( ! function_exists( 'timetics_generate_username' ) ) {
670 /**
671 * Generate unique username
672 *
673 * @param string $email
674 *
675 * @return string
676 */
677 function timetics_generate_username( $email ) {
678 $username = strtok( $email, '@' );
679
680 if ( username_exists( $username ) ) {
681 $username = $username . wp_rand( 10, 100 );
682 }
683
684 return $username;
685 }
686 }
687
688 if ( ! function_exists( 'timetics_is_json' ) ) {
689 /**
690 * Check a string is valid json or not
691 *
692 * @param string $json_string
693 *
694 * @return bool
695 */
696 function timetics_is_json( $json_string ) {
697 if ( ! is_string( $json_string ) ) {
698 return false;
699 }
700
701 $data = json_decode( $json_string, true );
702
703 return $data !== null && json_last_error() === JSON_ERROR_NONE;
704 }
705 }
706
707 if ( ! function_exists( 'timetics_is_google_meet_connected' ) ) {
708
709 /**
710 * Check google meet is connected or not
711 *
712 * @param integer $user_id
713 *
714 * @return bool
715 */
716 function timetics_is_google_meet_connected( $user_id = 0 ) {
717 if ( ! $user_id ) {
718 $user_id = get_current_user_id();
719 }
720
721 $google_auth = timetics_get_google_access_token( $user_id );
722
723 return ! empty( $google_auth );
724 }
725 }
726
727 if ( ! function_exists( 'timetics_is_zoom_connected' ) ) {
728
729 /**
730 * Check google meet is connected or not
731 *
732 * @param integer $user_id
733 *
734 * @return bool
735 */
736 function timetics_is_zoom_connected( $user_id = 0 ) {
737 if ( ! $user_id ) {
738 $user_id = get_current_user_id();
739 }
740
741 if ( ! function_exists( 'timetics_get_zoom_access_token' ) ) {
742 return false;
743 }
744
745 $zoom_auth = timetics_get_zoom_access_token( $user_id );
746
747 return ! empty( $zoom_auth );
748 }
749 }
750
751 if ( ! function_exists( 'timetics_get_current_user' ) ) {
752 /**
753 * Get current user data
754 *
755 * @return array
756 */
757 function timetics_get_current_user() {
758 $user_id = get_current_user_id();
759 $user = get_userdata( $user_id );
760
761 $user_data = [
762 'id' => $user->ID,
763 'username' => $user->user_login,
764 'email' => $user->user_email,
765 'roles' => $user-> roles,
766 ];
767 return $user_data;
768 }
769 }
770
771 if ( ! function_exists( 'timetics_connected_platforms' ) ) {
772
773 /**
774 * Get connected platforms
775 *
776 * @param integer $id
777 *
778 * @return array Connected platform lists
779 */
780 function timetics_connected_platforms( $id ) {
781 $integrations = timetics_get_staff_integrations( $id );
782 $integration_names = [];
783
784 if ( $integrations ) {
785 foreach ( $integrations as $integration ) {
786 if ( $integration['connected'] ) {
787 $integration_names[] = $integration['id'];
788 }
789 }
790 }
791
792 return $integration_names;
793 }
794 }
795
796 if ( ! function_exists( 'timetics_modules_list' ) ) {
797 /**
798 * Get the master list of Arraytics plugins for the About Us page.
799 *
800 * Each entry describes an external Arraytics plugin that can be
801 * installed/activated/deactivated from within the Timetics admin.
802 *
803 * @return array
804 */
805 function timetics_modules_list(): array {
806 return [
807 'eventin' => [
808 'name' => 'eventin',
809 'slug' => 'wp-event-solution',
810 'type' => 'plugin',
811 'upgrade' => false,
812 'upgrade_link' => 'https://themewinter.com/eventin',
813 'status' => 'on',
814 'is_pro' => false,
815 'title' => __( 'Eventin', 'timetics' ),
816 'description' => __( 'Best Free WordPress Event Calendar Plugin to manage booking, registration, RSVPs, schedule recurring events and sell event tickets with WooCommerce.', 'timetics' ),
817 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'eventin' ),
818 'notice' => '',
819 'demo_link' => 'https://product.themewinter.com/eventin',
820 'settings_link' => '',
821 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/eventin/',
822 ],
823 'booktics' => [
824 'name' => 'booktics',
825 'slug' => 'booktics',
826 'type' => 'plugin',
827 'upgrade' => false,
828 'upgrade_link' => 'https://arraytics.com/booktics',
829 'status' => 'on',
830 'is_pro' => false,
831 'title' => __( 'Booktics', 'timetics' ),
832 'description' => __( 'The ultimate online booking plugin for WordPress — services, staff, schedules, and payments in one place.', 'timetics' ),
833 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'booktics' ),
834 'notice' => '',
835 'demo_link' => 'https://arraytics.com/booktics',
836 'settings_link' => '',
837 'doc_link' => 'https://docs.arraytics.com/docs/booktics/',
838 ],
839 'poptics' => [
840 'name' => 'poptics',
841 'slug' => 'poptics',
842 'type' => 'plugin',
843 'upgrade' => false,
844 'upgrade_link' => 'https://arraytics.com/poptics',
845 'status' => 'on',
846 'is_pro' => false,
847 'title' => __( 'Poptics', 'timetics' ),
848 'description' => __( 'Discover the ultimate popup mix with Poptics Popup Builder to boost your lead generation and sales conversions.', 'timetics' ),
849 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'poptics' ),
850 'notice' => '',
851 'demo_link' => 'https://arraytics.com/poptics',
852 'settings_link' => '',
853 'doc_link' => 'https://docs.aethonic.com/docs/getting-started/intro/',
854 ],
855 'wpcafe' => [
856 'name' => 'wpcafe',
857 'slug' => 'wp-cafe',
858 'type' => 'plugin',
859 'upgrade' => false,
860 'upgrade_link' => 'https://themewinter.com/wpcafe',
861 'status' => 'on',
862 'is_pro' => false,
863 'title' => __( 'WPCafe', 'timetics' ),
864 'description' => __( 'WPCafe — Restaurant plugin with Online Ordering, Delivery, Pickup, Food Menu, Reservations, and Table Management.', 'timetics' ),
865 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'wpcafe' ),
866 'notice' => '',
867 'demo_link' => 'https://product.themewinter.com/wpcafe',
868 'settings_link' => '',
869 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/wp-cafe/',
870 ],
871 'aisentic' => [
872 'name' => 'aisentic',
873 'slug' => 'aisentic',
874 'type' => 'plugin',
875 'upgrade' => false,
876 'upgrade_link' => '',
877 'status' => 'on',
878 'is_pro' => false,
879 'title' => __( 'Aisentic', 'timetics' ),
880 'description' => __( 'AI assistant for WordPress — automate content, replies and on-site tasks without leaving your dashboard.', 'timetics' ),
881 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'aisentic' ),
882 'notice' => '',
883 'demo_link' => '',
884 'settings_link' => '',
885 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/aisentic/',
886 'download_url' => 'https://github.com/themewinter/aisentic-public/releases/download/v1.0.0/aisentic-1.0.0.zip',
887 ],
888 'optiontics' => [
889 'name' => 'optiontics',
890 'slug' => 'optiontics',
891 'type' => 'plugin',
892 'upgrade' => false,
893 'upgrade_link' => '',
894 'status' => 'on',
895 'is_pro' => false,
896 'title' => __( 'Optiontics', 'timetics' ),
897 'description' => __( 'Product add-ons / extras for food items — let customers pick toppings, sizes and other paid options along with the products.', 'timetics' ),
898 'icon' => \Timetics\Core\Addon\Extension_Icon::get( 'optiontics' ),
899 'notice' => '',
900 'demo_link' => '',
901 'settings_link' => '',
902 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/optiontics/how-to-create-product-options/',
903 'download_url' => 'https://github.com/themewinter/optiontics-public/releases/download/release/optiontics.zip',
904 ],
905 ];
906 }
907 }
908
909 if ( ! function_exists( 'timetics_extension' ) ) {
910 /**
911 * Get the Timetics Extension manager instance (tools-sdk).
912 *
913 * @return \Arraytics\ToolsSdk\Extension
914 */
915 function timetics_extension(): \Arraytics\ToolsSdk\Extension {
916 return new \Arraytics\ToolsSdk\Extension( 'timetics_available_modules', timetics_modules_list() );
917 }
918 }
919
920 if ( ! function_exists( 'timetics_get_enabled_modules' ) ) {
921 /**
922 * Get list of enabled module slugs.
923 *
924 * @return array
925 */
926 function timetics_get_enabled_modules(): array {
927 $modules = get_option( 'timetics_available_modules', [] );
928
929 if ( empty( $modules ) || ! is_array( $modules ) ) {
930 return [];
931 }
932
933 $enabled = [];
934
935 foreach ( $modules as $slug => $data ) {
936 if ( is_array( $data ) && isset( $data['status'] ) && 'on' === $data['status'] ) {
937 $enabled[] = $slug;
938 } elseif ( is_string( $data ) && 'on' === $data ) {
939 $enabled[] = $slug;
940 }
941 }
942
943 return $enabled;
944 }
945 }
946
947 if ( ! function_exists( 'timetics_reminder_fallback_timezone' ) ) {
948 /**
949 * Timezone to interpret a booking's stored wall clock in when the booking
950 * itself carries no usable timezone.
951 *
952 * A booking normally stores the customer's timezone next to the customer's
953 * local start time, and that pair must be read together. Bookings created
954 * before that meta existed, or from the admin side, have no timezone — those
955 * times were entered against the site clock, so the site timezone is the
956 * closer reading. Falling back to UTC put every reminder on such a booking
957 * out by the site's GMT offset.
958 *
959 * @return string A timezone identifier or ±hh:mm offset accepted by DateTimeZone.
960 */
961 function timetics_reminder_fallback_timezone() {
962 $timezone = wp_timezone_string();
963
964 return $timezone ? $timezone : 'UTC';
965 }
966 }
967
968 if ( ! function_exists( 'timetics_format_email_datetime' ) ) {
969 /**
970 * Format date, time and day according to WordPress admin settings
971 * Used in email templates to display consistent date/time formats
972 *
973 * @param string $start_date Start date (Y-m-d format)
974 * @param string $start_time Start time (h:i a format)
975 *
976 * @return array Array with 'day', 'time', 'date' keys
977 */
978 function timetics_format_email_datetime( $start_date, $start_time = '' ) {
979 $wp_date_format = get_option( 'date_format' );
980 $wp_time_format = get_option( 'time_format' );
981
982 $datetime = $start_time
983 ? $start_date . ' ' . $start_time
984 : $start_date;
985
986 $timestamp = strtotime( $datetime );
987
988 return [
989 'day' => date_i18n( 'l', $timestamp ),
990 'time' => date_i18n( $wp_time_format, $timestamp ),
991 'date' => date_i18n( $wp_date_format, $timestamp ),
992 ];
993 }
994 }
995