PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.29
Booktics – Appointment Booking Calendar for Service Businesses v1.0.29
1.0.29 1.0.28 1.0.27 1.0.26 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 All 31 releases
← All changes | utils/global-helper.php +449 -41 1.0.2 → 1.0.29 View file →
@@ -1,6 +1,11 @@
1 1 <?php
2 -
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 +}
3 8 /**
4 9 * Get timezone list.
5 10 *
6 11 * @return array
@@ -273,44 +278,30 @@
273 278
274 279 if ( ! function_exists( 'booktics_is_woocommerce_active' ) ) {
275 280
276 281 /**
277 - * Check woocommerce is active or not
282 + * Check whether WooCommerce payments are usable.
278 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 + *
279 288 * @return bool
280 289 */
281 290 function booktics_is_woocommerce_active() {
291 + if ( ! class_exists( 'WooCommerce' ) ) {
292 + return false;
293 + }
282 294
283 - if ( function_exists( 'WC' ) && booktics_get_option( 'wc_integration' ) ) {
284 - return true;
295 + if ( ! booktics_extension()->is_enabled( 'woocommerce' ) ) {
296 + return false;
285 297 }
286 298
287 - return false;
299 + return (bool) booktics_get_option( 'enable_woocommerce_payment' );
288 300 }
289 301 }
290 302
291 -if ( ! function_exists( 'booktics_add_woocommerce_product_cat' ) ) {
292 - /**
293 - * Add woocommerce product category
294 - *
295 - * @return void
296 - */
297 - function booktics_add_woocommerce_product_cat() {
298 - $category_name = __( 'Booktics Meeting', 'booktics' );
299 303
300 - // Prepare category arguments
301 - $category_args = array(
302 - 'taxonomy' => 'product_cat',
303 - 'cat_name' => $category_name,
304 - 'category_description' => __( 'Booktics meeting', 'booktics' ),
305 - 'slug' => 'booktics-meeting',
306 - );
307 -
308 - // Insert the category
309 - wp_insert_term( $category_args['cat_name'], $category_args['taxonomy'], $category_args );
310 - }
311 -}
312 -
313 304 if ( ! function_exists( 'booktics_is_valid_timezone' ) ) {
314 305 /**
315 306 * Check a timezone is valid or not
316 307 *
@@ -580,15 +571,15 @@
580 571 }
581 572 }
582 573 }
583 574
584 -if ( ! function_exists( 'is_booktics_pro' ) ) {
575 +if ( ! function_exists( 'booktics_is_pro_active' ) ) {
585 576 /**
586 577 * Check Booktics Pro plugin activate or deactivate
587 578 *
588 579 * @return bool
589 580 */
590 - function is_booktics_pro(): bool {
581 + function booktics_is_pro_active(): bool {
591 582 return is_plugin_active( 'booktics-pro/booktics-pro.php' );
592 583 }
593 584 }
594 585
@@ -648,14 +639,30 @@
648 639 'icon' => \Booktics\Extensions\Extension_Icon::get( 'stripe-addon' ),
649 640 'notice' => '',
650 641 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-stripe',
651 642 'settings_link' => '',
652 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-stripe',
643 + 'doc_link' => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-stripe-in-booktics/',
653 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 + ),
654 661 'fluentcrm' => array(
655 662 'name' => 'fluentcrm',
656 663 'slug' => 'fluentcrm',
657 - 'type' => 'module',
664 + 'type' => 'integration',
658 665 'upgrade' => true,
659 666 'upgrade_link' => 'https://themewinter.com/purchase-history',
660 667 'status' => 'off',
661 668 'is_pro' => false,
@@ -664,10 +671,75 @@
664 671 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-fluentcrm-addon' ),
665 672 'notice' => '',
666 673 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-fluentcrm',
667 674 'settings_link' => '',
668 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-google-calendar',
675 + 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-fluentcrm-with-booktics/',
669 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 + ),
670 742 'google-calendar' => array(
671 743 'name' => 'google-calendar',
672 744 'slug' => 'google-calendar',
673 745 'type' => 'module',
@@ -673,9 +745,9 @@
673 745 'type' => 'module',
674 746 'upgrade' => true,
675 747 'upgrade_link' => 'https://themewinter.com/purchase-history',
676 748 'status' => 'off',
677 - 'is_pro' => true,
749 + 'is_pro' => false,
678 750 'title' => __( 'Booktics Google Calendar', 'booktics' ),
679 751 'description' => __( 'It allows you to use google calendar.', 'booktics' ),
680 752 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-google-calendar' ),
681 753 'notice' => '',
@@ -680,9 +752,9 @@
680 752 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-google-calendar' ),
681 753 'notice' => '',
682 754 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-google-calendar',
683 755 'settings_link' => '',
684 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-google-calendar',
756 + 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-google-calendar-with-booktics/',
685 757 ),
686 758 'outlook-calendar' => array(
687 759 'name' => 'outlook-calendar',
688 760 'slug' => 'outlook-calendar',
@@ -696,9 +768,9 @@
696 768 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-outlook-calendar' ),
697 769 'notice' => '',
698 770 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-outlook-calendar-addon',
699 771 'settings_link' => '',
700 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-outlook-calendar-addon',
772 + 'doc_link' => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-outlook-calendar-with-booktics/',
701 773 ),
702 774 'paypal' => array(
703 775 'name' => 'paypal',
704 776 'slug' => 'paypal',
@@ -712,9 +784,9 @@
712 784 'icon' => \Booktics\Extensions\Extension_Icon::get( 'booktics-paypal-addon' ),
713 785 'notice' => '',
714 786 'demo_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-paypal-addon',
715 787 'settings_link' => '',
716 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-paypal-addon',
788 + 'doc_link' => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-paypal-in-booktics-pro/',
717 789 ),
718 790 );
719 791
720 792 return $modules_list;
@@ -721,8 +793,26 @@
721 793 }
722 794 }
723 795 }
724 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 +
725 815 if ( ! function_exists( 'booktics_get_multisite_table_name' ) ) {
726 816 /**
727 817 * Get multisite-safe table name
728 818 *
@@ -731,16 +821,334 @@
731 821 * @return string
732 822 */
733 823 function booktics_get_table_name( $table_name ) {
734 824 global $wpdb;
735 - if ( is_multisite() ) {
736 - $prefix = $wpdb->prefix . get_current_blog_id();
737 - if ( strpos( $table_name, $prefix ) === 0 ) {
738 - return $table_name;
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 + }
739 873 }
874 + }
740 875
741 - return $wpdb->prefix . get_current_blog_id() . '_' . $table_name;
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 );
742 879 }
743 880
744 - return $wpdb->prefix . $table_name;
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 +
1098 +if ( ! function_exists( 'booktics_get_store_currency' ) ) {
1099 + /**
1100 + * The currency the merchant configured for the store, lowercased.
1101 + *
1102 + * Public checkout must always price in this currency. A currency sent by the
1103 + * buyer cannot be trusted: the total is computed server side in the store
1104 + * currency, so relabeling it as another currency changes what is charged.
1105 + *
1106 + * @return string Lowercase ISO 4217 currency code.
1107 + */
1108 + function booktics_get_store_currency(): string {
1109 + $currency = strtolower( trim( (string) booktics_get_option( 'currency', 'USD' ) ) );
1110 +
1111 + // An empty or malformed setting would make every Stripe charge fail.
1112 + return preg_match( '/^[a-z]{3}$/', $currency ) ? $currency : 'usd';
1113 + }
1114 +}
1115 +
1116 +if ( ! function_exists( 'booktics_amount_to_minor_units' ) ) {
1117 + /**
1118 + * Convert a decimal amount to the smallest unit of a currency.
1119 + *
1120 + * Stripe takes amounts in minor units, and not every currency has two
1121 + * decimals: 100 JPY is sent as 100, 100 USD as 10000 and 100 KWD as 100000.
1122 + * ISK and UGX are zero-decimal but Stripe still expects them as whole
1123 + * amounts times 100, so 5 UGX is sent as 500.
1124 + *
1125 + * @see https://docs.stripe.com/currencies#minor-units
1126 + *
1127 + * @param float|int|string $amount Amount in major units, e.g. 19.99.
1128 + * @param string $currency ISO 4217 currency code.
1129 + *
1130 + * @return int Amount in minor units.
1131 + */
1132 + function booktics_amount_to_minor_units( $amount, string $currency ): int {
1133 + $zero_decimal = array( 'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga', 'pyg', 'rwf', 'vnd', 'vuv', 'xaf', 'xof', 'xpf' );
1134 + $whole_x100 = array( 'isk', 'ugx' );
1135 + $three_decimal = array( 'bhd', 'jod', 'kwd', 'omr', 'tnd' );
1136 + $currency = strtolower( $currency );
1137 + $amount = (float) $amount;
1138 +
1139 + if ( in_array( $currency, $zero_decimal, true ) ) {
1140 + return (int) round( $amount );
1141 + }
1142 +
1143 + if ( in_array( $currency, $whole_x100, true ) ) {
1144 + return (int) round( $amount ) * 100;
1145 + }
1146 +
1147 + if ( in_array( $currency, $three_decimal, true ) ) {
1148 + // Stripe requires the last digit of three-decimal amounts to be 0.
1149 + return (int) ( round( $amount * 100 ) * 10 );
1150 + }
1151 +
1152 + return (int) round( $amount * 100 );
745 1153 }
746 1154 }