# booktics/1.0.29/utils/global-helper.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.29. 1,155 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.29/code/utils/global-helper.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.29/raw/utils/global-helper.php
- Modified: 2026-10-11T10:01:32+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booktics/1.0.29/code/utils/global-helper.php#L10-L20`.

```php
<?php
if ( ! defined( 'ABSPATH' ) ) {
    // Composer autoloads this file, so CLI tooling loads it without WordPress.
    if ( PHP_SAPI !== 'cli' ) {
        exit;
    }
}
/**
 * Get timezone list.
 *
 * @return  array
 * @since 1.0.0
 */
if ( ! function_exists( 'booktics_get_timezone_list' ) ) {
    function booktics_get_timezone_list() {
        $tzlist = wp_timezone_choice( 'UTC' );
        $doc    = new DOMDocument();
        $doc->loadHTML( $tzlist );

        $elements  = $doc->getElementsByTagName( 'option' );
        $timezones = array();

        if ( ! empty( $elements ) ) {
            foreach ( $elements as $element ) {
                $data = array(
                    'value' => $element->getAttribute( 'value' ),
                    'name'  => $element->textContent,
                    // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
                );

                $timezones[] = $data;
            }
        }

        return $timezones;
    }
}

if ( ! function_exists( 'booktics_get_auth_redirect_uri' ) ) {
    /**
     * Get auth redirect uri
     *
     * @param string $auth
     *
     * @return  string
     */
    function booktics_get_auth_redirect_uri( $auth = '' ) {
        return site_url( 'booktics-integration/' . $auth );
    }
}


if ( ! function_exists( 'booktics_get_google_auth' ) ) {
    /**
     * Get google auth
     *
     * @param integer $user_id
     *
     * @return  array
     */
    function booktics_get_google_auth( $user_id = 0 ) {
        return get_user_meta( $user_id, 'booktics_google_auth', true );
    }
}

if ( ! function_exists( 'booktics_get_posts' ) ) {
    /**
     * Get posts with date range
     *
     * @param array $args
     *
     * @return  WP_Query
     */
    function booktics_get_posts( $args = array() ) {
        $defaults = array(
            'post_type'   => 'post',
            'post_status' => 'publish',
            'nopaging'    => true,
            'date_range'  => '',
        );

        $args = wp_parse_args( $args, $defaults );

        // Date range found.
        if ( $args['date_range'] ) {
            $from_date = $args['date_range']['start'];
            $to_date   = $args['date_range']['end'];

            $args['date_query'] = array(
                'relation' => 'AND',
                array(
                    'before'    => array(
                        'year'  => gmdate( 'Y', strtotime( $to_date ) ),
                        'month' => gmdate( 'm', strtotime( $to_date ) ),
                        'day'   => gmdate( 'd', strtotime( $to_date ) ),
                    ),
                    'after'     => array(
                        'year'  => gmdate( 'Y', strtotime( $from_date ) ),
                        'month' => gmdate( 'm', strtotime( $from_date ) ),
                        'day'   => gmdate( 'd', strtotime( $from_date ) ),
                    ),
                    'inclusive' => true,
                ),
            );
        }

        // The Query
        $posts = new WP_Query( $args );

        return $posts;
    }
}

if ( ! function_exists( 'booktics_count_posts()' ) ) {
    /**
     * Count post with date range or without date range
     *
     * @param array $args Required query params
     *
     * @return  integer
     */
    function booktics_count_posts( $args = array() ) {
        $posts = booktics_get_posts( $args );

        return $posts->post_count;
    }
}

if ( ! function_exists( 'booktics_count_users' ) ) {
    /**
     * Count user by role and date range
     *
     * @param array $args Required query params
     *
     * @return  integer
     */
    function booktics_count_users( $args = array() ) {
        $defaults = array(
            'role'        => '',
            'date_range'  => '',
            'count_total' => true,
        );

        $args = wp_parse_args( $args, $defaults );

        // Date range found.
        if ( $args['date_range'] ) {
            $from_date = $args['date_range']['start'];
            $to_date   = $args['date_range']['end'];

            $args['date_query'] = array(
                'relation' => 'AND',
                array(
                    'before'    => array(
                        'year'  => gmdate( 'Y', strtotime( $to_date ) ),
                        'month' => gmdate( 'm', strtotime( $to_date ) ),
                        'day'   => gmdate( 'd', strtotime( $to_date ) ),
                    ),
                    'after'     => array(
                        'year'  => gmdate( 'Y', strtotime( $from_date ) ),
                        'month' => gmdate( 'm', strtotime( $from_date ) ),
                        'day'   => gmdate( 'd', strtotime( $from_date ) ),
                    ),
                    'inclusive' => true,
                ),
            );
        }

        // The Query
        $user_query = new WP_User_Query( $args );

        // Return total users.
        return $user_query->total_users;
    }
}


if ( ! function_exists( 'booktics_get_default_timezone' ) ) {
    /**
     * Get default timezone
     *
     * @return  string
     */
    function booktics_get_default_timezone() {
        $timezone = get_option( 'timezone_string' );

        return $timezone;
    }
}

if ( ! function_exists( 'booktics_get_currencies' ) ) {
    /**
     * Get currency list
     *
     * @return  array
     */
    function booktics_get_currencies() {
        $currencies = require_once __DIR__ . '/currency.php';

        return $currencies;
    }
}

if ( ! function_exists( 'booktics_update_default_settings' ) ) {

    /**
     * Update default settings
     *
     * @return void
     */
    function booktics_update_default_settings() {
        $settings = array(
            'google_auth_redirect_uri' => booktics_get_auth_redirect_uri( 'google-auth' ),
            'default_booking_status'   => 'pending',
            'currency'                 => 'USD',
            'primary_color'            => '#3161F1',
            'secondary_color'          => '#6188ff',
        );

        $settings = apply_filters( 'booktics_default_settings', $settings );

        foreach ( $settings as $key => $value ) {
            booktics_update_option( $key, $value );
        }
    }
}

if ( ! function_exists( 'booktics_get_post_status' ) ) {

    /**
     * Get post status
     *
     * @return  array
     */
    function booktics_get_post_status() {
        return array(
            'approved',
            'pending',
            'cancel',
            'completed',
        );
    }
}

if ( ! function_exists( 'booktics_replace_placeholder' ) ) {
    /**
     * Replace string with placeholder
     *
     * @param array $placeholders
     * @param string $text
     *
     * @return  string
     */
    function booktics_replace_placeholder( $text, $placeholders = array() ) {

        if ( ! $placeholders ) {
            return $text;
        }

        foreach ( $placeholders as $placeholder => $replace_with ) {
            $text = str_replace( $placeholder, $replace_with, $text );
        }

        return $text;
    }
}

if ( ! function_exists( 'booktics_register_cron' ) ) {
    /**
     * Register booktics cron
     *
     * @return  void
     */
    function booktics_register_cron() {
        wp_schedule_event( time(), 'daily', 'booktics_booking_clear_schedule' );
    }
}

if ( ! function_exists( 'booktics_is_woocommerce_active' ) ) {

    /**
     * Check whether WooCommerce payments are usable.
     *
     * True only when WooCommerce itself is running, the module is enabled and
     * the payment setting is on. Any one of the three being false means the
     * gateway must not be offered.
     *
     * @return  bool
     */
    function booktics_is_woocommerce_active() {
        if ( ! class_exists( 'WooCommerce' ) ) {
            return false;
        }

        if ( ! booktics_extension()->is_enabled( 'woocommerce' ) ) {
            return false;
        }

        return (bool) booktics_get_option( 'enable_woocommerce_payment' );
    }
}


if ( ! function_exists( 'booktics_is_valid_timezone' ) ) {
    /**
     * Check a timezone is valid or not
     *
     * @param string $timezone
     *
     * @return  bool
     */
    function booktics_is_valid_timezone( $timezone ) {
        try {
            new DateTimeZone( $timezone );
        } catch ( Exception $e ) {
            return false;
        }

        return true;
    }
}

if ( ! function_exists( 'booktics_datetime' ) ) {

    /**
     * Booktics date time with timezone
     *
     * @param int | string $date
     * @param string $timezone
     *
     * @return  string
     */
    function booktics_datetime( $format, $date, $timezone = '' ) {
        $date = is_int( $date ) ? gmdate( 'Y-m-d g:ia', $date ) : $date;

        if ( ! $timezone ) {
            $timezone = 'UTC';
        }

        // Create a DateTime object with the provided timestamp and time zone
        $datetime = new \DateTime( $date, new \DateTimeZone( $timezone ) );

        // Get the converted timestamp
        return $datetime->format( $format );
    }
}

if ( ! function_exists( 'booktics_convert_timezone' ) ) {

    /**
     * Convert date and time from timezone to another timezone
     *
     * @param string $format
     * @param string $date_time
     * @param string $from
     * @param string $to
     *
     * @return  \DateTime
     */
    function booktics_convert_timezone( $date_time_string, $from, $to ) {

        if ( empty( $from ) || empty( $to ) ) {
            return new DateTime( $date_time_string );
        }

        $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 ) );

        $datetime = new DateTime( $date_time_string, new DateTimeZone( $from ) );
        $datetime->setTimezone( new DateTimeZone( $to ) );

        return $datetime;
    }
}

if ( ! function_exists( 'booktics_generate_username' ) ) {
    /**
     * Generate unique username
     *
     * @param string $email
     *
     * @return  string
     */
    function booktics_generate_username( $email ) {
        $username = strtok( $email, '@' );

        if ( username_exists( $username ) ) {
            $username = $username . wp_rand( 10, 100 );
        }

        return $username;
    }
}

if ( ! function_exists( 'booktics_is_json' ) ) {
    /**
     * Check a string is valid json or not
     *
     * @param string $json_string
     *
     * @return  bool
     */
    function booktics_is_json( $json_string ) {
        if ( ! is_string( $json_string ) ) {
            return false;
        }

        $data = json_decode( $json_string, true );

        return $data !== null && json_last_error() === JSON_ERROR_NONE;
    }
}


if ( ! function_exists( 'booktics_get_current_user' ) ) {
    /**
     * Get current user data
     *
     * @return  array
     */
    function booktics_get_current_user() {
        $user_id = get_current_user_id();
        $user    = get_userdata( $user_id );

        $user_data = array(
            'id'       => $user->ID,
            'username' => $user->user_login,
            'email'    => $user->user_email,
            'roles'    => $user->roles,
        );

        return $user_data;
    }
}

if ( ! function_exists( 'booktics_validate' ) ) {
    /**
     * Validate user input
     *
     * @param array $request
     * @param array $rules
     *
     * @return  bool | WP_Error
     */
    function booktics_validate( $request, $rules ) {
        $validator = new \Booktics\Validation\Validator( $request );

        $validator->set_rules( $rules );

        if ( ! $validator->validate() ) {
            return $validator->get_error();
        }

        return true;
    }
}

if ( ! function_exists( 'booktics_snake_case' ) ) {
    /**
     * Convert a string to snake_case.
     *
     * @param string $string
     *
     * @return  string
     */
    function booktics_snake_case( string $string ): string {
        $string = preg_replace( '/([a-z])([A-Z])/', '$1_$2', $string );

        return strtolower( str_replace( array( '-', ' ' ), '_', $string ) );
    }
}

if ( ! function_exists( 'booktics_kebab_case' ) ) {
    /**
     * Convert a string to kebab-case.
     *
     * @param string $string
     *
     * @return  string
     */
    function booktics_kebab_case( string $string ): string {
        $string = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $string );

        return strtolower( str_replace( array( '_', ' ' ), '-', $string ) );
    }
}

if ( ! function_exists( 'booktics_camel_case' ) ) {
    /**
     * Convert a string to camelCase.
     *
     * @param string $string
     *
     * @return  string
     */
    function booktics_camel_case( string $string ): string {
        $string = str_replace( array( '-', '_' ), ' ', strtolower( $string ) );
        $string = ucwords( $string );
        $string = str_replace( ' ', '', $string );

        return lcfirst( $string );
    }
}

if ( ! function_exists( 'booktics_pascal_case' ) ) {
    /**
     * Convert a string to PascalCase.
     *
     * @param string $string
     *
     * @return  string
     */
    function booktics_pascal_case( string $string ): string {
        $string = str_replace( array( '-', '_' ), ' ', strtolower( $string ) );
        $string = ucwords( $string );

        return str_replace( ' ', '', $string );
    }
}

if ( ! function_exists( 'booktics_date_range_loop' ) ) {
    /**
     * Make date range iterable array
     *
     * @param $start
     * @param $end
     * @param $step
     * @param $format
     *
     * @return array
     * @throws Exception
     */
    function booktics_date_range_loop( $start, $end, $step = '+1 day', $format = 'Y-m-d' ) {
        $dates = array();

        $current_date = new DateTime( $start );
        $end_date     = new DateTime( $end );

        while ( $current_date <= $end_date ) {
            $dates[] = $current_date->format( $format );
            $current_date->modify( $step );
        }

        return $dates;
    }

    if ( ! function_exists( 'booktics_get_option' ) ) {
        /**
         * Get option for Booktics
         *
         * @param string $key Option key
         * @param mixed $default Default value to return if option doesn't exist
         *
         * @return mixed
         */
        function booktics_get_option( $key = '', $default = false ) {
            return \Booktics\Settings\Settings::get( $key, $default );
        }
    }

    if ( ! function_exists( 'booktics_update_option' ) ) {
        /**
         * Update Booktics option
         *
         * @param string $key Option key
         * @param mixed $value Option value
         *
         * @return void
         */
        function booktics_update_option( $key, $value ) {
            \Booktics\Settings\Settings::update( $key, $value );
        }
    }
}

if ( ! function_exists( 'booktics_is_pro_active' ) ) {
    /**
     * Check Booktics Pro plugin activate or deactivate
     *
     * @return bool
     */
    function booktics_is_pro_active(): bool {
        return is_plugin_active( 'booktics-pro/booktics-pro.php' );
    }
}

if ( ! function_exists( 'booktics_extension' ) ) {
    /**
     * Get Booktics extension object from tools-sdk
     */
    function booktics_extension() {
        return ( new \Arraytics\ToolsSdk\Extension( 'booktics_available_modules', booktics_modules_list() ) );
    }
}

if ( ! function_exists( 'booktics_get_enabled_modules' ) ) {
    /**
     * Get enabled modules
     *
     * @return array
     */
    function booktics_get_enabled_modules(): array {
        $modules = get_option( 'booktics_available_modules', array() );

        if ( empty( $modules ) || ! is_array( $modules ) ) {
            return array();
        }

        $enabled = array();

        foreach ( $modules as $module_slug => $module_data ) {
            if ( is_array( $module_data ) && isset( $module_data['status'] ) && $module_data['status'] === 'on' ) {
                $enabled[] = $module_slug;
            } elseif ( is_string( $module_data ) && $module_data === 'on' ) {
                $enabled[] = $module_slug;
            }
        }

        return $enabled;
    }

    if ( ! function_exists( 'get_booktics_modules_list' ) ) {
        /**
         * Get Booktics modules list
         *
         * @return array
         */
        function booktics_modules_list(): array {
            $modules_list = array(
                'stripe'           => array(
                    'name'          => 'stripe',
                    'slug'          => 'stripe',
                    'type'          => 'module',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'Stripe', 'booktics' ),
                    'description'   => __( 'It allows you to use stripe payment.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'stripe-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-stripe',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-stripe-in-booktics/',
                ),
                'woocommerce'      => array(
                    'name'          => 'woocommerce',
                    'slug'          => 'woocommerce',
                    'type'          => 'module',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'WooCommerce', 'booktics' ),
                    'description'   => __( 'Take booking payments through WooCommerce checkout.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'woocommerce-addon' ),
                    'notice'        => '',
                    'demo_link'     => '',
                    'settings_link' => '',
                    'doc_link'      => 'https://arraytics.com/docs/docs/booktics/integration/woocommerce-payment-in-booktics-setup-guide/',
                ),
                'fluentcrm'        => array(
                    'name'          => 'fluentcrm',
                    'slug'          => 'fluentcrm',
                    'type'          => 'integration',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'FluentCRM', 'booktics' ),
                    'description'   => __( 'It allows you to use FluentCRM.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-fluentcrm-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-fluentcrm',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-fluentcrm-with-booktics/',
                ),
                'pabbly'           => array(
                    'name'          => 'pabbly',
                    'slug'          => 'pabbly',
                    'type'          => 'integration',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'Pabbly', 'booktics' ),
                    'description'   => __( 'Send booking data to Pabbly Connect via webhook.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-pabbly-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-pabbly',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-pabbly/',
                ),
                'zapier'           => array(
                    'name'          => 'zapier',
                    'slug'          => 'zapier',
                    'type'          => 'integration',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'Zapier', 'booktics' ),
                    'description'   => __( 'Send booking data to Zapier via webhook.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-zapier-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-zapier',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-zapier/',
                ),
                'zoho'             => array(
                    'name'          => 'zoho',
                    'slug'          => 'zoho',
                    'type'          => 'integration',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'Zoho', 'booktics' ),
                    'description'   => __( 'Send booking data to Zoho via webhook.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-zoho-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-zoho',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-zoho-crm/',
                ),
                'mailpoet'         => array(
                    'name'          => 'mailpoet',
                    'slug'          => 'mailpoet',
                    'type'          => 'integration',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'plugin_slug'   => 'mailpoet',
                    'title'         => __( 'MailPoet', 'booktics' ),
                    'description'   => __( 'Add booking customers to your MailPoet lists.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-mailpoet-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-mailpoet',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-booktics-with-mailpoet/',
                ),
                'google-calendar'  => array(
                    'name'          => 'google-calendar',
                    'slug'          => 'google-calendar',
                    'type'          => 'module',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => false,
                    'title'         => __( 'Booktics Google Calendar', 'booktics' ),
                    'description'   => __( 'It allows you to use google calendar.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-google-calendar' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-google-calendar',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-google-calendar-with-booktics/',
                ),
                'outlook-calendar' => array(
                    'name'          => 'outlook-calendar',
                    'slug'          => 'outlook-calendar',
                    'type'          => 'module',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => true,
                    'title'         => __( 'Booktics Outlook Calendar', 'booktics' ),
                    'description'   => __( 'It allows you to use outlook calendar.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-outlook-calendar' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-outlook-calendar-addon',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/integration/how-to-integrate-outlook-calendar-with-booktics/',
                ),
                'paypal'           => array(
                    'name'          => 'paypal',
                    'slug'          => 'paypal',
                    'type'          => 'module',
                    'upgrade'       => true,
                    'upgrade_link'  => 'https://themewinter.com/purchase-history',
                    'status'        => 'off',
                    'is_pro'        => true,
                    'title'         => __( 'PayPal', 'booktics' ),
                    'description'   => __( 'It allows you to use PayPal.', 'booktics' ),
                    'icon'          => \Booktics\Extensions\Extension_Icon::get( 'booktics-paypal-addon' ),
                    'notice'        => '',
                    'demo_link'     => 'https://support.themewinter.com/docs/plugins/plugin-docs/integration/booktics-paypal-addon',
                    'settings_link' => '',
                    'doc_link'      => 'https://docs.arraytics.com/docs/booktics/payment-type/how-to-configure-the-paypal-in-booktics-pro/',
                ),
            );

            return $modules_list;
        }
    }
}

if ( ! function_exists( 'booktics_log' ) ) {
    /**
     * Write a debug line, but only when the site has debug logging switched on.
     *
     * @param string $message Message to log.
     *
     * @return void
     */
    function booktics_log( $message ) {
        if ( ! defined( 'WP_DEBUG_LOG' ) || ! WP_DEBUG_LOG ) {
            return;
        }

        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Guarded above; this is the plugin's only debug sink.
        error_log( 'Booktics: ' . $message );
    }
}

if ( ! function_exists( 'booktics_get_multisite_table_name' ) ) {
    /**
     * Get multisite-safe table name
     *
     * @param string $table_name
     *
     * @return string
     */
    function booktics_get_table_name( $table_name ) {
        global $wpdb;
            $prefix = $wpdb->prefix;
		if ( strpos( $table_name, $prefix ) === 0 ) {
			return $table_name;
		}
        return $wpdb->prefix . $table_name;
    }
}

if ( ! function_exists( 'booktics_handle_conflicting_plugins' ) ) {
    /**
     * Deactivate conflicting plugins if active and check if they exist.
     */
    function booktics_handle_conflicting_plugins() {
        // Avoid during AJAX or REST requests
        if ( wp_doing_ajax() || defined( 'REST_REQUEST' ) ) {
            return;
        }

        // Only proceed if user is logged in and can manage plugins
        if ( ! is_user_logged_in() || ! current_user_can( 'activate_plugins' ) ) {
            return;
        }

        // Prevent running repeatedly on every page load using a transient
        if ( get_transient( 'booktics_conflict_check_done' ) ) {
            return;
        }

        $conflicting_plugins = array(
            'booktics-google-calendar-addon/booktics-google-calendar.php',
            'booktics-outlook-calendar-addon/booktics-outlook-calendar-addon.php',
            'booktics-paypal-addon/booktics-paypal-addon.php',
            'booktics-stripe-addon/booktics_stripe.php',
            'booktics-fluentcrm-addon/booktics-fluentcrm-addon.php',
        );

        $active_plugins   = get_option( 'active_plugins', array() );
        $deactivated_list = array();

        foreach ( $conflicting_plugins as $plugin_file ) {
            $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;

            if ( file_exists( $plugin_path ) ) {
                if ( in_array( $plugin_file, $active_plugins, true ) ) {
                    deactivate_plugins( $plugin_file );
                    $plugin_data = get_plugin_data( $plugin_path );
                    $deactivated_list[] = $plugin_data['Name'];
                }
            }
        }

        if ( ! empty( $deactivated_list ) ) {
            // Store data in transient for 1 minute to show notice once
            set_transient( 'booktics_deactivated_plugins_notice', $deactivated_list, MINUTE_IN_SECONDS );
        }

        // Set the conflict check as done (even if nothing was deactivated)
        set_transient( 'booktics_conflict_check_done', true, MINUTE_IN_SECONDS );
    }
}

if ( ! function_exists( 'booktics_show_conflict_deactivation_notice' ) ) {
    /**
     * Show a one-time admin notice for deactivated conflicting plugins.
     */
    function booktics_show_conflict_deactivation_notice() {
        if ( ! current_user_can( 'activate_plugins' ) ) {
            return;
        }

        $deactivated_list = get_transient( 'booktics_deactivated_plugins_notice' );

        if ( ! empty( $deactivated_list ) ) {
            delete_transient( 'booktics_deactivated_plugins_notice' );

            $plugin_names = implode( ', ', array_map( 'esc_html', $deactivated_list ) );
            ?>
            <div class="notice notice-warning is-dismissible">
                <p>
                    <?php echo esc_html__( 'No need for addons! The following addon plugins have been added as modules in Booktics:', 'booktics' ); ?>
                    <strong><?php echo esc_html( $plugin_names ); ?></strong>
                    <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' ); ?>
                </p>
            </div>
            <?php
        }
    }
}

if ( ! function_exists( 'booktics_time_to_gmt' ) ) {
    /**
     * Convert time from site timezone to GMT/UTC
     *
     * @param string $time Time string (e.g., "9:00 AM", "09:00:00")
     * @param string|int $context_date Date context (Y-m-d or 0 for regular schedules)
     * @return string Time in H:i:s format (GMT/UTC)
     * @since 1.0.15
     */
    function booktics_time_to_gmt( string $time, $context_date = 0 ): string {
        // Skip '0' placeholder for holidays
        if ( $time === '0' || $time === 0 ) {
            return '0';
        }

        // For regular schedules (context_date = 0), use current date as context
        // For custom schedules, use the actual date for DST-aware conversion
        $date = ( $context_date === 0 || $context_date === '0' || empty( $context_date ) )
            ? gmdate( 'Y-m-d' )
            : $context_date;

        $timestamp = strtotime( $time );
        if ( $timestamp === false ) {
            return $time;
        }
        $formatted_time = gmdate( 'H:i:s', $timestamp );
        $datetime_string = $date . ' ' . $formatted_time;

        $gmt_datetime = get_gmt_from_date( $datetime_string );

        // Extract and return only the time part
        return gmdate( 'H:i:s', strtotime( $gmt_datetime ) );
    }
}

if ( ! function_exists( 'booktics_is_admin_or_team_member' ) ) {
    /**
     * Check if the current user is an admin or a Booktics team member.
     *
     * @return bool
     * @since 1.0.16
     */
    function booktics_is_admin_or_team_member() {
        if ( current_user_can( 'manage_options' ) ) {
            return true;
        }
        if ( is_user_logged_in() ) {
            $roles = (array) wp_get_current_user()->roles;
            if ( in_array( 'booktics_team_member', $roles, true ) ) {
                return true;
            }
        }
        return false;
    }
}

if ( ! function_exists( 'booktics_is_team_member' ) ) {
    function booktics_is_team_member() {
        if ( is_user_logged_in() ) {
            $roles = (array) wp_get_current_user()->roles;
            if ( in_array( 'booktics_team_member', $roles, true ) ) {
                return true;
            }
        }
        return false;
    }
}

if ( ! function_exists( 'booktics_time_from_gmt' ) ) {
    /**
     * Convert time from GMT/UTC to site timezone
     *
     * @param string $time Time string in UTC (e.g., "14:00:00")
     * @param string|int $context_date Date context (Y-m-d or 0 for regular schedules)
     * @return string Time in H:i:s format (site timezone)
     * @since 1.0.15
     */
    function booktics_time_from_gmt( string $time, $context_date = 0 ): string {
        // Skip '0' placeholder for holidays
        if ( $time === '0' || $time === 0 ) {
            return '0';
        }

        $date = ( $context_date === 0 || $context_date === '0' || empty( $context_date ) )
            ? gmdate( 'Y-m-d' )
            : $context_date;

        $timestamp = strtotime( $time );
        if ( $timestamp === false ) {
            return $time;
        }
        $formatted_time = gmdate( 'H:i:s', $timestamp );
        $gmt_datetime_string = $date . ' ' . $formatted_time;

        $local_datetime = get_date_from_gmt( $gmt_datetime_string );

        return gmdate( 'H:i:s', strtotime( $local_datetime ) );
    }
}

if ( ! function_exists( 'booktics_safe_maybe_unserialize' ) ) {
    /**
     * Safely unserialize a value without ever instantiating PHP objects.
     *
     * @param mixed $value Possibly serialized value.
     *
     * @return mixed Unserialized value, or the original value when not serialized.
     */
    function booktics_safe_maybe_unserialize( $value ) {
        if ( is_string( $value ) && is_serialized( $value ) ) {
            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- allowed_classes => false prevents PHP Object Injection.
            return @unserialize( trim( $value ), array( 'allowed_classes' => false ) );
        }

        return $value;
    }
}

if ( ! function_exists( 'booktics_sanitize_id_string' ) ) {
    /**
     * Sanitize a free-form identifier received from a request.
     *
     * Returns a plain-text string id (e.g. 'default' or a client-generated id such
     * as a uuid/nanoid). A legitimate identifier is never a PHP-serialized string,
     * so any value that looks serialized is dropped to '' — this neutralizes PHP
     * Object Injection payloads at the storage boundary while leaving every real id
     * untouched. Non-scalar input (arrays/objects) is rejected to ''.
     *
     * @param mixed $value Raw value from the request.
     *
     * @return string Sanitized identifier, or '' when missing, non-scalar, or serialized.
     */
    function booktics_sanitize_id_string( $value ) {
        if ( ! is_scalar( $value ) ) {
            return '';
        }

        $value = sanitize_text_field( (string) $value );

        return is_serialized( $value ) ? '' : $value;
    }
}

if ( ! function_exists( 'booktics_aisentic_identity' ) ) {
    /**
     * Build the site identity offered to Aisentic when the user opts in.
     *
     * The consent UI and the send path both read this helper, so the email shown
     * on the consent checkbox is always the email that actually leaves the site.
     *
     * @return array{name: string, email: string, site_url: string}
     */
    function booktics_aisentic_identity(): array {
        $name  = booktics_get_option( 'business_name', get_bloginfo( 'name' ) );
        $email = booktics_get_option( 'business_email', get_option( 'admin_email' ) );

        return array(
            'name'     => sanitize_text_field( (string) $name ),
            'email'    => sanitize_email( (string) $email ),
            'site_url' => esc_url_raw( site_url() ),
        );
    }
}

if ( ! function_exists( 'booktics_aisentic_is_registered' ) ) {
    /**
     * Whether Aisentic already holds an API key.
     *
     * Reading Aisentic's own storage instead of our consent option means anyone
     * who connected from Aisentic's screens is never nagged by our banner.
     *
     * @return bool True when an Aisentic API key is present.
     */
    function booktics_aisentic_is_registered(): bool {
        if ( function_exists( 'aisentic_get_option' ) ) {
            return ! empty( aisentic_get_option( 'llmProviders.aisentic.apiKey', '' ) );
        }

        $settings = get_option( 'aisentic_settings', array() );

        return ! empty( $settings['llmProviders']['aisentic']['apiKey'] );
    }
}

if ( ! function_exists( 'booktics_get_store_currency' ) ) {
    /**
     * The currency the merchant configured for the store, lowercased.
     *
     * Public checkout must always price in this currency. A currency sent by the
     * buyer cannot be trusted: the total is computed server side in the store
     * currency, so relabeling it as another currency changes what is charged.
     *
     * @return string Lowercase ISO 4217 currency code.
     */
    function booktics_get_store_currency(): string {
        $currency = strtolower( trim( (string) booktics_get_option( 'currency', 'USD' ) ) );

        // An empty or malformed setting would make every Stripe charge fail.
        return preg_match( '/^[a-z]{3}$/', $currency ) ? $currency : 'usd';
    }
}

if ( ! function_exists( 'booktics_amount_to_minor_units' ) ) {
    /**
     * Convert a decimal amount to the smallest unit of a currency.
     *
     * Stripe takes amounts in minor units, and not every currency has two
     * decimals: 100 JPY is sent as 100, 100 USD as 10000 and 100 KWD as 100000.
     * ISK and UGX are zero-decimal but Stripe still expects them as whole
     * amounts times 100, so 5 UGX is sent as 500.
     *
     * @see https://docs.stripe.com/currencies#minor-units
     *
     * @param float|int|string $amount   Amount in major units, e.g. 19.99.
     * @param string           $currency ISO 4217 currency code.
     *
     * @return int Amount in minor units.
     */
    function booktics_amount_to_minor_units( $amount, string $currency ): int {
        $zero_decimal  = array( 'bif', 'clp', 'djf', 'gnf', 'jpy', 'kmf', 'krw', 'mga', 'pyg', 'rwf', 'vnd', 'vuv', 'xaf', 'xof', 'xpf' );
        $whole_x100    = array( 'isk', 'ugx' );
        $three_decimal = array( 'bhd', 'jod', 'kwd', 'omr', 'tnd' );
        $currency      = strtolower( $currency );
        $amount        = (float) $amount;

        if ( in_array( $currency, $zero_decimal, true ) ) {
            return (int) round( $amount );
        }

        if ( in_array( $currency, $whole_x100, true ) ) {
            return (int) round( $amount ) * 100;
        }

        if ( in_array( $currency, $three_decimal, true ) ) {
            // Stripe requires the last digit of three-decimal amounts to be 0.
            return (int) ( round( $amount * 100 ) * 10 );
        }

        return (int) round( $amount * 100 );
    }
}

```
