PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / trunk
Booktics – Appointment Booking Calendar for Service Businesses vtrunk
1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 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.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 All 27 releases
booktics / utils / global-helper.php

global-helper.php in Booktics – Appointment Booking Calendar for Service Businesses trunk, at utils/global-helper.php

1,097 lines 37.2 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 // Composer autoloads this file, so CLI tooling loads it without WordPress.
4 if ( PHP_SAPI !== 'cli' ) {
5 exit;
6 }
7 }
8 /**
9 * Get timezone list.
10 *
11 * @return array
12 * @since 1.0.0
13 */
14 if ( ! function_exists( 'booktics_get_timezone_list' ) ) {
15 function booktics_get_timezone_list() {
16 $tzlist = wp_timezone_choice( 'UTC' );
17 $doc = new DOMDocument();
18 $doc->loadHTML( $tzlist );
19
20 $elements = $doc->getElementsByTagName( 'option' );
21 $timezones = array();
22
23 if ( ! empty( $elements ) ) {
24 foreach ( $elements as $element ) {
25 $data = array(
26 'value' => $element->getAttribute( 'value' ),
27 'name' => $element->textContent,
28 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
29 );
30
31 $timezones[] = $data;
32 }
33 }
34
35 return $timezones;
36 }
37 }
38
39 if ( ! function_exists( 'booktics_get_auth_redirect_uri' ) ) {
40 /**
41 * Get auth redirect uri
42 *
43 * @param string $auth
44 *
45 * @return string
46 */
47 function booktics_get_auth_redirect_uri( $auth = '' ) {
48 return site_url( 'booktics-integration/' . $auth );
49 }
50 }
51
52
53 if ( ! function_exists( 'booktics_get_google_auth' ) ) {
54 /**
55 * Get google auth
56 *
57 * @param integer $user_id
58 *
59 * @return array
60 */
61 function booktics_get_google_auth( $user_id = 0 ) {
62 return get_user_meta( $user_id, 'booktics_google_auth', true );
63 }
64 }
65
66 if ( ! function_exists( 'booktics_get_posts' ) ) {
67 /**
68 * Get posts with date range
69 *
70 * @param array $args
71 *
72 * @return WP_Query
73 */
74 function booktics_get_posts( $args = array() ) {
75 $defaults = array(
76 'post_type' => 'post',
77 'post_status' => 'publish',
78 'nopaging' => true,
79 'date_range' => '',
80 );
81
82 $args = wp_parse_args( $args, $defaults );
83
84 // Date range found.
85 if ( $args['date_range'] ) {
86 $from_date = $args['date_range']['start'];
87 $to_date = $args['date_range']['end'];
88
89 $args['date_query'] = array(
90 'relation' => 'AND',
91 array(
92 'before' => array(
93 'year' => gmdate( 'Y', strtotime( $to_date ) ),
94 'month' => gmdate( 'm', strtotime( $to_date ) ),
95 'day' => gmdate( 'd', strtotime( $to_date ) ),
96 ),
97 'after' => array(
98 'year' => gmdate( 'Y', strtotime( $from_date ) ),
99 'month' => gmdate( 'm', strtotime( $from_date ) ),
100 'day' => gmdate( 'd', strtotime( $from_date ) ),
101 ),
102 'inclusive' => true,
103 ),
104 );
105 }
106
107 // The Query
108 $posts = new WP_Query( $args );
109
110 return $posts;
111 }
112 }
113
114 if ( ! function_exists( 'booktics_count_posts()' ) ) {
115 /**
116 * Count post with date range or without date range
117 *
118 * @param array $args Required query params
119 *
120 * @return integer
121 */
122 function booktics_count_posts( $args = array() ) {
123 $posts = booktics_get_posts( $args );
124
125 return $posts->post_count;
126 }
127 }
128
129 if ( ! function_exists( 'booktics_count_users' ) ) {
130 /**
131 * Count user by role and date range
132 *
133 * @param array $args Required query params
134 *
135 * @return integer
136 */
137 function booktics_count_users( $args = array() ) {
138 $defaults = array(
139 'role' => '',
140 'date_range' => '',
141 'count_total' => true,
142 );
143
144 $args = wp_parse_args( $args, $defaults );
145
146 // Date range found.
147 if ( $args['date_range'] ) {
148 $from_date = $args['date_range']['start'];
149 $to_date = $args['date_range']['end'];
150
151 $args['date_query'] = array(
152 'relation' => 'AND',
153 array(
154 'before' => array(
155 'year' => gmdate( 'Y', strtotime( $to_date ) ),
156 'month' => gmdate( 'm', strtotime( $to_date ) ),
157 'day' => gmdate( 'd', strtotime( $to_date ) ),
158 ),
159 'after' => array(
160 'year' => gmdate( 'Y', strtotime( $from_date ) ),
161 'month' => gmdate( 'm', strtotime( $from_date ) ),
162 'day' => gmdate( 'd', strtotime( $from_date ) ),
163 ),
164 'inclusive' => true,
165 ),
166 );
167 }
168
169 // The Query
170 $user_query = new WP_User_Query( $args );
171
172 // Return total users.
173 return $user_query->total_users;
174 }
175 }
176
177
178 if ( ! function_exists( 'booktics_get_default_timezone' ) ) {
179 /**
180 * Get default timezone
181 *
182 * @return string
183 */
184 function booktics_get_default_timezone() {
185 $timezone = get_option( 'timezone_string' );
186
187 return $timezone;
188 }
189 }
190
191 if ( ! function_exists( 'booktics_get_currencies' ) ) {
192 /**
193 * Get currency list
194 *
195 * @return array
196 */
197 function booktics_get_currencies() {
198 $currencies = require_once __DIR__ . '/currency.php';
199
200 return $currencies;
201 }
202 }
203
204 if ( ! function_exists( 'booktics_update_default_settings' ) ) {
205
206 /**
207 * Update default settings
208 *
209 * @return void
210 */
211 function booktics_update_default_settings() {
212 $settings = array(
213 'google_auth_redirect_uri' => booktics_get_auth_redirect_uri( 'google-auth' ),
214 'default_booking_status' => 'pending',
215 'currency' => 'USD',
216 'primary_color' => '#3161F1',
217 'secondary_color' => '#6188ff',
218 );
219
220 $settings = apply_filters( 'booktics_default_settings', $settings );
221
222 foreach ( $settings as $key => $value ) {
223 booktics_update_option( $key, $value );
224 }
225 }
226 }
227
228 if ( ! function_exists( 'booktics_get_post_status' ) ) {
229
230 /**
231 * Get post status
232 *
233 * @return array
234 */
235 function booktics_get_post_status() {
236 return array(
237 'approved',
238 'pending',
239 'cancel',
240 'completed',
241 );
242 }
243 }
244
245 if ( ! function_exists( 'booktics_replace_placeholder' ) ) {
246 /**
247 * Replace string with placeholder
248 *
249 * @param array $placeholders
250 * @param string $text
251 *
252 * @return string
253 */
254 function booktics_replace_placeholder( $text, $placeholders = array() ) {
255
256 if ( ! $placeholders ) {
257 return $text;
258 }
259
260 foreach ( $placeholders as $placeholder => $replace_with ) {
261 $text = str_replace( $placeholder, $replace_with, $text );
262 }
263
264 return $text;
265 }
266 }
267
268 if ( ! function_exists( 'booktics_register_cron' ) ) {
269 /**
270 * Register booktics cron
271 *
272 * @return void
273 */
274 function booktics_register_cron() {
275 wp_schedule_event( time(), 'daily', 'booktics_booking_clear_schedule' );
276 }
277 }
278
279 if ( ! function_exists( 'booktics_is_woocommerce_active' ) ) {
280
281 /**
282 * Check whether WooCommerce payments are usable.
283 *
284 * True only when WooCommerce itself is running, the module is enabled and
285 * the payment setting is on. Any one of the three being false means the
286 * gateway must not be offered.
287 *
288 * @return bool
289 */
290 function booktics_is_woocommerce_active() {
291 if ( ! class_exists( 'WooCommerce' ) ) {
292 return false;
293 }
294
295 if ( ! booktics_extension()->is_enabled( 'woocommerce' ) ) {
296 return false;
297 }
298
299 return (bool) booktics_get_option( 'enable_woocommerce_payment' );
300 }
301 }
302
303
304 if ( ! function_exists( 'booktics_is_valid_timezone' ) ) {
305 /**
306 * Check a timezone is valid or not
307 *
308 * @param string $timezone
309 *
310 * @return bool
311 */
312 function booktics_is_valid_timezone( $timezone ) {
313 try {
314 new DateTimeZone( $timezone );
315 } catch ( Exception $e ) {
316 return false;
317 }
318
319 return true;
320 }
321 }
322
323 if ( ! function_exists( 'booktics_datetime' ) ) {
324
325 /**
326 * Booktics date time with timezone
327 *
328 * @param int | string $date
329 * @param string $timezone
330 *
331 * @return string
332 */
333 function booktics_datetime( $format, $date, $timezone = '' ) {
334 $date = is_int( $date ) ? gmdate( 'Y-m-d g:ia', $date ) : $date;
335
336 if ( ! $timezone ) {
337 $timezone = 'UTC';
338 }
339
340 // Create a DateTime object with the provided timestamp and time zone
341 $datetime = new \DateTime( $date, new \DateTimeZone( $timezone ) );
342
343 // Get the converted timestamp
344 return $datetime->format( $format );
345 }
346 }
347
348 if ( ! function_exists( 'booktics_convert_timezone' ) ) {
349
350 /**
351 * Convert date and time from timezone to another timezone
352 *
353 * @param string $format
354 * @param string $date_time
355 * @param string $from
356 * @param string $to
357 *
358 * @return \DateTime
359 */
360 function booktics_convert_timezone( $date_time_string, $from, $to ) {
361
362 if ( empty( $from ) || empty( $to ) ) {
363 return new DateTime( $date_time_string );
364 }
365
366 $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 ) );
367
368 $datetime = new DateTime( $date_time_string, new DateTimeZone( $from ) );
369 $datetime->setTimezone( new DateTimeZone( $to ) );
370
371 return $datetime;
372 }
373 }
374
375 if ( ! function_exists( 'booktics_generate_username' ) ) {
376 /**
377 * Generate unique username
378 *
379 * @param string $email
380 *
381 * @return string
382 */
383 function booktics_generate_username( $email ) {
384 $username = strtok( $email, '@' );
385
386 if ( username_exists( $username ) ) {
387 $username = $username . wp_rand( 10, 100 );
388 }
389
390 return $username;
391 }
392 }
393
394 if ( ! function_exists( 'booktics_is_json' ) ) {
395 /**
396 * Check a string is valid json or not
397 *
398 * @param string $json_string
399 *
400 * @return bool
401 */
402 function booktics_is_json( $json_string ) {
403 if ( ! is_string( $json_string ) ) {
404 return false;
405 }
406
407 $data = json_decode( $json_string, true );
408
409 return $data !== null && json_last_error() === JSON_ERROR_NONE;
410 }
411 }
412
413
414 if ( ! function_exists( 'booktics_get_current_user' ) ) {
415 /**
416 * Get current user data
417 *
418 * @return array
419 */
420 function booktics_get_current_user() {
421 $user_id = get_current_user_id();
422 $user = get_userdata( $user_id );
423
424 $user_data = array(
425 'id' => $user->ID,
426 'username' => $user->user_login,
427 'email' => $user->user_email,
428 'roles' => $user->roles,
429 );
430
431 return $user_data;
432 }
433 }
434
435 if ( ! function_exists( 'booktics_validate' ) ) {
436 /**
437 * Validate user input
438 *
439 * @param array $request
440 * @param array $rules
441 *
442 * @return bool | WP_Error
443 */
444 function booktics_validate( $request, $rules ) {
445 $validator = new \Booktics\Validation\Validator( $request );
446
447 $validator->set_rules( $rules );
448
449 if ( ! $validator->validate() ) {
450 return $validator->get_error();
451 }
452
453 return true;
454 }
455 }
456
457 if ( ! function_exists( 'booktics_snake_case' ) ) {
458 /**
459 * Convert a string to snake_case.
460 *
461 * @param string $string
462 *
463 * @return string
464 */
465 function booktics_snake_case( string $string ): string {
466 $string = preg_replace( '/([a-z])([A-Z])/', '$1_$2', $string );
467
468 return strtolower( str_replace( array( '-', ' ' ), '_', $string ) );
469 }
470 }
471
472 if ( ! function_exists( 'booktics_kebab_case' ) ) {
473 /**
474 * Convert a string to kebab-case.
475 *
476 * @param string $string
477 *
478 * @return string
479 */
480 function booktics_kebab_case( string $string ): string {
481 $string = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $string );
482
483 return strtolower( str_replace( array( '_', ' ' ), '-', $string ) );
484 }
485 }
486
487 if ( ! function_exists( 'booktics_camel_case' ) ) {
488 /**
489 * Convert a string to camelCase.
490 *
491 * @param string $string
492 *
493 * @return string
494 */
495 function booktics_camel_case( string $string ): string {
496 $string = str_replace( array( '-', '_' ), ' ', strtolower( $string ) );
497 $string = ucwords( $string );
498 $string = str_replace( ' ', '', $string );
499
500 return lcfirst( $string );
501 }
502 }
503
504 if ( ! function_exists( 'booktics_pascal_case' ) ) {
505 /**
506 * Convert a string to PascalCase.
507 *
508 * @param string $string
509 *
510 * @return string
511 */
512 function booktics_pascal_case( string $string ): string {
513 $string = str_replace( array( '-', '_' ), ' ', strtolower( $string ) );
514 $string = ucwords( $string );
515
516 return str_replace( ' ', '', $string );
517 }
518 }
519
520 if ( ! function_exists( 'booktics_date_range_loop' ) ) {
521 /**
522 * Make date range iterable array
523 *
524 * @param $start
525 * @param $end
526 * @param $step
527 * @param $format
528 *
529 * @return array
530 * @throws Exception
531 */
532 function booktics_date_range_loop( $start, $end, $step = '+1 day', $format = 'Y-m-d' ) {
533 $dates = array();
534
535 $current_date = new DateTime( $start );
536 $end_date = new DateTime( $end );
537
538 while ( $current_date <= $end_date ) {
539 $dates[] = $current_date->format( $format );
540 $current_date->modify( $step );
541 }
542
543 return $dates;
544 }
545
546 if ( ! function_exists( 'booktics_get_option' ) ) {
547 /**
548 * Get option for Booktics
549 *
550 * @param string $key Option key
551 * @param mixed $default Default value to return if option doesn't exist
552 *
553 * @return mixed
554 */
555 function booktics_get_option( $key = '', $default = false ) {
556 return \Booktics\Settings\Settings::get( $key, $default );
557 }
558 }
559
560 if ( ! function_exists( 'booktics_update_option' ) ) {
561 /**
562 * Update Booktics option
563 *
564 * @param string $key Option key
565 * @param mixed $value Option value
566 *
567 * @return void
568 */
569 function booktics_update_option( $key, $value ) {
570 \Booktics\Settings\Settings::update( $key, $value );
571 }
572 }
573 }
574
575 if ( ! function_exists( 'booktics_is_pro_active' ) ) {
576 /**
577 * Check Booktics Pro plugin activate or deactivate
578 *
579 * @return bool
580 */
581 function booktics_is_pro_active(): bool {
582 return is_plugin_active( 'booktics-pro/booktics-pro.php' );
583 }
584 }
585
586 if ( ! function_exists( 'booktics_extension' ) ) {
587 /**
588 * Get Booktics extension object from tools-sdk
589 */
590 function booktics_extension() {
591 return ( new \Arraytics\ToolsSdk\Extension( 'booktics_available_modules', booktics_modules_list() ) );
592 }
593 }
594
595 if ( ! function_exists( 'booktics_get_enabled_modules' ) ) {
596 /**
597 * Get enabled modules
598 *
599 * @return array
600 */
601 function booktics_get_enabled_modules(): array {
602 $modules = get_option( 'booktics_available_modules', array() );
603
604 if ( empty( $modules ) || ! is_array( $modules ) ) {
605 return array();
606 }
607
608 $enabled = array();
609
610 foreach ( $modules as $module_slug => $module_data ) {
611 if ( is_array( $module_data ) && isset( $module_data['status'] ) && $module_data['status'] === 'on' ) {
612 $enabled[] = $module_slug;
613 } elseif ( is_string( $module_data ) && $module_data === 'on' ) {
614 $enabled[] = $module_slug;
615 }
616 }
617
618 return $enabled;
619 }
620
621 if ( ! function_exists( 'get_booktics_modules_list' ) ) {
622 /**
623 * Get Booktics modules list
624 *
625 * @return array
626 */
627 function booktics_modules_list(): array {
628 $modules_list = array(
629 'stripe' => array(
630 'name' => 'stripe',
631 'slug' => 'stripe',
632 'type' => 'module',
633 'upgrade' => true,
634 'upgrade_link' => 'https://themewinter.com/purchase-history',
635 'status' => 'off',
636 'is_pro' => false,
637 'title' => __( 'Stripe', 'booktics' ),
638 'description' => __( 'It allows you to use stripe payment.', 'booktics' ),
639 'icon' => \Booktics\Extensions\Extension_Icon::get( 'stripe-addon' ),
640 'notice' => '',
641 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-stripe',
642 'settings_link' => '',
643 'doc_link' => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-stripe-in-booktics/',
644 ),
645 'woocommerce' => array(
646 'name' => 'woocommerce',
647 'slug' => 'woocommerce',
648 'type' => 'module',
649 'upgrade' => true,
650 'upgrade_link' => 'https://themewinter.com/purchase-history',
651 'status' => 'off',
652 'is_pro' => false,
653 'title' => __( 'WooCommerce', 'booktics' ),
654 'description' => __( 'Take booking payments through WooCommerce checkout.', 'booktics' ),
655 'icon' => \Booktics\Extensions\Extension_Icon::get( 'woocommerce-addon' ),
656 'notice' => '',
657 'demo_link' => '',
658 'settings_link' => '',
659 'doc_link' => 'https://arraytics.com/docs/docs/booktics/integration/woocommerce-payment-in-booktics-setup-guide/',
660 ),
661 'fluentcrm' => array(
662 'name' => 'fluentcrm',
663 'slug' => 'fluentcrm',
664 'type' => 'integration',
665 'upgrade' => true,
666 'upgrade_link' => 'https://themewinter.com/purchase-history',
667 'status' => 'off',
668 'is_pro' => false,
669 'title' => __( 'FluentCRM', 'booktics' ),
670 'description' => __( 'It allows you to use FluentCRM.', 'booktics' ),
671 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-fluentcrm-addon' ),
672 'notice' => '',
673 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-fluentcrm',
674 'settings_link' => '',
675 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-fluentcrm-with-booktics/',
676 ),
677 'pabbly' => array(
678 'name' => 'pabbly',
679 'slug' => 'pabbly',
680 'type' => 'integration',
681 'upgrade' => true,
682 'upgrade_link' => 'https://themewinter.com/purchase-history',
683 'status' => 'off',
684 'is_pro' => false,
685 'title' => __( 'Pabbly', 'booktics' ),
686 'description' => __( 'Send booking data to Pabbly Connect via webhook.', 'booktics' ),
687 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-pabbly-addon' ),
688 'notice' => '',
689 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-pabbly',
690 'settings_link' => '',
691 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-pabbly/',
692 ),
693 'zapier' => array(
694 'name' => 'zapier',
695 'slug' => 'zapier',
696 'type' => 'integration',
697 'upgrade' => true,
698 'upgrade_link' => 'https://themewinter.com/purchase-history',
699 'status' => 'off',
700 'is_pro' => false,
701 'title' => __( 'Zapier', 'booktics' ),
702 'description' => __( 'Send booking data to Zapier via webhook.', 'booktics' ),
703 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-zapier-addon' ),
704 'notice' => '',
705 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-zapier',
706 'settings_link' => '',
707 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-zapier/',
708 ),
709 'zoho' => array(
710 'name' => 'zoho',
711 'slug' => 'zoho',
712 'type' => 'integration',
713 'upgrade' => true,
714 'upgrade_link' => 'https://themewinter.com/purchase-history',
715 'status' => 'off',
716 'is_pro' => false,
717 'title' => __( 'Zoho', 'booktics' ),
718 'description' => __( 'Send booking data to Zoho via webhook.', 'booktics' ),
719 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-zoho-addon' ),
720 'notice' => '',
721 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-zoho',
722 'settings_link' => '',
723 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-zoho-crm/',
724 ),
725 'mailpoet' => array(
726 'name' => 'mailpoet',
727 'slug' => 'mailpoet',
728 'type' => 'integration',
729 'upgrade' => true,
730 'upgrade_link' => 'https://themewinter.com/purchase-history',
731 'status' => 'off',
732 'is_pro' => false,
733 'plugin_slug' => 'mailpoet',
734 'title' => __( 'MailPoet', 'booktics' ),
735 'description' => __( 'Add booking customers to your MailPoet lists.', 'booktics' ),
736 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-mailpoet-addon' ),
737 'notice' => '',
738 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-mailpoet',
739 'settings_link' => '',
740 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-mailpoet/',
741 ),
742 'google-calendar' => array(
743 'name' => 'google-calendar',
744 'slug' => 'google-calendar',
745 'type' => 'module',
746 'upgrade' => true,
747 'upgrade_link' => 'https://themewinter.com/purchase-history',
748 'status' => 'off',
749 'is_pro' => false,
750 'title' => __( 'Booktics Google Calendar', 'booktics' ),
751 'description' => __( 'It allows you to use google calendar.', 'booktics' ),
752 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-google-calendar' ),
753 'notice' => '',
754 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-google-calendar',
755 'settings_link' => '',
756 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-google-calendar-with-booktics/',
757 ),
758 'outlook-calendar' => array(
759 'name' => 'outlook-calendar',
760 'slug' => 'outlook-calendar',
761 'type' => 'module',
762 'upgrade' => true,
763 'upgrade_link' => 'https://themewinter.com/purchase-history',
764 'status' => 'off',
765 'is_pro' => true,
766 'title' => __( 'Booktics Outlook Calendar', 'booktics' ),
767 'description' => __( 'It allows you to use outlook calendar.', 'booktics' ),
768 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-outlook-calendar' ),
769 'notice' => '',
770 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-outlook-calendar-addon',
771 'settings_link' => '',
772 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-outlook-calendar-with-booktics/',
773 ),
774 'paypal' => array(
775 'name' => 'paypal',
776 'slug' => 'paypal',
777 'type' => 'module',
778 'upgrade' => true,
779 'upgrade_link' => 'https://themewinter.com/purchase-history',
780 'status' => 'off',
781 'is_pro' => true,
782 'title' => __( 'PayPal', 'booktics' ),
783 'description' => __( 'It allows you to use PayPal.', 'booktics' ),
784 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-paypal-addon' ),
785 'notice' => '',
786 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-paypal-addon',
787 'settings_link' => '',
788 'doc_link' => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-paypal-in-booktics-pro/',
789 ),
790 );
791
792 return $modules_list;
793 }
794 }
795 }
796
797 if ( ! function_exists( 'booktics_log' ) ) {
798 /**
799 * Write a debug line, but only when the site has debug logging switched on.
800 *
801 * @param string $message Message to log.
802 *
803 * @return void
804 */
805 function booktics_log( $message ) {
806 if ( ! defined( 'WP_DEBUG_LOG' ) || ! WP_DEBUG_LOG ) {
807 return;
808 }
809
810 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Guarded above; this is the plugin's only debug sink.
811 error_log( 'Booktics: ' . $message );
812 }
813 }
814
815 if ( ! function_exists( 'booktics_get_multisite_table_name' ) ) {
816 /**
817 * Get multisite-safe table name
818 *
819 * @param string $table_name
820 *
821 * @return string
822 */
823 function booktics_get_table_name( $table_name ) {
824 global $wpdb;
825 $prefix = $wpdb->prefix;
826 if ( strpos( $table_name, $prefix ) === 0 ) {
827 return $table_name;
828 }
829 return $wpdb->prefix . $table_name;
830 }
831 }
832
833 if ( ! function_exists( 'booktics_handle_conflicting_plugins' ) ) {
834 /**
835 * Deactivate conflicting plugins if active and check if they exist.
836 */
837 function booktics_handle_conflicting_plugins() {
838 // Avoid during AJAX or REST requests
839 if ( wp_doing_ajax() || defined( 'REST_REQUEST' ) ) {
840 return;
841 }
842
843 // Only proceed if user is logged in and can manage plugins
844 if ( ! is_user_logged_in() || ! current_user_can( 'activate_plugins' ) ) {
845 return;
846 }
847
848 // Prevent running repeatedly on every page load using a transient
849 if ( get_transient( 'booktics_conflict_check_done' ) ) {
850 return;
851 }
852
853 $conflicting_plugins = array(
854 'booktics-google-calendar-addon/booktics-google-calendar.php',
855 'booktics-outlook-calendar-addon/booktics-outlook-calendar-addon.php',
856 'booktics-paypal-addon/booktics-paypal-addon.php',
857 'booktics-stripe-addon/booktics_stripe.php',
858 'booktics-fluentcrm-addon/booktics-fluentcrm-addon.php',
859 );
860
861 $active_plugins = get_option( 'active_plugins', array() );
862 $deactivated_list = array();
863
864 foreach ( $conflicting_plugins as $plugin_file ) {
865 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
866
867 if ( file_exists( $plugin_path ) ) {
868 if ( in_array( $plugin_file, $active_plugins, true ) ) {
869 deactivate_plugins( $plugin_file );
870 $plugin_data = get_plugin_data( $plugin_path );
871 $deactivated_list[] = $plugin_data['Name'];
872 }
873 }
874 }
875
876 if ( ! empty( $deactivated_list ) ) {
877 // Store data in transient for 1 minute to show notice once
878 set_transient( 'booktics_deactivated_plugins_notice', $deactivated_list, MINUTE_IN_SECONDS );
879 }
880
881 // Set the conflict check as done (even if nothing was deactivated)
882 set_transient( 'booktics_conflict_check_done', true, MINUTE_IN_SECONDS );
883 }
884 }
885
886 if ( ! function_exists( 'booktics_show_conflict_deactivation_notice' ) ) {
887 /**
888 * Show a one-time admin notice for deactivated conflicting plugins.
889 */
890 function booktics_show_conflict_deactivation_notice() {
891 if ( ! current_user_can( 'activate_plugins' ) ) {
892 return;
893 }
894
895 $deactivated_list = get_transient( 'booktics_deactivated_plugins_notice' );
896
897 if ( ! empty( $deactivated_list ) ) {
898 delete_transient( 'booktics_deactivated_plugins_notice' );
899
900 $plugin_names = implode( ', ', array_map( 'esc_html', $deactivated_list ) );
901 ?>
902 <div class="notice notice-warning is-dismissible">
903 <p>
904 <?php echo esc_html__( 'No need for addons! The following addon plugins have been added as modules in Booktics:', 'booktics' ); ?>
905 <strong><?php echo esc_html( $plugin_names ); ?></strong>
906 <br><?php echo esc_html__( 'Please deactivate and remove these plugins to avoid future issues. Don\'t worry, it will not affect your existing data and the features will remain the same.', 'booktics' ); ?>
907 </p>
908 </div>
909 <?php
910 }
911 }
912 }
913
914 if ( ! function_exists( 'booktics_time_to_gmt' ) ) {
915 /**
916 * Convert time from site timezone to GMT/UTC
917 *
918 * @param string $time Time string (e.g., "9:00 AM", "09:00:00")
919 * @param string|int $context_date Date context (Y-m-d or 0 for regular schedules)
920 * @return string Time in H:i:s format (GMT/UTC)
921 * @since 1.0.15
922 */
923 function booktics_time_to_gmt( string $time, $context_date = 0 ): string {
924 // Skip '0' placeholder for holidays
925 if ( $time === '0' || $time === 0 ) {
926 return '0';
927 }
928
929 // For regular schedules (context_date = 0), use current date as context
930 // For custom schedules, use the actual date for DST-aware conversion
931 $date = ( $context_date === 0 || $context_date === '0' || empty( $context_date ) )
932 ? gmdate( 'Y-m-d' )
933 : $context_date;
934
935 $timestamp = strtotime( $time );
936 if ( $timestamp === false ) {
937 return $time;
938 }
939 $formatted_time = gmdate( 'H:i:s', $timestamp );
940 $datetime_string = $date . ' ' . $formatted_time;
941
942 $gmt_datetime = get_gmt_from_date( $datetime_string );
943
944 // Extract and return only the time part
945 return gmdate( 'H:i:s', strtotime( $gmt_datetime ) );
946 }
947 }
948
949 if ( ! function_exists( 'booktics_is_admin_or_team_member' ) ) {
950 /**
951 * Check if the current user is an admin or a Booktics team member.
952 *
953 * @return bool
954 * @since 1.0.16
955 */
956 function booktics_is_admin_or_team_member() {
957 if ( current_user_can( 'manage_options' ) ) {
958 return true;
959 }
960 if ( is_user_logged_in() ) {
961 $roles = (array) wp_get_current_user()->roles;
962 if ( in_array( 'booktics_team_member', $roles, true ) ) {
963 return true;
964 }
965 }
966 return false;
967 }
968 }
969
970 if ( ! function_exists( 'booktics_is_team_member' ) ) {
971 function booktics_is_team_member() {
972 if ( is_user_logged_in() ) {
973 $roles = (array) wp_get_current_user()->roles;
974 if ( in_array( 'booktics_team_member', $roles, true ) ) {
975 return true;
976 }
977 }
978 return false;
979 }
980 }
981
982 if ( ! function_exists( 'booktics_time_from_gmt' ) ) {
983 /**
984 * Convert time from GMT/UTC to site timezone
985 *
986 * @param string $time Time string in UTC (e.g., "14:00:00")
987 * @param string|int $context_date Date context (Y-m-d or 0 for regular schedules)
988 * @return string Time in H:i:s format (site timezone)
989 * @since 1.0.15
990 */
991 function booktics_time_from_gmt( string $time, $context_date = 0 ): string {
992 // Skip '0' placeholder for holidays
993 if ( $time === '0' || $time === 0 ) {
994 return '0';
995 }
996
997 $date = ( $context_date === 0 || $context_date === '0' || empty( $context_date ) )
998 ? gmdate( 'Y-m-d' )
999 : $context_date;
1000
1001 $timestamp = strtotime( $time );
1002 if ( $timestamp === false ) {
1003 return $time;
1004 }
1005 $formatted_time = gmdate( 'H:i:s', $timestamp );
1006 $gmt_datetime_string = $date . ' ' . $formatted_time;
1007
1008 $local_datetime = get_date_from_gmt( $gmt_datetime_string );
1009
1010 return gmdate( 'H:i:s', strtotime( $local_datetime ) );
1011 }
1012 }
1013
1014 if ( ! function_exists( 'booktics_safe_maybe_unserialize' ) ) {
1015 /**
1016 * Safely unserialize a value without ever instantiating PHP objects.
1017 *
1018 * @param mixed $value Possibly serialized value.
1019 *
1020 * @return mixed Unserialized value, or the original value when not serialized.
1021 */
1022 function booktics_safe_maybe_unserialize( $value ) {
1023 if ( is_string( $value ) && is_serialized( $value ) ) {
1024 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- allowed_classes => false prevents PHP Object Injection.
1025 return @unserialize( trim( $value ), array( 'allowed_classes' => false ) );
1026 }
1027
1028 return $value;
1029 }
1030 }
1031
1032 if ( ! function_exists( 'booktics_sanitize_id_string' ) ) {
1033 /**
1034 * Sanitize a free-form identifier received from a request.
1035 *
1036 * Returns a plain-text string id (e.g. 'default' or a client-generated id such
1037 * as a uuid/nanoid). A legitimate identifier is never a PHP-serialized string,
1038 * so any value that looks serialized is dropped to '' — this neutralizes PHP
1039 * Object Injection payloads at the storage boundary while leaving every real id
1040 * untouched. Non-scalar input (arrays/objects) is rejected to ''.
1041 *
1042 * @param mixed $value Raw value from the request.
1043 *
1044 * @return string Sanitized identifier, or '' when missing, non-scalar, or serialized.
1045 */
1046 function booktics_sanitize_id_string( $value ) {
1047 if ( ! is_scalar( $value ) ) {
1048 return '';
1049 }
1050
1051 $value = sanitize_text_field( (string) $value );
1052
1053 return is_serialized( $value ) ? '' : $value;
1054 }
1055 }
1056
1057 if ( ! function_exists( 'booktics_aisentic_identity' ) ) {
1058 /**
1059 * Build the site identity offered to Aisentic when the user opts in.
1060 *
1061 * The consent UI and the send path both read this helper, so the email shown
1062 * on the consent checkbox is always the email that actually leaves the site.
1063 *
1064 * @return array{name: string, email: string, site_url: string}
1065 */
1066 function booktics_aisentic_identity(): array {
1067 $name = booktics_get_option( 'business_name', get_bloginfo( 'name' ) );
1068 $email = booktics_get_option( 'business_email', get_option( 'admin_email' ) );
1069
1070 return array(
1071 'name' => sanitize_text_field( (string) $name ),
1072 'email' => sanitize_email( (string) $email ),
1073 'site_url' => esc_url_raw( site_url() ),
1074 );
1075 }
1076 }
1077
1078 if ( ! function_exists( 'booktics_aisentic_is_registered' ) ) {
1079 /**
1080 * Whether Aisentic already holds an API key.
1081 *
1082 * Reading Aisentic's own storage instead of our consent option means anyone
1083 * who connected from Aisentic's screens is never nagged by our banner.
1084 *
1085 * @return bool True when an Aisentic API key is present.
1086 */
1087 function booktics_aisentic_is_registered(): bool {
1088 if ( function_exists( 'aisentic_get_option' ) ) {
1089 return ! empty( aisentic_get_option( 'llmProviders.aisentic.apiKey', '' ) );
1090 }
1091
1092 $settings = get_option( 'aisentic_settings', array() );
1093
1094 return ! empty( $settings['llmProviders']['aisentic']['apiKey'] );
1095 }
1096 }
1097