Contributor - Ve Bailovity (Incsub)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License (Version 2 - GPLv2) as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
if ( ! class_exists( 'Appointments' ) ) {
class Appointments {
public $version = '2.2.4';
public $db_version;
public $timetables = array();
public $local_time;
/** @var bool|Appointments_Google_Calendar */
public $gcal_api = false;
public $locale_error;
public $time_format;
public $datetime_format;
public $log_file;
public $salt;
public $worker;
public $location;
public $service;
public $openid;
public $plugin_url;
/** @var Appointments_Admin */
public $admin;
/** @var Appointments_Addons_Loader */
public $addons_loader;
/** @var Appointments_Notifications_Manager */
public $notifications;
public $pro = false;
public $shortcodes = array();
function __construct() {
include_once( 'includes/helpers.php' );
include_once( 'includes/helpers-settings.php' );
include_once( 'includes/helpers-timetables.php' );
include_once( 'includes/deprecated-hooks.php' );
include_once( 'includes/class-app-notifications-manager.php' );
include_once( 'includes/class-app-api-logins.php' );
include_once( 'includes/class-app-sessions.php' );
// Load premium features
if ( _appointments_is_pro() ) {
include_once( appointments_plugin_dir() . 'includes/pro/class-app-pro.php' );
$this->pro = new Appointments_Pro();
}
$this->timetables = get_transient( 'app_timetables' );
if ( ! $this->timetables || ! is_array( $this->timetables ) ) {
$this->timetables = array();
}
$this->plugin_url = plugins_url( basename( dirname( __FILE__ ) ) );
// Read all options at once
$this->options = get_option( 'appointments_options' );
// To follow WP Start of week, time, date settings
$this->local_time = current_time( 'timestamp' );
$this->start_of_week = appointments_week_start() - 1;
$this->time_format = appointments_get_date_format( 'time' );
$this->date_format = appointments_get_date_format( 'date' );
$this->datetime_format = appointments_get_date_format( 'full' );
add_action( 'delete_user', 'appointments_delete_worker' ); // Modify database in case a user is deleted
add_action( 'wpmu_delete_user', 'appointments_delete_worker' ); // Same as above
add_action( 'remove_user_from_blog', array( &$this, 'remove_user_from_blog' ), 10, 2 ); // Remove his records only for that blog
add_action( 'plugins_loaded', array( &$this, 'localization' ) ); // Localize the plugin
add_action( 'init', array( &$this, 'init' ), 20 ); // Initial stuff
add_filter( 'the_posts', array( &$this, 'load_styles' ) ); // Determine if we use shortcodes on the page
add_action( 'admin_init', array( $this, 'maybe_upgrade' ) );
include_once( 'includes/class-app-service.php' );
include_once( 'includes/class-app-worker.php' );
include_once( 'includes/class-app-appointment.php' );
include_once( 'includes/class-app-transaction.php' );
if ( is_admin() ) {
$this->load_admin();
}
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
include_once( 'includes/class-app-ajax.php' );
new Appointments_AJAX();
}
// Check for cookies
if ( ! empty( $this->options['login_required'] ) && 'yes' === $this->options['login_required'] ) {
// If we require a login and we had an user logged in,
// we don't need cookies after they log out
add_action( 'wp_logout', array( $this, 'drop_cookies_on_logout' ) );
}
// Widgets
require_once( appointments_plugin_dir() . 'includes/widgets.php' );
add_action( 'widgets_init', array( &$this, 'widgets_init' ) );
// Integration with other plugins/Themes
include_once( appointments_plugin_dir() . 'includes/integration/integration.php' );
$this->pages_to_be_cached = array();
$this->had_filter = false; // There can be a wpautop filter. We will check this later on.
add_action( 'init', array( $this, 'get_gcal_api' ), 10 );
// Database variables
global $wpdb;
$this->db = &$wpdb;
$this->services_table = $wpdb->prefix . 'app_services';
$this->transaction_table = $wpdb->prefix . 'app_transactions';
$this->cache_table = $wpdb->prefix . 'app_cache';
// DB version
$this->db_version = get_option( 'app_db_version' );
// Set meta tables
$wpdb->app_appointmentmeta = appointments_get_table( 'appmeta' );
// Set log file location
$uploads = wp_upload_dir();
if ( isset( $uploads['basedir'] ) ) {
$this->uploads_dir = $uploads['basedir'] . '/'; } else { $this->uploads_dir = WP_CONTENT_DIR . '/uploads/'; }
$this->log_file = $this->uploads_dir . 'appointments-log.txt';
// Other default settings
$this->script = $this->uri = $this->error_url = '';
$this->location = $this->service = $this->worker = 0;
$this->gcal_image = '
';
$this->locale_errlocale_error = false;
// Create a salt, if it doesn't exist from the previous installation
if ( ! $salt = get_option( 'appointments_salt' ) ) {
$salt = mt_rand();
add_option( 'appointments_salt', $salt ); // Save it to be used until it is cleared manually
}
$this->salt = $salt;
// Deal with zero-priced appointments auto-confirm
if ( isset( $this->options['payment_required'] ) && 'yes' == $this->options['payment_required'] && ! empty( $this->options['allow_free_autoconfirm'] ) ) {
if ( ! defined( 'APP_CONFIRMATION_ALLOW_FREE_AUTOCONFIRM' ) ) { define( 'APP_CONFIRMATION_ALLOW_FREE_AUTOCONFIRM', true ); }
}
$this->notifications = new Appointments_Notifications_Manager();
}
public function load_admin() {
include_once( 'admin/class-app-admin.php' );
$this->admin = new Appointments_Admin();
}
function maybe_upgrade() {
if ( isset( $_GET['app-clear'] ) && current_user_can( 'manage_options' ) ) {
appointments_clear_cache();
}
$db_version = get_option( 'app_db_version' );
if ( $db_version == $this->version ) {
return;
}
if ( false === $db_version ) {
appointments_activate();
}
appointments_clear_cache();
include_once( 'includes/class-app-upgrader.php' );
$upgrader = new Appointments_Upgrader( $this->version );
$upgrader->upgrade( $db_version, $this->version );
}
function get_gcal_api() {
if ( false === $this->gcal_api && ! defined( 'APP_GCAL_DISABLE' ) ) {
require_once appointments_plugin_dir() . 'includes/class-app-gcal.php';
$this->gcal_api = new Appointments_Google_Calendar();
}
return $this->gcal_api;
}
/**
***************************************************************************************************************
* Methods for optimization
*
* $l: location ID - For future use
* $s: service ID
* $w: worker ID
* $stat: Status (open: working or closed: not working)
* IMPORTANT: This plugin is NOT intended for hundreds of services or service providers,
* but it is intended to make database queries as cheap as possible with smaller number of services/providers.
* If you have lots of services and/or providers, codes will not scale and appointments pages will be VERY slow.
* If you need such an application, override some of the methods below with a child class.
***************************************************************************************************************
*/
/**
* Get location, service, worker
*/
function get_lsw() {
$this->location = $this->get_location_id();
$this->service = $this->get_service_id();
$this->worker = $this->get_worker_id();
}
/**
* Get location ID for future use
*/
function get_location_id() {
if ( isset( $_REQUEST['app_location_id'] ) ) {
return (int) $_REQUEST['app_location_id']; }
return 0;
}
/**
* Get service ID from front end
* @return integer
*/
function get_service_id() {
if ( isset( $_REQUEST['app_service_id'] ) ) {
return (int) $_REQUEST['app_service_id']; } else if ( ! $service_id = appointments_get_services_min_id() ) {
$service_id = 0; }
return $service_id;
}
/**
* Get worker ID from front end
* worker = provider
* @return integer
*/
function get_worker_id() {
if ( ! is_admin() ) {
if ( isset( $_GET['app_provider_id'] ) ) {
return (int) $_GET['app_provider_id'];
}
}
if ( isset( $_REQUEST['app_provider_id'] ) ) {
return (int) $_REQUEST['app_provider_id'];
}
if ( isset( $_REQUEST['app_worker_id'] ) ) {
return (int) $_REQUEST['app_worker_id'];
}
return 0;
}
/**
* Return all reserve appointments by worker ID
* @param week: Optionally appointments only in the number of week in ISO 8601 format (since 1.2.3)
* @return array of objects
*/
function get_reserve_apps_by_worker( $l, $w, $week = 0 ) {
$cache_key = $l . '_' . $w . '_' . $week;
$cached = wp_cache_get( 'reserve_apps_by_worker' );
if ( false === $cached ) {
$cached = array();
}
if ( ! isset( $cached[ $cache_key ] ) ) {
$services = appointments_get_services();
$apps = array();
if ( $services ) {
foreach ( $services as $service ) {
$args = array(
'location' => $l,
'service' => $service->ID,
'worker' => $w,
'week' => $week,
);
$apps_worker = appointments_get_appointments_filtered_by_services( $args );
if ( $apps_worker ) {
$apps = array_merge( $apps, $apps_worker ); }
}
}
$cached[ $cache_key ] = $apps;
wp_cache_set( 'reserve_apps_by_worker', $cached );
} else {
$apps = $cached[ $cache_key ];
}
return $apps;
}
/**
* Find if a user is dummy
* @param user_id: Id of the user who will be checked if he is dummy
* since 1.0.6
*
* @deprecated since 2.1
*
* @return bool
*/
function is_dummy( $user_id = 0 ) {
_deprecated_function( __FUNCTION__, '2.1', 'Appointments_Worker::is_dummy' );
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
$worker = appointments_get_worker( $user_id );
if ( ! $worker ) {
return false;
}
return $worker->is_dummy();
}
/**
* Find worker email given his ID
* since 1.0.6
* @return string
*/
function get_worker_email( $worker = 0 ) {
// Real person
$worker_obj = appointments_get_worker( $worker );
if ( ! $worker_obj ) {
return '';
}
if ( ! $worker_obj->is_dummy() ) {
$worker_data = get_userdata( $worker );
if ( $worker_data ) {
$worker_email = $worker_data->user_email; } else { $worker_email = ''; }
return apply_filters( 'app_worker_email', $worker_email, $worker );
}
// Dummy
if ( isset( $this->options['dummy_assigned_to'] ) && $this->options['dummy_assigned_to'] ) {
$worker_data = get_userdata( $this->options['dummy_assigned_to'] );
if ( $worker_data ) {
$worker_email = $worker_data->user_email; } else { $worker_email = ''; }
return apply_filters( 'app_dummy_email', $worker_email, $worker );
}
// If not set anything, assign to admin
return $this->get_admin_email( );
}
/**
* Return admin email
* since 1.2.7
* @return string
*/
function get_admin_email() {
global $current_site;
$admin_email = get_option( 'admin_email' );
if ( ! $admin_email ) {
$admin_email = 'admin@' . $current_site->domain; }
return apply_filters( 'app_get_admin_email', $admin_email );
}
/**
* Find service name given its ID
* @return string
*/
function get_service_name( $service = 0 ) {
// Safe text if we delete a service
$name = __( 'Not defined', 'appointments' );
$result = appointments_get_service( $service );
if ( $result ) {
$name = $result->name; }
$name = apply_filters( 'app_get_service_name', $name, $service );
return stripslashes( $name );
}
/**
* Find client name given his appointment
* @return string
*/
function get_client_name( $app_id ) {
$name = '';
// This is only used on admin side, so an optimization is not required.
$result = appointments_get_appointment( $app_id );
if ( $result ) {
// Client can be a user
if ( $result->user ) {
$userdata = get_userdata( $result->user );
if ( $userdata ) {
$href = function_exists( 'bp_core_get_user_domain' ) && (defined( 'APP_BP_LINK_TO_PROFILE' ) && APP_BP_LINK_TO_PROFILE)
? bp_core_get_user_domain( $result->user )
: admin_url( 'user-edit.php?user_id=' ). $result->user
;
$name = '' .
($result->name && ! (defined( 'APP_USE_LEGACY_ADMIN_USERDATA_OVERRIDES' ) && APP_USE_LEGACY_ADMIN_USERDATA_OVERRIDES) ? $result->name : $userdata->user_login) .
'';
} else { $name = $result->name; }
} else {
$name = $result->name;
if ( ! $name ) {
$name = $result->email; }
}
}
return apply_filters( 'app_get_client_name', $name, $app_id, $result );
}
/**
* Get price for the current service and worker
* If worker has additional price (optional), it is added to the service price
* @param paypal: If set true, deposit price is calculated
* @return string
*/
function get_price( $paypal = false ) {
global $current_user;
$this->get_lsw();
$price = appointments_get_price( $this->service, $this->worker );
/**
* Filter allows other plugins or integrations to apply a discount to
* the price.
*/
$price = apply_filters( 'app_get_price_prepare', $price, $paypal, $this );
// Discount
if ( $this->is_member() && isset( $this->options['members_discount'] ) && $this->options['members_discount'] ) {
// Special condition: Free for members
if ( 100 == $this->options['members_discount'] ) {
$price = 0; } else { $price = $price * ( 100 - $this->options['members_discount'] ) / 100; }
}
if ( $paypal ) {
// Deposit
if ( isset( $this->options['percent_deposit'] ) && $this->options['percent_deposit'] ) {
$price = $price * $this->options['percent_deposit'] / 100; }
if ( isset( $this->options['fixed_deposit'] ) && $this->options['fixed_deposit'] ) {
$price = $this->options['fixed_deposit']; }
// It is possible to ask special amounts to be paid
$price = apply_filters( 'app_paypal_amount', $price, $this->service, $this->worker, $current_user->ID );
} else {
$price = apply_filters( 'app_get_price', $price, $this->service, $this->worker, $current_user->ID );
}
// Use number_format right at the end, cause it converts the number to a string.
$price = number_format( $price, 2 );
return $price;
}
/**
* Get deposit given price
* This is required only for manual pricing
* @param price: the full price
* @since 1.0.8
* @return string
*/
function get_deposit( $price ) {
$deposit = 0;
if ( ! $price ) {
return apply_filters( 'app_get_deposit', 0 ); }
// Discount
if ( $this->is_member() && isset( $this->options['members_discount'] ) && $this->options['members_discount'] ) {
// Special condition: Free for members
if ( 100 == $this->options['members_discount'] ) {
$price = 0; } else { $price = number_format( $price * ( 100 - $this->options['members_discount'] ) / 100, 2 ); }
}
// Deposit
if ( isset( $this->options['percent_deposit'] ) && $this->options['percent_deposit'] ) {
$deposit = number_format( $price * $this->options['percent_deposit'] / 100, 2 ); }
if ( isset( $this->options['fixed_deposit'] ) && $this->options['fixed_deposit'] ) {
$deposit = $this->options['fixed_deposit']; }
return apply_filters( 'app_get_deposit', $deposit );
}
/**
* Get the capacity of the current service
* @return integer
*/
function get_capacity( $service_id = false ) {
if ( $service_id && $service = appointments_get_service( $service_id ) ) {
$service_id = $service->ID;
} else {
$service_id = $this->service;
}
return appointments_get_service_capacity( $service_id );
}
/****************
* General methods
*****************
*/
/**
* Save a message to the log file
*/
function log( $message = '' ) {
if ( $message ) {
$to_put = '['. date_i18n( $this->datetime_format, $this->local_time ) .'] '. $message;
// Prevent multiple messages with same text and same timestamp
if ( ! file_exists( $this->log_file ) || strpos( @file_get_contents( $this->log_file ), $to_put ) === false ) {
@file_put_contents( $this->log_file, $to_put . chr( 10 ). chr( 13 ), FILE_APPEND );
}
}
}
/**
* Remove tabs and breaks
*/
function esc_rn( $text ) {
$text = str_replace( array( "\t", "\n", "\r" ), '', $text );
return $text;
}
/**
* Converts number of seconds to hours:mins acc to the WP time format setting
* @param integer $secs Seconds
* @param bool $forced_format
* @param bool $do_i18n
* @return bool|int|string
*/
function secs2hours( $secs, $forced_format = false, $do_i18n = true ) {
$min = (int) ($secs / 60);
$hours = '00';
if ( $min < 60 ) {
$hours_min = $hours . ':' . $min; } else {
$hours = (int) ($min / 60);
if ( $hours < 10 ) {
$hours = '0' . $hours; }
$mins = $min - $hours * 60;
if ( $mins < 10 ) {
$mins = '0' . $mins; }
$hours_min = $hours . ':' . $mins;
}
if ( ! empty( $forced_format ) ) { $hours_min = strtotime( $hours_min . ':00' ); } else if ( $this->time_format ) { $hours_min = strtotime( $hours_min . ':00' ); // @TODO: TEST THIS THOROUGHLY!!!!
}
if ( $do_i18n ) {
$hours_min = date_i18n( $this->time_format, $hours_min );
} elseif ( $forced_format ) {
$hours_min = date( $forced_format, $hours_min );
} else {
$hours_min = date( $this->time_format, $hours_min );
}
return $hours_min;
}
function secs_to_24h( $secs ) {
$min = (int) ($secs / 60);
$hours = '00';
if ( $min < 60 ) {
$hours_min = $hours . ':' . $min; } else {
$hours = (int) ($min / 60);
if ( $hours < 10 ) {
$hours = '0' . $hours; }
$mins = $min - $hours * 60;
if ( $mins < 10 ) {
$mins = '0' . $mins; }
$hours_min = $hours . ':' . $mins;
}
return date( 'H:i', strtotime( $hours_min ) );
}
/**
* Return an array of preset base times, so that strange values are not set
* @return array
*/
function time_base() {
$default = array( 10,15,30,60,90,120 );
$options = appointments_get_options();
$a = $options['additional_min_time'];
// Additional time bases
if ( isset( $a ) && $a && is_numeric( $a ) ) {
$default[] = $a; }
return apply_filters( 'app_time_base', $default );
}
/**
* Return minimum set interval time
* If not set, return a safe time.
*
* @return integer
*/
function get_min_time() {
$options = appointments_get_options();
$min_time = $options['min_time'];
if ( $min_time && $min_time > apply_filters( 'app_safe_min_time', 9 ) ) {
return apply_filters( 'app-time-min_time', absint( $min_time ) );
} else {
return apply_filters( 'app-time-min_time', apply_filters( 'app_safe_time', 10 ) );
}
}
/**
* Number of days that an appointment can be taken
* @return integer
*/
function get_app_limit() {
$options = appointments_get_options();
$app_limit = $options['app_limit'];
if ( $app_limit ) {
return apply_filters( 'app_limit', absint( $app_limit ) );
} else {
return apply_filters( 'app_limit', 365 );
}
}
/**
* Return an array of weekdays
* @return array
*/
function weekdays() {
return array(
__( 'Sunday', 'appointments' ) => 'Sunday',
__( 'Monday', 'appointments' ) => 'Monday',
__( 'Tuesday', 'appointments' ) => 'Tuesday',
__( 'Wednesday', 'appointments' ) => 'Wednesday',
__( 'Thursday', 'appointments' ) => 'Thursday',
__( 'Friday', 'appointments' ) => 'Friday',
__( 'Saturday', 'appointments' ) => 'Saturday',
);
}
/**
* Return a selected field name to further customize them and make translation easier
* @return string (name of the field)
*/
function get_field_name( $key ) {
$field_names = array(
'name' => __( 'Name', 'appointments' ),
'email' => __( 'Email', 'appointments' ),
'phone' => __( 'Phone', 'appointments' ),
'address' => __( 'Address', 'appointments' ),
'city' => __( 'City', 'appointments' ),
'note' => __( 'Note', 'appointments' ),
);
$field_names = apply_filters( 'app_get_field_name', $field_names );
if ( array_key_exists( $key, $field_names ) ) {
return $field_names[ $key ];
} else {
return __( 'Not defined', 'appointments' );
}
}
/**
* Return an array of all available front end box classes
* @return array
*/
function get_classes() {
return apply_filters( 'app_box_class_names',
array(
'free' => __( 'Free', 'appointments' ),
'busy' => __( 'Busy', 'appointments' ),
'notpossible' => __( 'Not possible', 'appointments' ),
)
);
}
/**
* Return a default color for a selected box class
* @return string
*/
function get_preset( $class, $set ) {
$presets = array(
1 => array(
'free' => '48c048',
'busy' => 'ffffff',
'notpossible' => 'ffffff',
),
2 => array(
'free' => '73ac39',
'busy' => '616b6b',
'notpossible' => '8f99a3',
),
3 => array(
'free' => '40BF40',
'busy' => '454C54',
'notpossible' => '454C54',
),
);
return isset( $presets[ $set ][ $class ] ) ? $presets[ $set ][ $class ] : '111111';
}
/************************************************************
* Methods for Shortcodes and those related to shortcodes only
*************************************************************
*/
/**
* Generate an excerpt from the selected service/worker page
* Applies custom filter set instead of the default one.
*/
function get_excerpt( $page_id, $thumb_size, $thumb_class, $worker_id = 0, $show_thumb_holder = false ) {
$text = '';
if ( ! $page_id ) {
return $text; }
$page = get_post( $page_id );
if ( ! $page ) {
return $text; }
$text = $page->post_excerpt;
if ( empty( $text ) ) {
$text = $page->post_content;
}
$text = strip_shortcodes( $text );
$text = apply_filters( 'app_the_content', $text, $page_id, $worker_id );
$text = str_replace( ']]>', ']]>', $text );
$excerpt_length = apply_filters( 'app_excerpt_length', 55 );
$excerpt_more = apply_filters( 'app_excerpt_more', ' … ' . __( 'More information →', 'appointments' ) . '' );
$text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
if ( $show_thumb_holder ) {
// @TODO Little crap :( to avoid so many queries when there are many services
$thumb = '
';
} else {
$thumb = $this->get_thumbnail( $page_id, $thumb_size, $thumb_class, $worker_id );
}
return apply_filters( 'app_excerpt', $thumb. $text, $page_id, $worker_id );
}
/**
* Fetch content from the selected service/worker page.
* Applies custom filter set instead of the default one.
*/
function get_content( $page_id, $thumb_size, $thumb_class, $worker_id = 0, $show_thumb_holder = false ) {
$content = '';
if ( ! $page_id ) {
return $content; }
$page = get_post( $page_id );
if ( ! $page ) {
return $content; }
if ( $show_thumb_holder ) {
// @TODO Little crap :( to avoid so many queries when there are many services
$thumb = '';
} else {
$thumb = $this->get_thumbnail( $page_id, $thumb_size, $thumb_class, $worker_id );
}
$app_content = apply_filters( 'app_pre_content', wpautop( $this->strip_app_shortcodes( $page->post_content ), true ) );
return apply_filters( 'app_content', $thumb. $app_content, $page_id, $worker_id );
}
/**
* Clear app shortcodes
* @since 1.1.9
*/
function strip_app_shortcodes( $content ) {
// Don't even try to touch a non string, just in case
if ( ! is_string( $content ) ) {
return $content; } else { return preg_replace( '%\[app_(.*?)\]%is', '', $content ); }
}
/**
* Get html code for thumbnail or avatar
*/
function get_thumbnail( $page_id, $thumb_size, $thumb_class, $worker_id ) {
if ( $thumb_size && 'none' != $thumb_size ) {
if ( strpos( $thumb_size, 'avatar' ) !== false ) {
if ( strpos( $thumb_size, ',' ) !== false ) {
$size_arr = explode( ',', $thumb_size );
$size = $size_arr[1];
} else { $size = 96; }
$thumb = get_avatar( $worker_id, $size );
if ( $thumb_class ) {
// Dirty, but faster than preg_replace
$thumb = str_replace( "class='", "class='".$thumb_class.' ', $thumb );
$thumb = str_replace( 'class="', 'class="'.$thumb_class.' ', $thumb );
}
} else {
if ( strpos( $thumb_size, ',' ) !== false ) {
$size = explode( ',', $thumb_size ); } else { $size = $thumb_size; }
$thumb = get_the_post_thumbnail( $page_id, $size, apply_filters( 'app_thumbnail_attr', array( 'class' => $thumb_class ) ) );
}
} else { $thumb = ''; }
return apply_filters( 'app_thumbnail', $thumb, $page_id, $worker_id );
}
/**
* Build GCal url for GCal Button. It requires UTC time.
* @param start: Timestamp of the start of the app
* @param end: Timestamp of the end of the app
* @param php: If this is called for php. If false, called for js
* @param address: Address of the appointment
* @param city: City of the appointment
* @return string
*/
function gcal( $service, $start, $end, $php = false, $address, $city ) {
// Find time difference from Greenwich as GCal asks UTC
$text = sprintf( __( '%s Appointment', 'appointments' ), $this->get_service_name( $service ) );
if ( ! $php ) { $text = esc_js( $text ); }
$gmt_start = get_gmt_from_date( date( 'Y-m-d H:i:s', $start ), 'Ymd\THis\Z' );
$gmt_end = get_gmt_from_date( date( 'Y-m-d H:i:s', $end ), 'Ymd\THis\Z' );
$location = isset( $this->options['gcal_location'] ) && '' != trim( $this->options['gcal_location'] )
? esc_js( str_replace( array( 'ADDRESS', 'CITY' ), array( $address, $city ), $this->options['gcal_location'] ) )
: esc_js( get_bloginfo( 'description' ) );
$param = array(
'action' => 'TEMPLATE',
'text' => $text,
'dates' => $gmt_start . '/' . $gmt_end,
'sprop' => 'website:' . home_url(),
'location' => rawurlencode( $location ),
);
return add_query_arg(
apply_filters( 'app_gcal_variables', $param, $service, $start, $end ),
'http://www.google.com/calendar/event'
);
}
/**
* Die showing which field has a problem
*
* @param string $field_name
*/
function json_die( $field_name ) {
die( json_encode( array( 'error' => sprintf( __( 'Something wrong about the submitted %s', 'appointments' ), $this->get_field_name( $field_name ) ) ) ) );
}
/**
* Check for too frequent back to back apps
* return true means no spam
* @return bool
*/
function check_spam() {
$options = appointments_get_options();
if (
! isset( $options['spam_time'] )
|| ! $options['spam_time']
|| ! Appointments_Sessions::is_visitor_appointments_cookie_set()
) {
return true;
}
$apps = Appointments_Sessions::get_current_visitor_appointments();
if ( empty( $apps ) ) {
return true;
}
$checkdate = date( 'Y-m-d H:i:s', $this->local_time - $options['spam_time'] );
$results = appointments_get_appointments( array(
'app_id' => $apps,
'status' => 'pending',
'date_query' => array(
array(
'field' => 'created',
'compare' => '>',
'value' => $checkdate,
),
),
) );
// A recent app is found
if ( $results ) {
return false;
}
return true;
}
/**
* Find timestamp of first day of month for a given time
* @param time: input whose first day will be found
* @param add: how many months to add
* @return integer (timestamp)
* @since 1.0.4
*/
function first_of_month( $time, $add ) {
$year = date( 'Y', $time );
$month = date( 'n', $time ); // Notice "n"
return mktime( 0, 0, 0, $month + $add, 1, $year );
}
/**
* Helper function to create a monthly schedule
*
* @deprecated 2.0.6
*/
function get_monthly_calendar( $timestamp = false, $class = '', $long, $widget ) {
_deprecated_function( __FUNCTION__, '2.1', 'appointments_monthly_calendar' );
$this->get_lsw();
$args = array(
'service_id' => $this->service,
'worker_id' => $this->worker,
'location_id' => $this->location,
'class' => $class,
'long' => $long,
'echo' => false,
'widget' => $widget,
);
return appointments_monthly_calendar( $timestamp, $args );
}
/**
* Helper function to create a time table for monthly schedule
*
* @since 2.2.1 Added `hide_today` argument.
*/
function get_timetable( $day_start, $capacity, $schedule_key = false, $hide_today = false ) {
$local_time = current_time( 'timestamp' );
$data = $this->_get_timetable_slots( $day_start, $capacity, $schedule_key );
// We need this only for the first timetable
// Otherwise $time will be calculated from $day_start
if ( isset( $_GET['wcalendar'] ) && (int) $_GET['wcalendar'] ) {
$time = (int) $_GET['wcalendar'];
} else {
$time = $local_time;
}
// Are we looking to today?
// If today is a working day, shows its free times by default unless user hides it
$style = ' style="display:none"';
if ( date( 'Ymd', $day_start ) == date( 'Ymd', $time ) && ! $hide_today ) {
$style = '';
}
$ret = '';
$ret .= '';
return $ret;
}
/**
* This function tries to separate logic from presentation in Appointments::get_timetables()
* It's a first step to move this function to another place so do not use it
*/
public function _get_timetable_slots( $day_start, $capacity, $schedule_key = false ) {
$timetable_key = $day_start . '-' . $capacity;
$local_time = current_time( 'timestamp' );
$this->get_lsw();
if ( ! $schedule_key ) {
$timetable_key .= '-0';
} else {
$timetable_key .= '-' . $schedule_key;
}
// We need this only for the first timetable
// Otherwise $time will be calculated from $day_start
if ( isset( $_GET['wcalendar'] ) && (int) $_GET['wcalendar'] ) {
$time = (int) $_GET['wcalendar'];
} else {
$time = $local_time;
}
$timetable_key .= '-' . $this->worker;
$timetable_key .= '-' . date( 'Ym', $time );
// Calculate step
$start = $end = 0;
if ( $min_max = $this->min_max_wh( 0, 0 ) ) {
$start = $min_max['min'];
$end = $min_max['max'];
}
if ( $start >= $end ) {
$start = 8;
$end = 18;
}
$start = apply_filters( 'app_schedule_starting_hour', $start, $day_start, 'day' );
$end = apply_filters( 'app_schedule_ending_hour', $end, $day_start, 'day' );
$first = $start * HOUR_IN_SECONDS + $day_start; // Timestamp of the first cell
$last = $end * HOUR_IN_SECONDS + $day_start; // Timestamp of the last cell
$min_step_time = $this->get_min_time() * MINUTE_IN_SECONDS; // Cache min step increment
if ( appointments_use_legacy_duration_calculus() ) {
$step = $min_step_time; // Timestamp increase interval to one cell ahead
} else {
$service = appointments_get_service( $this->service );
$step = ( ! empty( $service->duration ) ? $service->duration : $min_step_time) * 60; // Timestamp increase interval to one cell ahead
}
if ( ! appointments_use_legacy_duration_calculus() ) {
$start_result = appointments_get_worker_working_hours( 'open', $this->worker, $this->location );
$start_unpacked_days = isset( $start_result->hours ) ? $start_result->hours : array();
} else {
$start_unpacked_days = array();
}
if ( appointments_use_legacy_break_times_padding_calculus() ) {
$break_result = appointments_get_worker_working_hours( 'closed', $this->worker, $this->location );
$break_times = $break_result->hours;
} else {
$break_times = array();
}
// Allow direct step increment manipulation,
// mainly for service duration based calculus start/stop times
$step = apply_filters( 'app-timetable-step_increment', $step, 'timetable' );
if ( empty( $step ) || ! is_numeric( $step ) ) {
// If step is null/0 etc we can end up with problems
return '';
}
$timetable_key .= '-' . $step . '-' . $this->service;
if ( isset( $this->timetables[ $timetable_key ] ) ) {
$data = $this->timetables[ $timetable_key ];
} else {
$data = array();
for ( $t = $first; $t < $last; ) {
$ccs = apply_filters( 'app_ccs', $t ); // Current cell starts
$cce = apply_filters( 'app_cce', $ccs + $step ); // Current cell ends
// Fix for service durations calculus and workhours start conflict with different duration services
// Example: http://premium.wpmudev.org/forums/topic/problem-with-time-slots-not-properly-allocating-free-time
$this_day_key = date( 'l', $t );
if ( ! empty( $start_unpacked_days ) && ! appointments_use_legacy_duration_calculus() ) {
if ( ! empty( $start_unpacked_days[ $this_day_key ] ) ) {
// Check slot start vs opening start
$this_day_opening_timestamp = strtotime( date( 'Y-m-d ' . $start_unpacked_days[ $this_day_key ]['start'], $ccs ) );
if ( $t < $this_day_opening_timestamp ) {
$t = ($t - $step) + (apply_filters( 'app_safe_time', 1 ) * 60);
$t = apply_filters( 'app_next_time_step', $t + $step, $ccs, $step ); //Allows dynamic/variable step increment.
continue;
}
// Check slot end vs opening end - optional, but still applies
//$this_day_closing_timestamp = strtotime(date('Y-m-d ' . $start_unpacked_days[$this_day_key]['end'], $ccs));
//if ($cce > $this_day_closing_timestamp) continue;
}
}
// Breaks are not behaving like paddings, which is to be expected.
// This fix (2) will force them to behave more like paddings
if ( ! empty( $break_times[ $this_day_key ]['active'] ) && appointments_use_legacy_break_times_padding_calculus() ) {
$active = $break_times[ $this_day_key ]['active'];
$break_starts = $break_times[ $this_day_key ]['start'];
$break_ends = $break_times[ $this_day_key ]['end'];
if ( ! is_array( $active ) && 'no' !== $active ) {
$break_start_ts = strtotime( date( 'Y-m-d ' . $break_starts, $ccs ) );
$break_end_ts = strtotime( date( 'Y-m-d ' . $break_ends, $ccs ) );
if ( $t == $break_start_ts ) {
$t += ($break_end_ts - $break_start_ts) - $step;
$t = apply_filters( 'app_next_time_step', $t + $step, $ccs, $step ); //Allows dynamic/variable step increment.
continue;
}
} else if ( is_array( $active ) && in_array( 'yes', array_values( $active ) ) ) {
$has_break_time = false;
for ( $idx = 0; $idx < count( $break_starts ); $idx++ ) {
$break_start_ts = strtotime( date( 'Y-m-d ' . $break_starts[ $idx ], $ccs ) );
$break_end_ts = strtotime( date( 'Y-m-d ' . $break_ends[ $idx ], $ccs ) );
if ( $t == $break_start_ts ) {
$has_break_time = $break_end_ts - $break_start_ts;
break;
}
}
if ( $has_break_time ) {
$t += ($has_break_time - $step);
$t = apply_filters( 'app_next_time_step', $t + $step, $ccs, $step ); //Allows dynamic/variable step increment.
continue;
}
}
}
// End fixes area
$is_busy = false;
// Mark now
if ( $local_time > $ccs && $local_time < $cce ) {
$class_name = 'notpossible now';
} // Mark passed hours
else if ( $local_time > $ccs ) {
$class_name = 'notpossible app_past';
} // Then check if this time is blocked
else if ( isset( $this->options['app_lower_limit'] ) && $this->options['app_lower_limit']
&& ( $local_time + $this->options['app_lower_limit'] * 3600 ) > $cce
) {
$class_name = 'notpossible app_blocked';
} // Check if this is break
else if ( $this->is_break( $ccs, $cce ) ) {
$class_name = 'notpossible app_break';
} // Then look for appointments
else if ( $is_busy = $this->is_busy( $ccs, $cce, $capacity ) ) {
$class_name = 'busy';
} // Then check if we have enough time to fulfill this app
else if ( ! $this->is_service_possible( $ccs, $cce, $capacity ) ) {
$class_name = 'notpossible service_notpossible';
} // If nothing else, then it must be free
else {
$class_name = 'free';
// We found at least one timetable cell to be free
}
$class_name = apply_filters( 'app_class_name', $class_name, $ccs, $cce );
$title = apply_filters( 'app-schedule_cell-title', date_i18n( $this->datetime_format, $ccs ), $is_busy, $ccs, $cce, $schedule_key );
$data[] = array(
'class' => $class_name,
'title' => $title,
'hours' => $this->secs2hours( $ccs - $day_start ),
'ccs' => $ccs,
'cce' => $cce,
);
$t = apply_filters( 'app_next_time_step', $t + $step, $t, $step ); //Allows dynamic/variable step increment.
}
}
$this->timetables[ $timetable_key ] = $data;
// Save timetables only once at the end of the execution
add_action( 'shutdown', array( $this, 'save_timetables' ) );
return $data;
}
public function save_timetables() {
set_transient( 'app_timetables', $this->timetables, 86400 ); // save for one day
}
function _get_table_meta_row_monthly( $which, $long ) {
if ( ! $long ) {
$day_names_array = $this->arrange( $this->get_short_day_names(), false );
} else {
$day_names_array = $this->arrange( $this->get_day_names(), false );
}
$cells = '' . join( ' | ', $day_names_array ) . ' | ';
return "<{$which}>{$cells}
{$which}>";
}
/**
* Helper function to create a weekly schedule
*
* @deprecated since 2.1
*/
function get_weekly_calendar( $timestamp = false, $class = '', $long = false ) {
_deprecated_function( __FUNCTION__, '2.1', 'appointments_weekly_calendar' );
$this->get_lsw();
$current_time = current_time( 'timestamp' );
$date = $timestamp ? $timestamp : $current_time;
return appointments_weekly_calendar( $date, array(
'worker_id' => $this->worker,
'service_id' => $this->service,
'location_id' => $this->location,
'long' => $long,
'class' => $class,
'echo' => false,
));
}
function get_day_names() {
global $wp_locale;
return $wp_locale->weekday;
}
function get_short_day_names() {
global $wp_locale;
return array_values( $wp_locale->weekday_initial );
}
/**
* Returns the timestamp of Sunday of the current time or selected date
* @param timestamp: Timestamp of the selected date or false for current time
* @return integer (timestamp)
*
* @deprecated since 2.1
*/
function sunday( $timestamp = false ) {
_deprecated_function( __FUNCTION__, '2.1' );
$date = $timestamp ? $timestamp : $this->local_time;
// Return today's timestamp if today is sunday and start of the week is set as Sunday
if ( 'Sunday' == date( 'l', $date ) && 0 == $this->start_of_week ) {
return strtotime( 'today', $date ); } // Else return last week's timestamp
else { return strtotime( 'last Sunday', $date ); }
}
/**
* Arranges days array acc. to start of week, e.g 1234567 (Week starting with Monday)
* @param days: input array
* @param prepend: What to add as first element
* @pram nod: If number of days (true) or name of days (false)
* @return array
*/
function arrange( $days, $prepend, $nod = false ) {
if ( $this->start_of_week ) {
for ( $n = 1; $n <= $this->start_of_week; $n++ ) {
array_push( $days, array_shift( $days ) );
}
// Fix for displaying past days; apply only for number of days
if ( $nod ) {
$first = false;
$temp = array();
foreach ( $days as $key => $day ) {
if ( ! $first ) {
$first = $day; // Save the first day
} if ( $day < $first ) {
$temp[ $key ] = $day + 7; // Latter days should be higher than the first day
} else { $temp[ $key ] = $day; }
}
$days = $temp;
}
}
if ( false !== $prepend ) {
array_unshift( $days, $prepend ); }
return $days;
}
/**
* Get which days of the week we are working
* @return array (may be empty)
*/
function get_working_days( $worker = 0, $location = 0 ) {
$working_days = array();
$result = appointments_get_worker_working_hours( 'open', $worker, $location );
if ( $result !== null ) {
$days = $result->hours;
if ( is_array( $days ) ) {
foreach ( $days as $day_name => $day ) {
if ( isset( $day['active'] ) && 'yes' == $day['active'] ) {
$working_days[] = $day_name;
}
}
}
}
return $working_days;
}
/**
* Check if this is an exceptional working day
* Optionally a worker is selectable ( $w != 0 )
*
* @deprecated since 2.2
*
* @return bool
*/
function is_exceptional_working_day( $ccs, $cce, $w = 0 ) {
_deprecated_function( __FUNCTION__, '2.2', 'appointments_is_exceptional_working_day' );
// A worker can be forced
if ( ! $w ) {
$w = $this->worker;
}
return appointments_is_exceptional_working_day( $ccs, $cce, $w );
}
/**
* Check if it is break time
* Optionally a worker is selectable ( $w != 0 )
* @return bool
*/
function is_break( $ccs, $cce, $w = 0 ) {
// A worker can be forced
if ( ! $w ) {
$w = $this->worker;
}
return appointments_is_interval_break( $ccs, $cce, $w, $this->location );
}
/**
* Check if a specific worker is working at this time slot
* @return bool
* @since 1.2.2
*
* @deprecated since 2.2
*/
function is_working( $ccs, $cse, $w ) {
_deprecated_function( __FUNCTION__, '2.2', 'appointments_is_working' );
if ( ! $w ) {
$w = $this->worker;
}
return appointments_is_working( $ccs, $cse, $w, $this->location );
}
/**
* Correctly calculate timestamp based on day and hours:min
* This is required as php versions prior to 5.3 cannot calculate 24:00
* @param $this_day: Date in d F Y format
* @param $end: time in military hours:min format
* @since 1.1.8
* @return integer (timestamp)
*/
function str2time( $this_day, $end ) {
if ( '24:00' != $end ) {
return strtotime( $this_day. ' '. $end, $this->local_time ); } else { return ( strtotime( $this_day. ' 23:59', $this->local_time ) + 60 ); }
}
/**
* Check if time is enough for this service
* e.g if we are working until 6pm, it is not possible to take an app with 60 mins duration at 5:30pm
* Please note that "not possible" is an exception
* @return bool
*/
function is_service_possible( $ccs, $cce, $capacity ) {
// If this cell exceeds app limit then return false
if ( $this->get_app_limit() < ceil( ( $ccs - $this->local_time ) / 86400 ) ) {
return false;
}
$result = appointments_get_service( $this->service );
if ( ! $result !== null ) {
$duration = $result->duration;
if ( ! $duration ) {
return true; // This means min time will be applied. No need to look
}
// The same for break time
if ( isset( $this->options['allow_overwork_break'] ) && 'yes' == $this->options['allow_overwork_break'] ) {
$allow_overwork_break = true; } else { $allow_overwork_break = false; }
// The same for break time
if ( isset( $this->options['allow_overwork'] ) && 'yes' == $this->options['allow_overwork'] ) {
$allow_overwork = true; } else { $allow_overwork = false; }
// Check for further appointments or breaks on this day, if this is a lasting appointment
if ( $duration > $this->get_min_time() ) {
$step = ceil( $duration / $this->get_min_time() );
$min_secs = $this->get_min_time() * 60;
if ( $step < 20 ) { // Let's not exaggerate !
for ( $n = 1; $n < $step; $n++ ) {
if ( ! $allow_overwork ) {
if ( $this->is_busy( $ccs + $n * $min_secs, $ccs + ($n + 1) * $min_secs, $capacity ) ) {
return false; // There is an appointment in the predeeding times
}
}
// We can check breaks here too
if ( ! $allow_overwork_break ) {
if ( $this->is_break( $ccs + $n * $min_secs, $ccs + ($n + 1) * $min_secs ) ) {
return false; // There is a break in the predeeding times
}
}
}
}
}
// Now look where our working hour ends
$days = wp_cache_get( 'app-open_times-for-' . $this->worker );
if ( ! $days ) {
// Preprocess and cache workhours
// Look where our working hour ends
$result_days = appointments_get_worker_working_hours( 'open', $this->worker, $this->location );
if ( $result_days && is_object( $result_days ) && ! empty( $result_days->hours ) ) {
$days = $result_days->hours;
}
if ( $days ) {
wp_cache_set( 'app-open_times-for-' . $this->worker, $days );
}
}
if ( ! is_array( $days ) || empty( $days ) ) {
return true;
}
// What is the name of this day?
$this_days_name = date( 'l', $ccs );
// This days midnight
$this_day = date( 'd F Y', $ccs );
// Will the service exceed or working time?
$css_plus_duration = $ccs + ($duration * 60);
foreach ( $days as $day_name => $day ) {
// // Jose's fix pt1 (c19c7d65bb860a265ceb7f6a6075ae668bd60100)
//if ( $day_name == $this_days_name && isset( $day["active"] ) && 'yes' == $day["active"] ) {
if ( $day_name == $this_days_name ) {
// Special case: End time is 00:00
$end_mil = $this->to_military( $day['end'] );
if ( '00:00' == $end_mil ) {
$end_mil = '24:00';
}
if ( $allow_overwork ) {
if ( $ccs >= $this->str2time( $this_day, $end_mil ) ) {
return false;
}
} else {
if ( $css_plus_duration > $this->str2time( $this_day, $end_mil ) ) {
return false;
}
}
// We need to check a special case where schedule starts on eg 4pm, but our work starts on 4:30pm.
if ( $ccs < strtotime( $this_day . ' ' . $this->to_military( $day['start'] ) , $this->local_time ) ) {
return false; }
}
}
}
return true;
}
/**
* Return available number of workers for a time slot
* e.g if one worker works between 8-11 and another works between 13-15, there is no worker between 11-13
* This is called from is_busy function
* since 1.0.6
* @return integer
*/
function available_workers( $ccs, $cce ) {
// If a worker is selected we dont need to do anything special
if ( $this->worker ) {
return $this->get_capacity(); }
return appointments_get_available_workers_for_interval( $ccs, $cce, $this->service, $this->location );
}
/**
* Check if a cell is not available, i.e. all appointments taken OR we dont have workers for this time slot
* @return bool
*/
function is_busy( $start, $end, $capacity ) {
$args = array(
'location_id' => $this->location,
'service_id' => $this->service,
'worker_id' => $this->worker,
'capacity' => $capacity,
);
return apppointments_is_range_busy( $start, $end, $args );
}
/**
* Remove duplicate appointment objects by app ID
* @since 1.1.5.1
* @return array of objects
*/
function array_unique_object_by_ID( $apps ) {
if ( ! is_array( $apps ) || empty( $apps ) ) {
return array(); }
$idlist = array();
// Save array to a temp area
$result = $apps;
foreach ( $apps as $key => $app ) {
if ( isset( $app->ID ) ) {
if ( in_array( $app->ID, $idlist ) ) {
unset( $result[ $key ] ); } else { $idlist[] = $app->ID; }
}
}
return $result;
}
/**
* Get the maximum and minimum working hour
* @return array|false
*/
function min_max_wh( $worker = 0, $location = 0 ) {
$this->get_lsw();
$result = appointments_get_worker_working_hours( 'open', $this->worker, $this->location );
if ( $result ) {
$days = $result->hours;
$days = array_filter( $days );
if ( is_array( $days ) ) {
$min = 24; $max = 0;
foreach ( $days as $day ) {
// Jose's fix pt2 (c19c7d65bb860a265ceb7f6a6075ae668bd60100)
/*
if ( isset( $day["active"] ) && 'yes' == $day["active"] ) {
$start = date( "G", strtotime( $this->to_military( $day["start"] ) ) );
$end_timestamp = strtotime( $this->to_military( $day["end"] ) );
$end = date( "G", $end_timestamp );
// Add 1 hour if there are some minutes left. e.g. for 10:10pm, make max as 23
if ( '00' != date( "i", $end_timestamp ) && $end != 24 )
$end = $end + 1;
if ( $start < $min )
$min = $start;
if ( $end > $max )
$max = $end;
// Special case: If end is 0:00, regard it as 24
if ( 0 == $end && '00' == date( "i", $end_timestamp ) )
$max = 24;
}
*/
if ( ! isset( $day['start'] ) || ! isset( $day['end'] ) ) {
continue;
}
$start = date( 'G', strtotime( $this->to_military( $day['start'] ) ) );
$end_timestamp = strtotime( $this->to_military( $day['end'] ) );
$end = date( 'G', $end_timestamp );
// Add 1 hour if there are some minutes left. e.g. for 10:10pm, make max as 23
if ( '00' != date( 'i', $end_timestamp ) && $end != 24 ) {
$end = $end + 1; }
if ( $start < $min ) {
$min = $start; }
if ( $end > $max ) {
$max = $end; }
// Special case: If end is 0:00, regard it as 24
if ( 0 == $end && '00' == date( 'i', $end_timestamp ) ) {
$max = 24; }
}
return array( 'min' => $min, 'max' => $max );
}
}
return false;
}
/**
* Convert any time format to military format
* @since 1.0.3
* @return string
*/
function to_military( $time, $end = false ) {
// Already in military format
if ( 'H:i' == $this->time_format ) {
return $time; }
// In one of the default formats
if ( 'g:i a' == $this->time_format || 'g:i A' == $this->time_format ) {
return date( 'H:i', strtotime( $time ) ); }
// Custom format. Use a reference time
// ref will something like 23saat45dakika
$ref = date_i18n( $this->time_format, strtotime( '23:45' ) );
if ( strpos( $ref, '23' ) !== false ) {
$twentyfour = true; } else { $twentyfour = false; }
// Now ref is something like saat,dakika
$ref = ltrim( str_replace( array( '23', '45' ), ',', $ref ), ',' );
$ref_arr = explode( ',', $ref );
if ( isset( $ref_arr[0] ) ) {
$s = $ref_arr[0]; // separator. We will replace it by :
if ( isset( $ref_arr[1] ) && $ref_arr[1] ) {
$e = $ref_arr[1]; } else {
$e = 'PLACEHOLDER';
$time = $time. $e; // Add placeholder at the back
}
if ( $twentyfour ) {
$new_e = ''; } else { $new_e = ' a'; }
} else { return $time; // Nothing found ??
}
return date( 'H:i', strtotime( str_replace( array( $s, $e ), array( ':', $new_e ), $time ) ) );
}
/**
* Pack several fields as a string using glue ":"
* location : service : worker : ccs : cce : post ID
* @return string
*/
function pack( $ccs, $cce ) {
global $post;
if ( is_object( $post ) ) {
$post_id = $post->ID; } else { $post_id = 0; }
return $this->location . ':' . $this->service . ':' . $this->worker . ':' . $ccs . ':' . $cce . ':' . $post_id;
}
/**
* Make sure we clean up cookies after logging out.
*/
public function drop_cookies_on_logout() {
$options = appointments_get_options();
if ( 'yes' !== $options['login_required'] ) {
return;
}
Appointments_Sessions::clear_visitor_data();
}
/****************************************
* Methods for integration with Membership
*****************************************
*/
/**
* Finds if user is Membership member with sufficient level
* @return bool
*/
function is_member() {
$membership_active = ( is_admin() && class_exists( 'membershipadmin' ) ) || ( ! is_admin() && class_exists( 'membershippublic' ) );
if ( $membership_active && isset( $this->options['members'] ) ) {
global $current_user;
$meta = maybe_unserialize( $this->options['members'] );
$member = new M_Membership( $current_user->ID );
if ( is_array( $meta ) && $current_user->ID > 0 && $member->has_levels() ) {
// Load the levels for this member
$levels = $member->get_level_ids( );
if ( is_array( $levels ) && is_array( $meta['level'] ) ) {
foreach ( $levels as $level ) {
if ( in_array( $level->level_id, $meta['level'] ) ) {
return true; // Yes, user has sufficent level
}
}
}
}
}
return false;
}
/*******************************
* Methods for inits, styles, js
********************************
*/
/**
* Initialize widgets
*/
function widgets_init() {
if ( ! is_blog_installed() ) {
return; }
register_widget( 'Appointments_Widget_Services' );
register_widget( 'Appointments_Widget_Service_Providers' );
register_widget( 'Appointments_Widget_Monthly_Calendar' );
}
/**
* Add a script to be used in the footer, checking duplicates
* In some servers, footer scripts were called twice. This function fixes it.
* @since 1.2.0
*/
function add2footer( $script = '' ) {
if ( $script && strpos( $this->script, $script ) === false ) {
$this->script = $this->script . $script; }
}
/**
* Load javascript to the footer
*/
function wp_footer() {
$script = '';
$this->script = apply_filters( 'app_footer_scripts', $this->script );
if ( $this->script ) {
ob_start();
?>
esc_rn( $script );
do_action( 'app-footer_scripts-after' );
}
/**
* Load style and script only when they are necessary
* http://beerpla.net/2010/01/13/wordpress-plugin-development-how-to-include-css-and-javascript-conditionally-and-only-when-needed-by-the-posts/
*/
function load_styles( $posts ) {
if ( empty( $posts ) || is_admin() ) {
return $posts; }
$this->shortcode_found = false; // use this flag to see if styles and scripts need to be enqueued
foreach ( $posts as $post ) {
if ( is_object( $post ) && stripos( $post->post_content, '[app_' ) !== false ) {
$this->shortcode_found = true;
do_action( 'app-shortcodes-shortcode_found', $post );
}
}
if ( $this->shortcode_found ) {
$this->load_scripts_styles( ); }
return $posts;
}
/**
* Function to load all necessary scripts and styles
* Can be called externally, e.g. when forced from a page template
*/
function load_scripts_styles() {
wp_enqueue_script( 'jquery' );
wp_enqueue_script( 'jquery-tablesorter', $this->plugin_url . '/js/jquery.tablesorter.min.js', array( 'jquery' ), $this->version );
add_action( 'wp_footer', array( &$this, 'wp_footer' ) ); // Publish plugin specific scripts in the footer
// TODO: consider this
wp_enqueue_script( 'app-js-check', $this->plugin_url . '/js/js-check.js', array( 'jquery' ), $this->version );
$thank_page_id = ! empty( $this->options['thank_page'] ) ? absint( $this->options['thank_page'] ) : 0;
$cancel_page_id = ! empty( $this->options['cancel_page'] ) ? absint( $this->options['cancel_page'] ) : 0;
wp_localize_script( 'app-js-check', '_appointments_data',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'root_url' => plugins_url( 'appointments/images/' ),
'thank_page_url' => get_permalink( $thank_page_id ),
'cancel_url' => get_permalink( $cancel_page_id ),
)
);
if ( ! current_theme_supports( 'appointments_style' ) ) {
wp_enqueue_style( 'appointments', $this->plugin_url. '/css/front.css', array(), $this->version );
add_action( 'wp_head', array( &$this, 'wp_head' ) );
}
do_action( 'app-scripts-general' );
// Prevent external caching plugins for this page
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
define( 'DONOTCACHEPAGE', true ); }
// Prevent W3T Minify
if ( ! defined( 'DONOTMINIFY' ) ) {
define( 'DONOTMINIFY', true ); }
// Set up services support defaults
$show_login_button = array( 'google', 'wordpress' );
if ( ! empty( $this->options['facebook-app_id'] ) ) { $show_login_button[] = 'facebook'; }
if ( ! empty( $this->options['twitter-app_id'] ) && ! empty( $this->options['twitter-app_secret'] ) ) { $show_login_button[] = 'twitter'; }
// Is registration allowed?
$do_register = is_multisite()
? in_array( get_site_option( 'registration' ), array( 'all', 'user' ) )
: (int) get_option( 'users_can_register' );
// Load the rest only if API use is selected
if ( @$this->options['accept_api_logins'] ) {
wp_enqueue_script( 'appointments_api_js', $this->plugin_url . '/js/appointments-api.js', array( 'jquery' ), $this->version );
wp_localize_script('appointments_api_js', 'l10nAppApi', apply_filters('app-scripts-api_l10n', array(
'facebook' => __( 'Login with Facebook', 'appointments' ),
'twitter' => __( 'Login with Twitter', 'appointments' ),
'google' => __( 'Login with Google+', 'appointments' ),
'wordpress' => __( 'Login with WordPress', 'appointments' ),
'submit' => __( 'Submit', 'appointments' ),
'cancel' => _x( 'Cancel', 'Drop current action', 'appointments' ),
'please_wait' => __( 'Please, wait...', 'appointments' ),
'logged_in' => __( 'You are now logged in', 'appointments' ),
'error' => __( 'Login error. Please try again.', 'appointments' ),
'_can_use_twitter' => ( ! empty( $this->options['twitter-app_id'] ) && ! empty( $this->options['twitter-app_secret'] )),
'show_login_button' => $show_login_button,
'register' => ($do_register ? __( 'Register', 'appointments' ) : ''),
'registration_url' => ($do_register ? wp_registration_url() : ''),
)));
if ( ! empty( $this->options['facebook-app_id'] ) ) {
if ( ! $this->options['facebook-no_init'] ) {
add_action('wp_footer', create_function('', "echo '" .
sprintf(
'',
$this->options['facebook-app_id']
) .
"';"));
}
}
do_action( 'app-scripts-api' );
}
/**
* Fired when scripts/styles have been loaded
*/
do_action( 'appointments_scripts_loaded' );
}
/**
* css that will be added to the head, again only for app pages
*/
function wp_head() {
?>
30,
'additional_min_time' => '',
'admin_min_time' => '',
'app_lower_limit' => 0,
'app_limit' => 365,
'clear_time' => 60,
'spam_time' => 0,
'auto_confirm' => 'no',
'allow_worker_selection' => 'no',
'allow_worker_confirm' => 'no',
'allow_overwork' => 'no',
'allow_overwork_break' => 'no',
'dummy_assigned_to' => 0,
'app_page_type' => 'monthly',
'accept_api_logins' => '',
'facebook-app_id' => '',
'twitter-app_id' => '',
'twitter-app_secret' => '',
'show_legend' => 'yes',
'gcal' => 'yes',
'gcal_location' => '',
'color_set' => 1,
'free_color' => '48c048',
'busy_color' => 'ffffff',
'notpossible_color' => 'ffffff',
'make_an_appointment' => '',
'ask_name' => '1',
'ask_email' => '1',
'ask_phone' => '1',
'ask_address' => '',
'ask_city' => '',
'ask_note' => '',
'additional_css' => '.appointments-list td{ border:none; width:50%; }',
'payment_required' => 'no',
'percent_deposit' => '',
'fixed_deposit' => '',
'currency' => 'USD',
'mode' => 'sandbox',
'merchant_email' => '',
'return' => 1,
'login_required' => 'no',
'send_confirmation' => 'yes',
'send_notification' => 'no',
'send_reminder' => 'yes',
'reminder_time' => '24',
'send_reminder_worker' => 'yes',
'reminder_time_worker' => '4',
'confirmation_subject' => __( 'Confirmation of your Appointment','appointments' ),
'confirmation_message' => $confirmation_message,
'reminder_subject' => __( 'Reminder for your Appointment','appointments' ),
'reminder_message' => $reminder_message,
'log_emails' => 'yes',
'allow_cancel' => 'no',
'cancel_page' => 0,
));
do_action( 'appointments_init', $this );
// Run this code not before 10 mins
if ( ( time() - get_option( 'app_last_update' ) ) < apply_filters( 'app_update_time', 600 ) ) {
return;
}
$this->remove_appointments();
update_option( 'app_last_update', time() );
}
/*******************************
* Methods for Confirmation
********************************
/**
* Replace placeholders with real values for email subject and content
*/
function _replace( $text, $user, $service, $worker, $datetime, $price, $deposit, $phone = '', $note = '', $address = '', $email = '', $city = '' ) {
/*
return str_replace(
array( "SITE_NAME", "CLIENT", "SERVICE_PROVIDER", "SERVICE", "DATE_TIME", "PRICE", "DEPOSIT", "PHONE", "NOTE", "ADDRESS", "EMAIL", "CITY" ),
array( wp_specialchars_decode(get_option('blogname'), ENT_QUOTES), $user, $worker, $service, mysql2date( $this->datetime_format, $datetime ), $price, $deposit, $phone, $note, $address, $email, $city ),
$text
);
*/
$balance = ! empty( $price ) && ! empty( $deposit )
? (float) $price - (float) $deposit
: ( ! empty( $price ) ? $price : 0.0)
;
$replacement = array(
'SITE_NAME' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
'CLIENT' => $user,
'SERVICE_PROVIDER' => $worker,
'SERVICE' => preg_replace( '/\$(\d)/', '\\\$$1', $service ),
'DATE_TIME' => mysql2date( $this->datetime_format, $datetime ),
'PRICE' => $price,
'DEPOSIT' => $deposit,
'BALANCE' => $balance,
'PHONE' => $phone,
'NOTE' => $note,
'ADDRESS' => $address,
'EMAIL' => $email,
'CITY' => $city,
);
foreach ( $replacement as $macro => $repl ) {
$text = preg_replace( '/\b' . preg_quote( $macro, '/' ) . '\b/U', $repl, $text );
}
return $text;
}
/**
* Email message headers
*/
function message_headers() {
$admin_email = $this->get_admin_email();
$blogname = strip_tags( wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) );
$content_type = apply_filters( 'app-emails-content_type', 'text/plain' );
if ( ! (defined( 'APP_EMAIL_DROP_LEGACY_HEADERS' ) && APP_EMAIL_DROP_LEGACY_HEADERS) ) {
$message_headers = "MIME-Version: 1.0\n" . "From: {$blogname}" . " <{$admin_email}>\n" . "Content-Type: {$content_type}; charset=\"" . get_option( 'blog_charset' ) . "\"\n";
} else {
$message_headers = "MIME-Version: 1.0\n" .
"Content-Type: {$content_type}; charset=\"" . get_option( 'blog_charset' ) . "\"\n"
;
add_filter( 'wp_mail_from', create_function( '', "return '{$admin_email}';" ) );
add_filter( 'wp_mail_from_name', create_function( '', "return '{$blogname}';" ) );
}
// Modify message headers
$message_headers = apply_filters( 'app_message_headers', $message_headers );
return $message_headers;
}
/**
* Remove an appointment if not paid or expired
* Clear expired appointments.
* Change status to completed if they are confirmed or paid
* Change status to removed if they are pending or reserved
*/
function remove_appointments() {
$process_expired = apply_filters( 'app-auto_cleanup-process_expired', true );
if ( ! $process_expired ) {
return;
}
$options = appointments_get_options();
$clear_secs = 0;
if ( isset( $options['clear_time'] ) && $options['clear_time'] > 0 ) {
$clear_secs = $options['clear_time'] * 60;
}
$expireds = appointments_get_expired_appointments( $clear_secs );
if ( $expireds && $process_expired ) {
foreach ( $expireds as $expired ) {
if ( 'pending' == $expired->status || 'reserved' == $expired->status ) {
if ( 'reserved' == $expired->status ) {
if ( 'reserved' == $expired->status && strtotime( $expired->end ) > current_time( 'timestamp' ) ) {
$new_status = $expired->status; // Don't shift the GCal apps until they actually expire (end time in past)
} else {
$new_status = 'completed';
}
} else {
// Pending
$new_status = 'removed';
}
} else if ( 'confirmed' == $expired->status || 'paid' == $expired->status ) {
$new_status = 'completed';
} else {
$new_status = $expired->status; // Do nothing ??
}
if ( appointments_update_appointment_status( $expired->ID, $new_status ) ) {
do_action( 'app_remove_expired', $expired, $new_status );
}
}
}
update_option( 'app_last_update', time() );
// Appointment status probably changed, so clear cache.
// Anyway it is good to clear the cache in certain intervals.
// This can be removed for pages with very heavy visitor traffic, but little appointments
appointments_clear_cache();
}
/**
* Replace CANCEL placeholder with its link
* Removed due to security issues
*/
function add_cancel_link( $text, $app_id ) {
// Removed due to security issues
return str_replace( 'CANCEL', appointments_get_cancel_link_url( $app_id ), $text );
}
/*******************************
* Methods for Admin
********************************
*/
/**
* Deletes a worker's database records in case he is deleted
*
* @deprecated since 2.1
*
* @since 1.0.4
*/
function delete_user( $ID ) {
_deprecated_function( __FUNCTION__, '2.1', 'appointments_delete_worker' );
appointments_delete_worker( $ID );
}
/**
* Removes a worker's database records in case he is removed from that blog
* @param ID: user ID
* @param blog_id: ID of the blog that user has been removed from
* @since 1.2.3
*/
function remove_user_from_blog( $ID, $blog_id ) {
switch_to_blog( $blog_id );
appointments_delete_worker( $ID );
restore_current_blog();
}
/**
* Create a working hour form
* Worker can be forced.
* @param status: Open (working hours) or close (break hours)
*/
function working_hour_form( $status = 'open' ) {
$path = appointments_get_view_path( 'form-working-hours' );
if ( is_file( $path ) ) {
include $path;
}
}
/**
* @internal
* @param $name
* @param $min_secs
* @param string $selected
*
* @return string
*/
public function _time_selector( $name, $min_secs, $selected = '' ) {
ob_start();
?>
date_format );
if ( '' == trim( $check ) ) {
return $this->date_format; }
// Return a default safe format
return 'F j Y';
}
/**
* Modify a date if it is non US
* Change d/m/y to d-m-y so that strtotime can behave correctly
* Also change local dates to m/d/y format
* @return string
*
* @deprecated This function is deprecated and it will dissapear in following versions but in order
* to keep backwards compatibility, is not really marked as deprecated cause can be used in another functions
* see appointments_insert_appointment() and appointments_update_appointment()
*
* @since 1.0.4.2
*/
function to_us( $date ) {
// Find the real format we are using
$date_format = $this->safe_date_format();
$date_arr = explode( '/', $date_format );
if ( isset( $date_arr[0] ) && isset( $date_arr[1] ) && 'd/m' == $date_arr[0] .'/'. $date_arr[1] ) {
return str_replace( '/', '-', $date ); }
// Already US format
if ( isset( $date_arr[0] ) && isset( $date_arr[1] ) && 'm/d' == $date_arr[0] .'/'. $date_arr[1] ) {
return $date; }
global $wp_locale;
if ( ! is_object( $wp_locale ) || empty( $wp_locale->month ) ) {
$this->locale_error = true;
return $date;
}
$datepick_local_months = false;
$datepick_abb_local_months = false;
$months = array(
'January' => '01',
'February' => '02',
'March' => '03',
'April' => '04',
'May' => '05',
'June' => '06',
'July' => '07',
'August' => '08',
'September' => '09',
'October' => '10',
'November' => '11',
'December' => '12',
);
// A special check where locale is set, but language files are not loaded
if ( strpos( $date_format, 'F' ) !== false || strpos( $date_format, 'M' ) !== false ) {
$n = 0;
$k = 0;
foreach ( $months as $month_name => $month_no ) {
$month_name_local = $wp_locale->get_month( $month_no );
$month_name_abb_local = $wp_locale->get_month_abbrev( $month_name_local );
if ( $month_name_local == $month_name ) {
$n++; }
// Also check if any month will give a 1970 result
if ( '1970-01-01' == date( 'Y-m-d', strtotime( $month_name . ' 1 2012' ) ) ) {
$this->locale_error = true;
return $date;
}
// Also check translation of datepick
if ( strpos( $date_format, 'F' ) !== false ) {
if ( $month_name_local != trim( $datepick_local_months[ $k ] ) ) {
$this->locale_error = true;
return $date;
}
}
if ( strpos( $date_format, 'M' ) !== false ) {
// Also check translation of datepick for short month names
if ( $month_name_abb_local != trim( $datepick_abb_local_months[ $k ] ) ) {
$this->locale_error = true;
return $date;
}
}
$k++;
}
if ( $n > 11 ) {
// This means we shall use English
$this->locale_error = true;
return $date;
}
}
// Check if F (long month name) is set
if ( strpos( $date_format, 'F' ) !== false ) {
foreach ( $months as $month_name => $month_no ) {
$month_name_local = $wp_locale->get_month( $month_no );
if ( strpos( $date, $month_name_local ) !== false ) {
return date( 'm/d/y', strtotime( str_replace( $month_name_local, $month_name, $date ) ) ); }
}
}
if ( strpos( $date_format, 'M' ) !== false ) {
// Check if M (short month name) is set
foreach ( $months as $month_name => $month_no ) {
$month_name_local = $wp_locale->get_month( $month_no );
$month_name_abb_local = $wp_locale->get_month_abbrev( $month_name_local );
if ( strpos( $date, $month_name_abb_local ) !== false ) {
return date( 'm/d/y', strtotime( str_replace( $month_name_abb_local, $month_name, $date ) ) ); }
}
}
$this->locale_error = true;
return $date;
}
}
}
define( 'APP_PLUGIN_DIR', dirname( __FILE__ ) );
define( 'APP_ADMIN_PLUGIN_DIR', trailingslashit( dirname( __FILE__ ) ) . 'admin' );
define( 'APP_PLUGIN_FILE', __FILE__ );
require_once APP_PLUGIN_DIR . '/includes/default_filters.php';
require_once APP_PLUGIN_DIR . '/includes/class_app_install.php';
require_once APP_PLUGIN_DIR . '/includes/class_app_timed_abstractions.php';
require_once APP_PLUGIN_DIR . '/includes/class_app_roles.php';
require_once APP_PLUGIN_DIR . '/includes/class_app_codec.php';
require_once APP_PLUGIN_DIR . '/includes/shortcodes/abstract-app-shortcode.php';
require_once APP_PLUGIN_DIR . '/includes/shortcodes.php';
App_Installer::serve();
App_Shortcodes::serve();
global $appointments;
$appointments = new Appointments();
// Load addons
include_once( 'includes/class-app-addon.php' );
include_once( 'includes/class-app-addons-loader.php' );
if ( ! defined( 'APP_PLUGIN_ADDONS_DIR' ) ) {
define( 'APP_PLUGIN_ADDONS_DIR', APP_PLUGIN_DIR . '/includes/addons' );
}
$appointments->addons_loader = Appointments_Addons_Loader::get_instance();
$appointments->addons_loader->load_active_addons();
if ( is_admin() ) {
require_once APP_PLUGIN_DIR . '/includes/class-app-tutorial.php';
App_Tutorial::serve();
require_once APP_PLUGIN_DIR . '/includes/support/class_app_admin_help.php';
App_AdminHelp::serve();
// Setup dashboard notices
if ( file_exists( APP_PLUGIN_DIR . '/includes/external/wpmudev-dash/wpmudev-dash-notification.php' ) && _appointments_is_pro() ) {
global $wpmudev_notices;
if ( ! is_array( $wpmudev_notices ) ) { $wpmudev_notices = array(); }
$wpmudev_notices[] = array(
'id' => 679841,
'name' => 'Appointments+',
'screens' => array(
'appointments_page_app_settings',
'appointments_page_app_shortcodes',
'appointments_page_app_faq',
),
);
require_once APP_PLUGIN_DIR . '/includes/external/wpmudev-dash/wpmudev-dash-notification.php';
}
// End dash bootstrap
}
/**
* Find blogs and uninstall tables for each of them
* @since 1.0.2
* @until 1.4.1
*/
if ( ! function_exists( 'wpmudev_appointments_uninstall' ) ) {
function wpmudev_appointments_uninstall() {
do_action( 'app-core-doing_it_wrong', __FUNCTION__ ); }
}
if ( ! function_exists( '_wpmudev_appointments_uninstall' ) ) {
function _wpmudev_appointments_uninstall() {
do_action( 'app-core-doing_it_wrong', __FUNCTION__ ); }
function wpmudev_appointments_rmdir( $dir ) {
do_action( 'app-core-doing_it_wrong', __FUNCTION__ ); }
}
function appointments_activate() {
$installer = new App_Installer();
$installer->install();
}
function appointments_uninstall() {
App_Installer::uninstall();
}
function appointments_plugin_url() {
global $appointments;
return trailingslashit( $appointments->plugin_url );
}
function appointments_plugin_dir() {
return trailingslashit( plugin_dir_path( __FILE__ ) );
}
function appointments() {
global $appointments;
return $appointments;
}