plugin = $plugin; $this->hooks(); } /** * Initiate our hooks. * * @since 0.0.3 */ public function hooks() { } public function defensive_timezone_fix() { if ( 'UTC' === date_default_timezone_get() ) { return; } $this->server_default_timezone_before_ssa = date_default_timezone_get(); // We know that setting the default_timezone on a server is bad practice // WordPress expects it to be UTC to function properly // Our plugin also expects it to be UTC to function properly // Unfortunately we have found that some plugins do change the default timezone // We only call this function as a defensive measure, so SSA can co-exist with plugins // that set the timezone. Looking at you... // // * Ajax Event Calendar plugin [https://wordpress.org/support/plugin/ajax-event-calendar] // *** already removed from the wordpress.org repository // // * Series Engine plugin [https://seriesengine.com/] // ** Pro plugin not available on wordpress.org // // Here's our approach to addressing this issue: // We ONLY set the timezone to UTC if it's something different // // We feel that it should be forced to UTC to adhere to WordPress standards, // but that will probably break users' sites running these problematic plugins // // To try and play nicely with others and to protect the user, we will call our // defensive_timezone_fix() before SSA does anything where we rely on a UTC timezone // at the end of our functions, we will call defensive_timezone_reset() so we'll put it // back to whatever the server already had set. // // We see this as the only way to co-exist with these problematic plugins and simplify // life for the user. If there is a better approach, please get in touch. // We'd love to remove this code :) date_default_timezone_set( 'UTC' ); } public function defensive_timezone_reset() { if ( empty( $this->server_default_timezone_before_ssa ) || 'UTC' === $this->server_default_timezone_before_ssa ) { return; } // We know that setting the default_timezone on a server is bad practice // ^^^ See note above in defensive_timezone_fix() ^^^ date_default_timezone_set( $this->server_default_timezone_before_ssa ); } public static function site_unique_hash( $string ) { if (defined('SSA_AUTH_SALT')) { $salt = SSA_AUTH_SALT; } else if ( defined( 'AUTH_SALT' ) ) { $salt = AUTH_SALT; } else { $salt = 'M+mZDQYJlrHoRZ0OfE1ESGG5T5CgPMOgOub25eOmwJYdPLmiNLbKwXYGQfG0pkF5YCw45DVtwaWREx3Jr4hILB'; } return hash_hmac('md5', $string, $salt); } /** * * * @param [type] $string * @return void */ public static function deprecated_hash( $string ) { if ( defined( 'SSA_AUTH_SALT' ) ) { $salt = SSA_AUTH_SALT; } else { $salt = '6U2aRk6oGvAZAEXstbFNMppRF=D|H.NX!-gU:-aXGVH<)8kcF~FPor5{Z $value ) { if ( ! array_key_exists( $key, $b ) ) { return false; } if ( is_array( $value ) ) { if ( ! is_array( $b[ $key ] ) ) { return false; } if ( ! self::arrays_assoc_are_equal( $value, $b[ $key ] ) ) { return false; } } else { if ( $value !== $b[ $key ] ) { return false; } } } return true; } public static function array_key( $array, $key ) { if ( isset( $array[ $key ] ) ) { return $array[ $key ]; } return false; } public static function datetime( $time='now' ) { if ( $time instanceof DateTimeImmutable ) { return $time; } if ( 0 === strpos( $time, 'Invalid' ) ) { ssa_debug_log( 'SSA_Utils::datetime() `Invalid Date` detected' ); // $time = 'now'; return null; // TODO: handle error state gracefully } else if ( empty( $time ) ) { $time = 'now'; } $timezone = new DateTimeZone( 'UTC' ); return new DateTimeImmutable( $time, $timezone ); } public static function ceil_datetime( DateTimeImmutable $datetime, $mins = 5 ) { $seconds = $mins * 60; $time = ( ceil( $datetime->getTimestamp() / $seconds ) ) * $seconds; return $datetime->setTimestamp( $time ); } public static function floor_datetime( DateTimeImmutable $datetime, $mins = 5 ) { $seconds = $mins * 60; $time = ( floor( $datetime->getTimestamp() / $seconds ) ) * $seconds; return $datetime->setTimestamp( $time ); } /** * Create DateTimeZone safely, handling PHP 8.3+ deprecated timezone names. * * @since 5.8.8 * @param string $timezone_string Timezone identifier * @param DateTimeZone|null $fallback Optional fallback timezone if creation fails * @return DateTimeZone */ public static function safe_timezone( $timezone_string, $fallback = null ) { if ( $timezone_string instanceof DateTimeZone ) { return $timezone_string; } try { return new DateTimeZone( $timezone_string ); } catch ( Exception $e ) { // Map deprecated timezone names to modern equivalents (PHP 8.3+) // Based on IANA timezone database backward compatibility file $deprecated_timezones = array( // US timezones 'US/Alaska' => 'America/Anchorage', 'US/Aleutian' => 'America/Adak', 'US/Arizona' => 'America/Phoenix', 'US/Central' => 'America/Chicago', 'US/Eastern' => 'America/New_York', 'US/East-Indiana' => 'America/Indiana/Indianapolis', 'US/Hawaii' => 'Pacific/Honolulu', 'US/Indiana-Starke'=> 'America/Indiana/Knox', 'US/Michigan' => 'America/Detroit', 'US/Mountain' => 'America/Denver', 'US/Pacific' => 'America/Los_Angeles', 'US/Samoa' => 'Pacific/Pago_Pago', // Canada 'Canada/Atlantic' => 'America/Halifax', 'Canada/Central' => 'America/Winnipeg', 'Canada/Eastern' => 'America/Toronto', 'Canada/Mountain' => 'America/Edmonton', 'Canada/Newfoundland' => 'America/St_Johns', 'Canada/Pacific' => 'America/Vancouver', 'Canada/Saskatchewan' => 'America/Regina', 'Canada/Yukon' => 'America/Whitehorse', // Asia 'Asia/Calcutta' => 'Asia/Kolkata', 'Asia/Katmandu' => 'Asia/Kathmandu', 'Asia/Saigon' => 'Asia/Ho_Chi_Minh', 'Asia/Chongqing' => 'Asia/Shanghai', 'Asia/Chungking' => 'Asia/Shanghai', 'Asia/Dacca' => 'Asia/Dhaka', 'Asia/Harbin' => 'Asia/Shanghai', 'Asia/Kashgar' => 'Asia/Urumqi', 'Asia/Macao' => 'Asia/Macau', 'Asia/Tel_Aviv' => 'Asia/Jerusalem', 'Asia/Thimbu' => 'Asia/Thimphu', 'Asia/Ujung_Pandang' => 'Asia/Makassar', 'Asia/Ulan_Bator' => 'Asia/Ulaanbaatar', // Europe 'Europe/Belfast' => 'Europe/London', 'Europe/Tiraspol' => 'Europe/Chisinau', 'Europe/Nicosia' => 'Asia/Nicosia', // Africa 'Africa/Asmera' => 'Africa/Asmara', 'Africa/Timbuktu' => 'Africa/Bamako', // America 'America/Argentina/ComodRivadavia' => 'America/Argentina/Catamarca', 'America/Atka' => 'America/Adak', 'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires', 'America/Catamarca' => 'America/Argentina/Catamarca', 'America/Coral_Harbour' => 'America/Atikokan', 'America/Cordoba' => 'America/Argentina/Cordoba', 'America/Ensenada' => 'America/Tijuana', 'America/Fort_Wayne' => 'America/Indiana/Indianapolis', 'America/Indianapolis' => 'America/Indiana/Indianapolis', 'America/Jujuy' => 'America/Argentina/Jujuy', 'America/Knox_IN' => 'America/Indiana/Knox', 'America/Louisville' => 'America/Kentucky/Louisville', 'America/Mendoza' => 'America/Argentina/Mendoza', 'America/Porto_Acre' => 'America/Rio_Branco', 'America/Rosario' => 'America/Argentina/Cordoba', 'America/Virgin' => 'America/St_Thomas', // Australia 'Australia/ACT' => 'Australia/Sydney', 'Australia/Canberra' => 'Australia/Sydney', 'Australia/LHI' => 'Australia/Lord_Howe', 'Australia/NSW' => 'Australia/Sydney', 'Australia/North' => 'Australia/Darwin', 'Australia/Queensland' => 'Australia/Brisbane', 'Australia/South' => 'Australia/Adelaide', 'Australia/Tasmania' => 'Australia/Hobart', 'Australia/Victoria' => 'Australia/Melbourne', 'Australia/West' => 'Australia/Perth', 'Australia/Yancowinna' => 'Australia/Broken_Hill', // Brazil 'Brazil/Acre' => 'America/Rio_Branco', 'Brazil/DeNoronha' => 'America/Noronha', 'Brazil/East' => 'America/Sao_Paulo', 'Brazil/West' => 'America/Manaus', // Chile 'Chile/Continental' => 'America/Santiago', 'Chile/EasterIsland' => 'Pacific/Easter', // Pacific 'Pacific/Ponape' => 'Pacific/Pohnpei', 'Pacific/Samoa' => 'Pacific/Pago_Pago', 'Pacific/Truk' => 'Pacific/Chuuk', 'Pacific/Yap' => 'Pacific/Chuuk', // Country shortcuts 'Egypt' => 'Africa/Cairo', 'Eire' => 'Europe/Dublin', 'GB' => 'Europe/London', 'GB-Eire' => 'Europe/London', 'Greenwich' => 'Etc/GMT', 'Hongkong' => 'Asia/Hong_Kong', 'Iceland' => 'Atlantic/Reykjavik', 'Iran' => 'Asia/Tehran', 'Israel' => 'Asia/Jerusalem', 'Jamaica' => 'America/Jamaica', 'Japan' => 'Asia/Tokyo', 'Kwajalein' => 'Pacific/Kwajalein', 'Libya' => 'Africa/Tripoli', 'NZ' => 'Pacific/Auckland', 'NZ-CHAT' => 'Pacific/Chatham', 'Navajo' => 'America/Denver', 'PRC' => 'Asia/Shanghai', 'Poland' => 'Europe/Warsaw', 'Portugal' => 'Europe/Lisbon', 'ROC' => 'Asia/Taipei', 'ROK' => 'Asia/Seoul', 'Singapore' => 'Asia/Singapore', 'Turkey' => 'Europe/Istanbul', 'UCT' => 'Etc/UTC', 'Universal' => 'Etc/UTC', 'W-SU' => 'Europe/Moscow', 'Zulu' => 'Etc/UTC', // Mexico 'Mexico/BajaNorte' => 'America/Tijuana', 'Mexico/BajaSur' => 'America/Mazatlan', 'Mexico/General' => 'America/Mexico_City', ); if ( isset( $deprecated_timezones[ $timezone_string ] ) ) { try { return new DateTimeZone( $deprecated_timezones[ $timezone_string ] ); } catch ( Exception $e2 ) { // Mapped timezone also failed - this shouldn't happen with valid mappings error_log( sprintf( 'SSA: Deprecated timezone "%s" mapped to "%s" but mapping also failed: %s', $timezone_string, $deprecated_timezones[ $timezone_string ], $e2->getMessage() ) ); // Now Fall through to fallback logic } } // Return fallback or UTC as last resort if ( $fallback instanceof DateTimeZone ) { return $fallback; } return new DateTimeZone( 'UTC' ); } } public static function get_datetime_in_utc( $datetime, $datetimezone='UTC' ) { if ( ! ( $datetimezone instanceof DateTimeZone ) ) { $datetimezone = self::safe_timezone( $datetimezone ); } if ( ! ( $datetime instanceof DateTimeImmutable ) ) { $datetime = new DateTimeImmutable( $datetime, $datetimezone ); } $datetime = $datetime->setTimezone( new DateTimeZone( 'UTC' ) ); return $datetime; } public static function get_period_in_utc( Period $period ) { return self::get_period_in_timezone( $period, new DateTimeZone( 'UTC' ) ); } public static function get_period_in_timezone( Period $period, DateTimeZone $timezone ) { return new Period( $period->getStartDate()->setTimezone( $timezone ), $period->getEndDate()->setTimezone( $timezone ) ); } public function get_datetime_as_local_datetime( $datetime, $appointment_type_id=0, $staff_id = 0, $location_id = 0 ) { $local_timezone = $this->get_datetimezone( $appointment_type_id, $staff_id, $location_id ); if ( empty( $local_timezone ) || ! ( $local_timezone instanceof DateTimeZone ) ) { throw new SSA_Exception( __( 'No $local_timezone defined' ), 500 ); } if ( $datetime instanceof DateTime ) { $datetime = new DateTimeImmutable( $datetime ); } elseif ( ! ( $datetime instanceof DateTimeImmutable ) ) { $utc_timezone = new DateTimeZone( 'UTC' ); $datetime = new DateTimeImmutable( $datetime, $utc_timezone ); $datetime = $datetime->setTimezone( $local_timezone ); } else { $datetime = $datetime->setTimezone( $local_timezone ); } return $datetime; } public function get_datetimezone( $appointment_type_id = 0, $staff_id = 0, $location_id = 0 ) { if ( !empty( $staff_id ) ) { throw new SSA_Exception( __( 'Staff members are not supported yet' ), 500 ); } elseif ( !empty( $location_id ) ) { throw new SSA_Exception( __( 'Locations are not supported yet' ), 500 ); } else { $local_timezone = $this->plugin->settings_global->get_datetimezone(); } return $local_timezone; } /** * Returns datetime format set by the customer on the global settings page. * * @since 4.9.1 * * @return string */ public static function get_localized_date_format_from_settings() { $settings = ssa()->settings->get(); $format = $settings['global']['date_format'] . ' ' . $settings['global']['time_format']; return $format; } /** * Returns date format set by the customer on the global settings page. * * @since 4.9.1 * * @return string */ public static function get_localized_date_only_format_from_settings() { $settings = ssa()->settings->get(); $format = $settings['global']['date_format']; return $format; } /** * Returns time format set by the customer on the global settings page. * * @since 4.9.1 * * @return string */ public static function get_localized_time_only_format_from_settings() { $settings = ssa()->settings->get(); $format = $settings['global']['time_format']; return $format; } public static function localize_default_date_strings( $php_date_format ) { if ( 'F j, Y' === $php_date_format ) { $php_date_format = __( 'F j, Y', 'default' ); } else if ( 'g:i a' === $php_date_format ) { $php_date_format = __( 'g:i a', 'default' ); } else if ( 'F j, Y g:i a' === $php_date_format ) { $php_date_format = __( 'F j, Y g:i a', 'default' ); } return $php_date_format; } public static function translate_formatted_date( $formatted_date ) { $translations = array(); if( !empty( ssa()->translation->programmatic_locale ) && is_string( ssa()->translation->programmatic_locale ) ){ $mo_file = WP_LANG_DIR . '/' . ssa()->translation->programmatic_locale . '.mo'; if( ! file_exists( $mo_file ) ){ // if the WP core language file doesn't exist, try the plugin language file $mo_file = WP_LANG_DIR . '/plugins/simply-schedule-appointments-' . ssa()->translation->programmatic_locale . '.mo'; } if ( file_exists( $mo_file ) ) { // Make sure the MO class is available. if ( ! class_exists( 'MO' ) ) { require_once( ABSPATH . WPINC . '/l10n.php' ); } // Instantiate a new MO object. $mo = new MO(); // Load your specific .mo file. // (Replace '/path/to/your/file.mo' with the correct path.) if ( $mo->import_from_file( $mo_file ) ) { // Now translate the strings using the loaded .mo file. $translations = array( 'January' => $mo->translate( 'January' ), 'February' => $mo->translate( 'February' ), 'March' => $mo->translate( 'March' ), 'April' => $mo->translate( 'April' ), 'May' => $mo->translate( 'May' ), 'June' => $mo->translate( 'June' ), 'July' => $mo->translate( 'July' ), 'August' => $mo->translate( 'August' ), 'September' => $mo->translate( 'September' ), 'October' => $mo->translate( 'October' ), 'November' => $mo->translate( 'November' ), 'December' => $mo->translate( 'December' ), 'Monday' => $mo->translate( 'Monday' ), 'Tuesday' => $mo->translate( 'Tuesday' ), 'Wednesday' => $mo->translate( 'Wednesday' ), 'Thursday' => $mo->translate( 'Thursday' ), 'Friday' => $mo->translate( 'Friday' ), 'Saturday' => $mo->translate( 'Saturday' ), 'Sunday' => $mo->translate( 'Sunday' ), ); } } } if(empty($translations)){ $translations = array( 'January' => __( 'January' ), 'February' => __( 'February' ), 'March' => __( 'March' ), 'April' => __( 'April' ), 'May' => __( 'May' ), 'June' => __( 'June' ), 'July' => __( 'July' ), 'August' => __( 'August' ), 'September' => __( 'September' ), 'October' => __( 'October' ), 'November' => __( 'November' ), 'December' => __( 'December' ), 'Monday' => __( 'Monday' ), 'Tuesday' => __( 'Tuesday' ), 'Wednesday' => __( 'Wednesday' ), 'Thursday' => __( 'Thursday' ), 'Friday' => __( 'Friday' ), 'Saturday' => __( 'Saturday' ), 'Sunday' => __( 'Sunday' ), ); } return str_replace( array_keys( $translations ), array_values( $translations ), $formatted_date ); // return strtr( ( string ) $formatted_date, $translations ); } public static function php_to_moment_format($php_date_format) { $php_date_format = self::localize_default_date_strings( $php_date_format ); $replacements = array( '\\h' => '[h]', '\\m\\i\\n' => '[min]', '\\m' => '[m]', '\\' => '', '\\d' => '\\d', // Leave Spanish string 'de' (of) untouched '\\e' => '\\e', // Leave Spanish string 'de' (of) untouched 'd' => 'DD', 'D' => 'ddd', 'j' => 'D', 'l' => 'dddd', 'N' => 'E', 'S' => 'o', 'w' => 'e', 'z' => 'DDD', 'W' => 'W', 'F' => 'MMMM', 'm' => 'MM', 'M' => 'MMM', 'n' => 'M', 't' => '', // no equivalent 'L' => '', // no equivalent 'o' => 'YYYY', 'Y' => 'YYYY', 'y' => 'YY', 'a' => 'a', 'A' => 'A', 'B' => '', // no equivalent 'g' => 'h', 'G' => 'H', 'h' => 'hh', 'H' => 'HH', 'i' => 'mm', 's' => 'ss', 'u' => 'SSS', 'e' => 'zz', // deprecated since version 1.6.0 of moment.js 'I' => '', // no equivalent 'O' => '', // no equivalent 'P' => '', // no equivalent 'T' => '', // no equivalent 'Z' => '', // no equivalent 'c' => '', // no equivalent 'r' => '', // no equivalent 'U' => 'X', ); $moment_format = strtr($php_date_format, $replacements); if( isset( $_GET["ssa_locale"] )){ $moment_format = apply_filters( 'ssa/moment_format', $moment_format, $_GET["ssa_locale"] ); } return $moment_format; } public static function moment_to_php_format($moment_date_format) { $replacements = array( 'DD' => 'd', 'ddd' => 'D', 'D' => 'j', 'dddd' => 'l', 'E' => 'N', 'o' => 'S', 'e' => 'w', 'DDD' => 'z', 'W' => 'W', 'MMMM' => 'F', 'MM' => 'm', 'MMM' => 'M', 'M' => 'n', 'YYYY' => 'o', 'YYYY' => 'Y', 'YY' => 'y', 'a' => 'a', 'A' => 'A', 'h' => 'g', 'H' => 'G', 'hh' => 'h', 'HH' => 'H', 'mm' => 'i', 'ss' => 's', 'SSS' => 'u', 'zz' => 'e', // deprecated since version 1.6.0 of moment.js 'X' => 'U', ); $php_format = strtr($moment_date_format, $replacements); return $php_format; } public function get_formatted_date( $date, $appointment_type_id = 0 ){ $local_date = $this->get_datetime_as_local_datetime( $date, $appointment_type_id ); $format = self::get_localized_date_format_from_settings(); $format = self::localize_default_date_strings( $format ) . ' (T)'; $value = $local_date->format( $format ); $value = self::translate_formatted_date( $value ); return $value; } /** * Define constant if not already set. * * @param string $name Constant name. * @param string|bool $value Constant value. */ public static function define( $name, $value ) { if ( ! defined( $name ) ) { define( $name, $value ); } } public static function get_appointment_type( $appointment_type ) { if ( (int)$appointment_type == $appointment_type ) { $appointment_type = ssa()->appointment_type_model->get( $appointment_type ); } if ( empty( $appointment_type['id'] ) ) { return false; } return $appointment_type; } public static function get_query_period( Period $query_period = null ) { if ( null === $query_period ) { $query_period = SSA_Constants::EPOCH_PERIOD(); } return $query_period; } } function ssa_datetime( $time='now' ) { return SSA_Utils::datetime( $time ); } function ssa_gmtstrtotime( $string ) { $time = strtotime($string . ' +0000'); if ( -1 == $time ) { return strtotime($string); } return $time; } function ssa_defensive_timezone_fix() { ssa()->utils->defensive_timezone_fix(); } function ssa_defensive_timezone_reset() { ssa()->utils->defensive_timezone_reset(); } function ssa_debug_log( $var, $debug_level = 1, $label = '', $file = 'debug' ) { if ( defined( 'SSA_DEBUG_LOG' ) && empty( SSA_DEBUG_LOG ) ) { return; } // Store backtrace early for PHP 7.0+ compatibility (before using parameters) $backtrace = debug_backtrace(); $ssa_debug_level = get_option( 'ssa_debug_level', 10 ); if ( $debug_level < $ssa_debug_level ) { // We want to log really fatal errors (level 10) so the support team can see them when logging in after the fact return; } $path = ssa()->support_status->get_log_file_path( $file ); $log_prefix = gmdate( '[Y-m-d H:i:s] ' ); if ( empty( $path ) ) { return; } // Validate indices before access (PHP 8.0+ compatibility) if ( isset( $backtrace[1]['function'] ) ) { error_log( PHP_EOL . 'The following is logged from ' . $backtrace[1]['function'] . '()' . PHP_EOL, 3, $path ); } if ( isset( $backtrace[0]['file'], $backtrace[0]['line'] ) ) { error_log( 'in ' . $backtrace[0]['file'] . ' line ' . $backtrace[0]['line'] . ':' . PHP_EOL, 3, $path ); } if ( isset( $backtrace[2]['function'] ) ) { error_log( PHP_EOL . '... ' . $backtrace[2]['function'] . '()' . PHP_EOL, 3, $path ); } if ( isset( $backtrace[1]['file'], $backtrace[1]['line'] ) ) { error_log( 'in ' . $backtrace[1]['file'] . ' line ' . $backtrace[1]['line'] . ':' . PHP_EOL, 3, $path ); } if ( isset( $backtrace[3]['function'] ) ) { error_log( PHP_EOL . '...... ' . $backtrace[3]['function'] . '()' . PHP_EOL, 3, $path ); } if ( isset( $backtrace[2]['file'], $backtrace[2]['line'] ) ) { error_log( 'in ' . $backtrace[2]['file'] . ' line ' . $backtrace[2]['line'] . ':' . PHP_EOL, 3, $path ); } if ( ! empty( $label ) ) { error_log( $log_prefix.$label.PHP_EOL, 3, $path ); } if ( is_string( $var ) ) { error_log( $log_prefix.$var.PHP_EOL, 3, $path ); } else { // error_log( $log_prefix.print_r( $var, true ).PHP_EOL, 3, $path ); } } function ssa_int_hash( $string ) { return abs( crc32( $string ) ); } function ssa_get_staff_id( $user_id ) { return ssa()->staff_model->get_staff_id_for_user_id( $user_id ); } function ssa_get_current_staff_id() { return ssa_get_staff_id( get_current_user_id() ); } function ssa_cache_get( $key, $group = '', $force = false, &$found = null ) { return SSA_Cache::object_cache_get( $key, $group, $force, $found ); } function ssa_cache_set( $key, $data, $group = '', $expire = 0 ) { return SSA_Cache::object_cache_set( $key, $data, $group, $expire ); } function ssa_cache_delete( $key, $group = '' ) { return SSA_Cache::object_cache_delete( $key, $group ); } /** * SSA custom class_exists function * * @since 6.0.3 * * @param string $class * @return bool */ function ssa_class_exists( $class = '' ) { if ( ! class_exists( $class ) ) { $var = 'Class "' . $class . '" not found!'; $debug_level = 10; ssa_debug_log($var, $debug_level ); return false; } return true; } /** * SSA custom function_exists function * * @since 6.0.3 * * @param string $function * @return bool */ function ssa_function_exists( $function = '' ) { if ( ! function_exists( $function ) ) { $var = 'Function "' . $function . '" not found!'; $debug_level = 10; ssa_debug_log($var, $debug_level ); return false; } return true; } /** * SSA custom as_has_scheduled_action that does all the checking needed * * @since 6.0.3 * * @param string $hook Required * @return bool|null */ function ssa_has_scheduled_action( $hook = '', $args = array() ) { if ( empty( $hook ) ) { return; } if ( ! ssa_class_exists( 'ActionScheduler' ) ) { return; } if ( ! ssa_function_exists( 'as_has_scheduled_action' ) ) { return; } try { return as_has_scheduled_action( $hook, $args ); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * SSA custom as_schedule_recurring_action that does all the checking needed * * @since 6.0.3 * * @param integer $timestamp Required * @param integer $interval_in_seconds Required * @param string $hook Required * @param array $args Optional * @param string $group Optional * @return integer|null The action ID or void */ function ssa_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook='', $args = array(), $group = '' ) { if ( empty( $timestamp ) || empty( $interval_in_seconds ) || empty( $hook ) ) { return; } if ( ! ssa_class_exists( 'ActionScheduler' ) ) { return; } if ( ! ssa_function_exists( 'as_schedule_recurring_action' ) ) { return; } try { // return the ID of the scheduled action return as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args, $group ); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * SSA custom as_unschedule_action that does all the checking needed * * @since 6.0.3 * * @param string $hook Required * @param array $args Optional * @param string $group Optional * @return integer|null. The scheduled action ID if a scheduled action was found, or null if no matching action found. */ function ssa_unschedule_action( $hook='', $args = array(), $group = '' ) { if ( empty( $hook ) ) { return; } if ( ! ssa_class_exists( 'ActionScheduler' ) ) { return; } if ( ! ssa_function_exists( 'as_unschedule_action' ) ) { return; } try { return as_unschedule_action( $hook, $args, $group ); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * SSA custom as_unschedule_all_actions that does all the checking needed * * @since 6.0.3 * * @param string $hook Required * @param array $args Optional * @param string $group Optional */ function ssa_unschedule_all_actions( $hook='', $args = array(), $group = '' ) { if ( empty( $hook ) ) { return; } if ( ! ssa_class_exists( 'ActionScheduler' ) ) { return; } if ( ! ssa_function_exists( 'as_unschedule_all_actions' ) ) { return; } try { return as_unschedule_all_actions( $hook, $args, $group ); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * SSA custom as_schedule_single_action that does all the checking needed * * @since 6.0.3 * * @param integer $timestamp Required * @param string $hook Required * @param array $args Optional * @param string $group Optional * @return integer|null. The scheduled action ID if a scheduled action was found, or null if no matching action found. */ function ssa_schedule_single_action( $timestamp, $hook='', $args = array(), $group = '' ) { if ( empty( $timestamp ) || empty( $hook ) ) { return; } if ( ! ssa_class_exists( 'ActionScheduler' ) ) { return; } if ( ! ssa_function_exists( 'as_schedule_single_action' ) ) { return; } try { return as_schedule_single_action( $timestamp, $hook, $args, $group ); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * SSA queue runner for wp_actionscheduler_actions table * * @since 6.4.3 * * @return integer */ function ssa_run_action_scheduler_queue(){ if ( ! class_exists( 'ActionScheduler' ) ) { return; } try { return ActionScheduler::runner()->run(); } catch( \Exception $e ) { $var = $e->getMessage(); $debug_level = 10; $label = 'Action Scheduler'; ssa_debug_log( $var, $debug_level, $label ); } } /** * Get all active plugins * Return array of active plugins names, eg. array( 'draw-attention', 'simply-schedule-appointments', 'wp-mail-logging' ); * * @return array */ function ssa_get_all_active_plugins(){ $active_plugins = get_option( 'active_plugins' ); $output = array(); foreach ($active_plugins as $active_plugin ) { if ( strpos( $active_plugin, '/' ) ) { $active_plugin = substr( $active_plugin, 0, strpos( $active_plugin, '/' ) ); } $output[] = $active_plugin; } return $output; } function ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $date_timezone ){ $modifier_string = explode( ':', $modifier, 3); // by default format as day of the week $formatted_value = ssa_datetime( $start_date )->setTimezone( $date_timezone )->format( 'l' ); if( $modifier_string[1] === 'format' ){ // if a format string is included, format accordingly $date_format = str_replace( 'y', 'Y', $modifier_string[2] ); $date_format = str_replace( 'h', 'H', $date_format ); $formatted_value = ssa_datetime( $start_date )->setTimezone( $date_timezone )->format( $date_format ); } $formatted_value = SSA_Utils::translate_formatted_date( $formatted_value ); return $formatted_value; } function ssa_evaluate_merge_tag ( $appointment_id, $modifier ) { try { $appointment_obj = new SSA_Appointment_Object( $appointment_id ); $start_date = $appointment_obj->start_date; } catch ( Exception $e ) { // failed to get appointment from appointment_id return ''; } // get settings $settings = ssa()->settings->get(); if ( 'business_start_date' === explode( ':', $modifier )[0] ) { $date_timezone = SSA_Utils::safe_timezone( $settings['global']['timezone_string'] ); return ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $date_timezone ); } if ( 'customer_start_date' === explode( ':', $modifier )[0] && count( explode( ':', $modifier ) ) > 1 ) { return ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $appointment_obj->get_customer_timezone() ); } if ( 'instructions' === $modifier ) { return $appointment_obj->get_appointment_type()->instructions; } if ( 'public_edit_url' === $modifier ) { return $appointment_obj->get_public_edit_url(); } if ( 'appointment_type_slug' === $modifier ) { $appointment_type = $appointment_obj->get_appointment_type(); return $appointment_type->get_slug(); } if ( 'appointment_type_title' === $modifier ) { $appointment_type = $appointment_obj->get_appointment_type(); return $appointment_type->get_title(); } if ( 'web_meeting_url' === $modifier ) { return $appointment_obj->__get( 'web_meeting_url' ); } if ( 'add_to_calendar_link_ics' === $modifier ) { $token = ssa()->appointment_model->get_id_token( $appointment_id ); return add_query_arg( array( 'token' => $token, ), $appointment_obj->get_ics_download_url( 'customer' ) ); } if ( 'add_to_calendar_link_gcal' === $modifier ) { return $appointment_obj->get_gcal_add_link( 'customer' ); } if ( 'team_member_name' === $modifier ) { $members = $appointment_obj->get_staff_members(); if ( empty( $members ) ) { return ''; } // loop through members and get a list of names. $names = array(); foreach ( $members as $member ) { $names[] = $member->get_name(); } if ( count( $names ) > 1 ) { $last = array_pop( $names ); $text = implode( ', ', $names ); $text .= ' and ' . $last; return $text; } else { return $names[0]; } } if ( 'team_member_email' === $modifier ) { $members = $appointment_obj->get_staff_members(); if ( empty( $members ) ) { return ''; } // loop through members and get a list of emails. $emails = array(); foreach ( $members as $member ) { $emails[] = $member->get_email(); } if ( count( $emails ) > 1 ) { $last = array_pop( $emails ); $text = implode( ', ', $emails ); $text .= ' and ' . $last; return $text; } else { return $emails[0]; } } $ssa = ssa(); if ( 'start_date_only' === $modifier || 'start_time_only' === $modifier ) { $business_start_date = ssa_datetime( $start_date ); if ( 'start_date_only' === $modifier ) { $format = SSA_Utils::get_localized_date_only_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ); } else { $format = SSA_Utils::get_localized_time_only_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)'; } $formatted_value = $ssa->utils->get_datetime_as_local_datetime( $business_start_date, $appointment_id )->format( $format ); $formatted_value = SSA_Utils::translate_formatted_date( $formatted_value ); return $formatted_value; } if ( 'customer_start_date' === $modifier || 'customer_start_date_only' === $modifier || 'customer_start_time_only' === $modifier ) { if ( 'customer_start_date' === $modifier ) { $format = SSA_Utils::get_localized_date_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)'; } elseif ( 'customer_start_date_only' === $modifier ) { $format = SSA_Utils::get_localized_date_only_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ); } elseif ( 'customer_start_time_only' === $modifier ) { $format = SSA_Utils::get_localized_time_only_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)'; } $formatted_value = ssa_datetime( $start_date )->setTimezone( $appointment_obj->get_customer_timezone() )->format( $format ); $formatted_value = SSA_Utils::translate_formatted_date( $formatted_value ); return $formatted_value; } $local_start_date = $ssa->utils->get_datetime_as_local_datetime( $start_date ); $format = SSA_Utils::get_localized_date_format_from_settings(); $format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)'; $formatted_value = $local_start_date->format( $format ); $formatted_value = SSA_Utils::translate_formatted_date( $formatted_value ); return $formatted_value; } function ssa_get_stack_trace() { ob_start(); debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $trace = ob_get_contents(); ob_end_clean(); return $trace; } function ssa_should_render_booking_flow() { return ssa()->settings_installed->is_installed( 'booking_flows' ) ? 1 : 0; } /** * Unles the notification is sent to a customer, it's categorized as a staff notification * * @param array $recipients * @return string */ function ssa_get_recipient_type_for_recipients_array ( array $recipients ) { $recipients_string = implode( ',', $recipients ); if ( strpos( $recipients_string, 'customer_email' ) === false && strpos( $recipients_string, 'customer_phone' ) === false ){ return 'staff'; } return 'customer'; } /** * Build current page URL without query parameters * * @return string */ function ssa_get_current_page_url() { global $wp; return home_url($wp->request); } /** * Sanitizes the accent color input. * * @param string $color The input color string. * @return string|null The sanitized color or null if the input is invalid. */ function ssa_sanitize_color_input($color) { $hex_pattern = '/^[a-fA-F0-9]+$/'; $rgba_pattern = '/^rgba\(\d{1,3},\d{1,3},\d{1,3},(?:0|1|0?\.\d+)\)$/i'; if (preg_match($hex_pattern, $color) && in_array(strlen($color), [6, 3])) { return strtolower($color); } if (preg_match($rgba_pattern, $color)) { return $color; } return null; } // sometimes the request hits the php code even though it's a file request function ssa_is_json_request() { // wp_is_json_request checks for the headers - not helpful here // so we check if /wp-json/ is in the request URI return strpos( $_SERVER['REQUEST_URI'], '/wp-json/' ) !== false && strpos( $_SERVER['REQUEST_URI'], '/block-renderer/ssa') === false; } function ssa_is_file_request() { $request_uri = $_SERVER['REQUEST_URI']; $file_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico', 'css', 'js', 'woff', 'woff2', 'ttf', 'eot', 'otf', 'svg', 'webp', 'map']; $path_info = pathinfo($request_uri); if ( isset( $path_info['extension'] ) && in_array( strtolower( $path_info['extension'] ), $file_extensions ) ) { return true; } return false; } function ssa_should_skip_async_logic() { $should_skip = true; if( ssa_doing_async() ) { $should_skip = false; } // wp cron if ( wp_doing_cron() ) { $should_skip = false; } return $should_skip; }