PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.28
Booktics – Appointment Booking Calendar for Service Businesses v1.0.28
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 +310 -100 1.0.12 → 1.0.28 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',
@@ -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,74 +784,10 @@
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 - 'timetics' => array(
719 - 'name' => 'timetics',
720 - 'slug' => 'timetics',
721 - 'type' => 'plugin',
722 - 'upgrade' => false,
723 - 'upgrade_link' => 'https://themewinter.com/purchase-history',
724 - 'status' => 'on',
725 - 'is_pro' => false,
726 - 'title' => __( 'WP Timetics', 'booktics' ),
727 - 'description' => __( 'Appointment Booking and Scheduling Calendar Plugin - WP Timetics', 'booktics' ),
728 - 'icon' => \Booktics\Extensions\Extension_Icon::get( 'timetics' ),
729 - 'notice' => '',
730 - 'demo_link' => 'https://arraytics.com/timetics',
731 - 'settings_link' => '',
732 - 'doc_link' => 'https://docs.arraytics.com/docs/timetics/getting-started/',
733 - ),
734 - 'poptics' => array(
735 - 'name' => 'poptics',
736 - 'slug' => 'poptics',
737 - 'type' => 'plugin',
738 - 'upgrade' => false,
739 - 'upgrade_link' => 'https://themewinter.com/purchase-history',
740 - 'status' => 'on',
741 - 'is_pro' => false,
742 - 'title' => __( 'Poptics', 'booktics' ),
743 - 'description' => __( 'Discover the ultimate popup mix with Poptics Popup Builder to boost your lead generation and sales conversions.', 'booktics' ),
744 - 'icon' => \Booktics\Extensions\Extension_Icon::get( 'poptics' ),
745 - 'notice' => '',
746 - 'demo_link' => 'https://demo.aethonic.com/popticsadmin/',
747 - 'settings_link' => '',
748 - 'doc_link' => 'https://docs.aethonic.com/docs/getting-started/intro/',
749 - ),
750 - 'eventin' => array(
751 - 'name' => 'eventin',
752 - 'slug' => 'eventin',
753 - 'type' => 'plugin',
754 - 'upgrade' => false,
755 - 'upgrade_link' => 'https://themewinter.com/purchase-history',
756 - 'status' => 'on',
757 - 'is_pro' => false,
758 - 'title' => __( 'Eventin', 'booktics' ),
759 - 'description' => __( 'Best Free WordPress Event Calendar Plugin to manage booking, registration, RSVPs, schedule recurring events and sell event tickets with WooCommerce.', 'booktics' ),
760 - 'icon' => \Booktics\Extensions\Extension_Icon::get( 'eventin' ),
761 - 'notice' => '',
762 - 'demo_link' => 'https://product.themewinter.com/eventin',
763 - 'settings_link' => '',
764 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/eventin/',
765 - ),
766 - 'wpcafe' => array(
767 - 'name' => 'wpcafe',
768 - 'slug' => 'wpcafe',
769 - 'type' => 'plugin',
770 - 'upgrade' => false,
771 - 'upgrade_link' => 'https://themewinter.com/purchase-history',
772 - 'status' => 'on',
773 - 'is_pro' => false,
774 - 'title' => __( 'WPCafe', 'booktics' ),
775 - 'description' => __( 'WPCafe - A restaurant plugin to increase sales with an Online Ordering System, Delivery, Pickup, Food Menu, Reservations, and Table Management.', 'booktics' ),
776 - 'icon' => \Booktics\Extensions\Extension_Icon::get( 'wpcafe' ),
777 - 'notice' => '',
778 - 'demo_link' => 'https://product.themewinter.com/wpcafe',
779 - 'settings_link' => '',
780 - 'doc_link' => 'https://support.themewinter.com/docs/plugins/docs/wp-cafe/',
781 - ),
782 790 );
783 791
784 792 return $modules_list;
785 793 }
@@ -785,8 +793,26 @@
785 793 }
786 794 }
787 795 }
788 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 +
789 815 if ( ! function_exists( 'booktics_get_multisite_table_name' ) ) {
790 816 /**
791 817 * Get multisite-safe table name
792 818 *
@@ -875,9 +901,9 @@
875 901 ?>
876 902 <div class="notice notice-warning is-dismissible">
877 903 <p>
878 904 <?php echo esc_html__( 'No need for addons! The following addon plugins have been added as modules in Booktics:', 'booktics' ); ?>
879 - <strong><?php echo $plugin_names; ?></strong>
905 + <strong><?php echo esc_html( $plugin_names ); ?></strong>
880 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' ); ?>
881 907 </p>
882 908 </div>
883 909 <?php
@@ -882,5 +908,189 @@
882 908 </div>
883 909 <?php
884 910 }
885 911 }
886 -}
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 +}