2026-03-27 11:54:51 +00:00

1313 lines
38 KiB
PHP

<?php
/**
* Simply Schedule Appointments Utils.
*
* @since 0.0.3
* @package Simply_Schedule_Appointments
*/
use League\Period\Period;
/**
* Simply Schedule Appointments Utils.
*
* @since 0.0.3
*/
class SSA_Utils {
/**
* Parent plugin class.
*
* @since 0.0.3
*
* @var Simply_Schedule_Appointments
*/
protected $plugin = null;
protected $server_default_timezone_before_ssa;
/**
* Constructor.
*
* @since 0.0.3
*
* @param Simply_Schedule_Appointments $plugin Main plugin object.
*/
public function __construct( $plugin ) {
$this->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<SFr~wKz';
}
return hash_hmac('md5', $string, $salt);
}
public static function get_home_id() {
return self::site_unique_hash( get_home_url() );
}
public static function is_assoc_array( array $arr ) {
if ( array() === $arr ) return false;
return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
}
public static function arrays_assoc_are_equal( $a, $b ) {
if ( ! is_array( $a ) || ! is_array( $b ) ) {
return false;
}
if ( count( $a ) !== count( $b ) ) {
return false;
}
foreach ( $a as $key => $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;
}