806 lines
26 KiB
PHP
806 lines
26 KiB
PHP
<?php
|
||
/**
|
||
* Simply Schedule Availabilities Availabilities Model.
|
||
*
|
||
* @since 0.0.3
|
||
* @package Simply_Schedule_Availabilities
|
||
*/
|
||
use League\Period\Period;
|
||
|
||
/**
|
||
* Simply Schedule Availabilities Availabilities Model.
|
||
*
|
||
* @since 0.0.3
|
||
*/
|
||
class SSA_Availability_Model extends SSA_Db_Model {
|
||
protected $slug = 'availability';
|
||
protected $pluralized_slug = 'availability';
|
||
protected $version = '1.9.3';
|
||
|
||
/**
|
||
* Parent plugin class.
|
||
*
|
||
* @since 0.0.2
|
||
*
|
||
* @var Simply_Schedule_Availabilities
|
||
*/
|
||
protected $plugin = null;
|
||
|
||
/**
|
||
* Constructor.
|
||
*
|
||
* @since 0.0.2
|
||
*
|
||
* @param Simply_Schedule_Availabilities $plugin Main plugin object.
|
||
*/
|
||
public function __construct( $plugin ) {
|
||
// $this->version = $this->version.'.'.time(); // dev mode
|
||
parent::__construct( $plugin );
|
||
|
||
$this->hooks();
|
||
}
|
||
|
||
/**
|
||
* Initiate our hooks.
|
||
*
|
||
* @since 0.0.2
|
||
*/
|
||
public function hooks() {
|
||
|
||
}
|
||
|
||
public function belongs_to() {
|
||
return array(
|
||
'AppointmentType' => array(
|
||
'model' => $this->plugin->appointment_type_model,
|
||
'foreign_key' => 'appointment_type_id',
|
||
),
|
||
);
|
||
}
|
||
|
||
protected $schema = array(
|
||
'start_date' => array(
|
||
'field' => 'start_date',
|
||
'label' => 'Start Date',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'datetime',
|
||
'mysql_length' => '',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => true,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'end_date' => array(
|
||
'field' => 'end_date',
|
||
'label' => 'End Date',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'datetime',
|
||
'mysql_length' => '',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => true,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'capacity_available' => array(
|
||
'field' => 'capacity_available',
|
||
'label' => 'Capacity Available',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'MEDIUMINT',
|
||
'mysql_length' => 6,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'capacity_reserved' => array(
|
||
'field' => 'capacity_reserved',
|
||
'label' => 'Capacity Reserved',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'MEDIUMINT',
|
||
'mysql_length' => 6,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'buffer_available' => array(
|
||
'field' => 'buffer_available',
|
||
'label' => 'Buffer Available',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'MEDIUMINT',
|
||
'mysql_length' => 6,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'buffer_reserved' => array(
|
||
'field' => 'buffer_reserved',
|
||
'label' => 'Buffer Reserved',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'MEDIUMINT',
|
||
'mysql_length' => 6,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'capacity_meta' => array(
|
||
'field' => 'capacity_meta',
|
||
'label' => 'Capacity Meta',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'TEXT',
|
||
'mysql_length' => false,
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => true,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
'encoder' => 'json',
|
||
),
|
||
'appointment_type_id' => array(
|
||
'field' => 'appointment_type_id',
|
||
'label' => 'Appointment Type ID',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'BIGINT',
|
||
'mysql_length' => 20,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'staff_id' => array(
|
||
'field' => 'staff_id',
|
||
'label' => 'Staff Id',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'BIGINT',
|
||
'mysql_length' => 11,
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'resource_group_id' => array(
|
||
'field' => 'resource_group_id',
|
||
'label' => 'Resource Group Id',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'BIGINT',
|
||
'mysql_length' => 11,
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'type' => array(
|
||
'field' => 'type',
|
||
'label' => 'Type',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'VARCHAR',
|
||
'mysql_length' => '16',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'subtype' => array(
|
||
'field' => 'subtype',
|
||
'label' => 'Subtype',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'VARCHAR',
|
||
'mysql_length' => '16',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'description' => array(
|
||
'field' => 'description',
|
||
'label' => 'Description',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'TEXT',
|
||
'mysql_length' => '',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'status' => array(
|
||
'field' => 'status',
|
||
'label' => 'Status',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'VARCHAR',
|
||
'mysql_length' => 250,
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'is_all_day' => array(
|
||
'field' => 'is_all_day',
|
||
'label' => 'Is All Day Event?',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'VARCHAR',
|
||
'mysql_length' => 250,
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'cache_key' => array(
|
||
'field' => 'cache_key',
|
||
'label' => 'Cache Key',
|
||
'default_value' => false,
|
||
'format' => '%d',
|
||
'mysql_type' => 'BIGINT',
|
||
'mysql_length' => 11,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'cache_args_hash' => array(
|
||
'field' => 'cache_args_hash',
|
||
'label' => 'Args Hash',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'INT',
|
||
'mysql_length' => 10,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'date_created' => array(
|
||
'field' => 'date_created',
|
||
'label' => 'Date Created',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'datetime',
|
||
'mysql_length' => '',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'date_modified' => array(
|
||
'field' => 'date_modified',
|
||
'label' => 'Date Modified',
|
||
'default_value' => false,
|
||
'format' => '%s',
|
||
'mysql_type' => 'datetime',
|
||
'mysql_length' => '',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'availability_type' => array(
|
||
'field' => 'availability_type',
|
||
'label' => 'Availability Type',
|
||
'default_value' => false, // 'busy', 'buffer', 'free',
|
||
'format' => '%s',
|
||
'mysql_type' => 'VARCHAR',
|
||
'mysql_length' => '8',
|
||
'mysql_unsigned' => false,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'is_available' => array(
|
||
'field' => 'is_available',
|
||
'label' => 'Is Available',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'TINYINT',
|
||
'mysql_length' => 1,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
'availability_score' => array(
|
||
'field' => 'availability_score',
|
||
'label' => 'Availability Score',
|
||
'default_value' => 0,
|
||
'format' => '%d',
|
||
'mysql_type' => 'INT',
|
||
'mysql_length' => 3,
|
||
'mysql_unsigned' => true,
|
||
'mysql_allow_null' => false,
|
||
'mysql_extra' => '',
|
||
'cache_key' => false,
|
||
),
|
||
);
|
||
|
||
public $indexes = array(
|
||
'appointment_type_id' => [ 'appointment_type_id' ],
|
||
'is_available' => [ 'is_available' ],
|
||
'start_date' => [ 'start_date' ],
|
||
'end_date' => [ 'end_date' ],
|
||
'type' => [ 'type' ],
|
||
'subtype' => [ 'subtype' ],
|
||
'cache_key' => [ 'cache_key' ],
|
||
'cache_args_hash' => [ 'cache_args_hash' ],
|
||
'date_created' => [ 'date_created' ],
|
||
'cache_query' => [ 'cache_args_hash', 'start_date', 'end_date', 'cache_key' ],
|
||
);
|
||
|
||
public function filter_where_conditions( $where, $args ) {
|
||
global $wpdb;
|
||
if ( !empty( $args['appointment_type_id'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND appointment_type_id=%d', sanitize_text_field( $args['appointment_type_id'] ) );
|
||
}
|
||
|
||
if ( !empty( $args['resource_group_id'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND resource_group_id=%d', sanitize_text_field( $args['resource_group_id'] ) );
|
||
}
|
||
|
||
if ( !empty( $args['staff_id'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND staff_id=%d', sanitize_text_field( $args['staff_id'] ) );
|
||
}
|
||
|
||
if ( isset( $args['is_available'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND is_available=%d', sanitize_text_field( $args['is_available'] ) );
|
||
}
|
||
|
||
if ( isset( $args['type'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND type=%s', sanitize_text_field( $args['type'] ) );
|
||
}
|
||
|
||
if ( isset( $args['subtype'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND subtype=%s', sanitize_text_field( $args['subtype'] ) );
|
||
}
|
||
|
||
if ( isset( $args['cache_args_hash'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND cache_args_hash=%d', sanitize_text_field( $args['cache_args_hash'] ) );
|
||
}
|
||
|
||
if ( isset( $args['cache_key'] ) ) {
|
||
$where .= $wpdb->prepare( ' AND cache_key=%d', sanitize_text_field( $args['cache_key'] ) );
|
||
}
|
||
|
||
if ( isset( $args['intersects_period'] ) ) {
|
||
if ( $args['intersects_period'] instanceof Period ) {
|
||
$start_date_string = $args['intersects_period']->getStartDate()->format( 'Y-m-d H:i:s' );
|
||
$end_date_string = $args['intersects_period']->getEndDate()->format( 'Y-m-d H:i:s' );
|
||
|
||
// it should END in the queried period
|
||
// OR
|
||
// it should START in the queried period
|
||
// OR
|
||
// it should CONTAIN the queried period
|
||
$where .= " AND (
|
||
(end_date >= '{$start_date_string}' AND end_date <= '{$end_date_string}' )
|
||
OR
|
||
(start_date <= '{$end_date_string}' AND start_date >= '{$start_date_string}' )
|
||
OR
|
||
(start_date <= '{$start_date_string}' AND end_date >= '{$end_date_string}' )
|
||
)";
|
||
}
|
||
}
|
||
|
||
return $where;
|
||
}
|
||
|
||
public function update_rows( $availability_period_rows, $args = array() ) {
|
||
$args = array_merge( array(
|
||
'cache_key' => time(),
|
||
'type' => '',
|
||
'subtype' => '',
|
||
), $args );
|
||
|
||
$execution_datetime = gmdate( 'Y-m-d H:i:s' );
|
||
global $wpdb;
|
||
|
||
foreach ($availability_period_rows as $key => $availability_period_row) {
|
||
$availability_period_row['cache_key'] = $args['cache_key'];
|
||
$response = $this->insert( $availability_period_row );
|
||
}
|
||
|
||
$query = "DELETE FROM {$this->get_table_name()} WHERE 1=1";
|
||
$query = $wpdb->prepare( $query .= " AND cache_key < %d", $args['cache_key'] );
|
||
$query = $wpdb->prepare( $query .= " AND start_date >= %s", $args['start_date_min'] );
|
||
$query = $wpdb->prepare( $query .= " AND start_date <= %s", $args['start_date_max'] );
|
||
$query = $wpdb->prepare( $query .= " AND type = %s", $args['type'] );
|
||
$query = $wpdb->prepare( $query .= " AND subtype = %s", $args['subtype'] );
|
||
$result = $wpdb->query( $query );
|
||
|
||
}
|
||
|
||
public function bulk_delete( $args=array() ) {
|
||
return $this->db_bulk_delete( $args );
|
||
}
|
||
|
||
public function register_routes() {
|
||
// override register_routes() because we don't want any default behavior reading/writing to this database table
|
||
$namespace = $this->api_namespace.'/v' . $this->api_version;
|
||
$base = $this->get_api_base();
|
||
|
||
register_rest_route( $namespace, '/' . $base, array(
|
||
array(
|
||
'methods' => WP_REST_Server::READABLE,
|
||
'callback' => array( $this, 'availability_query' ),
|
||
// 'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||
'permission_callback' => '__return_true',
|
||
'args' => array(
|
||
|
||
),
|
||
),
|
||
) );
|
||
|
||
register_rest_route( $namespace, '/' . $base . '/troubleshoot', array(
|
||
array(
|
||
'methods' => WP_REST_Server::READABLE,
|
||
'callback' => array( $this, 'troubleshoot' ),
|
||
// 'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||
'permission_callback' => '__return_true',
|
||
'args' => array(
|
||
'start_date' => array(
|
||
'required' => true,
|
||
),
|
||
'end_date' => array(
|
||
'required' => false,
|
||
),
|
||
'appointment_type_id' => array(
|
||
'required' => true,
|
||
),
|
||
),
|
||
),
|
||
) );
|
||
}
|
||
|
||
public function availability_query( $request ) {
|
||
$params = $request->get_params();
|
||
$appointment_type = SSA_Appointment_Type_Object::instance( (int) $params['appointment_type_id'] );
|
||
try {
|
||
$appointment_type_title = $appointment_type->title;
|
||
} catch ( Exception $e ) {
|
||
return 'appointment_type_not_found';
|
||
}
|
||
|
||
$start_date = ssa_datetime( $params['start_date'] ); // TODO: handle invalid dates?
|
||
|
||
if ( ! empty( $params['end_date'] ) ) {
|
||
$end_date = ssa_datetime( $params['end_date'] );
|
||
$period = new Period( $start_date, $end_date );
|
||
} else {
|
||
$period = new Period( $start_date, $start_date->add( $appointment_type->get_duration_interval() ) );
|
||
}
|
||
|
||
$availability_query = new SSA_Availability_Query(
|
||
$appointment_type,
|
||
$period,
|
||
array(
|
||
'cache_level_read' => 1,
|
||
'cache_level_write' => 1,
|
||
)
|
||
);
|
||
$bookable_start_datetime_strings = $availability_query->get_bookable_appointment_start_datetime_strings();
|
||
if ( ! empty( $bookable_start_datetime_strings['0']['start_date'] ) ) {
|
||
$bookable_start_datetime_strings = wp_list_pluck( $bookable_start_datetime_strings, 'start_date' );
|
||
}
|
||
|
||
return $bookable_start_datetime_strings;
|
||
}
|
||
|
||
public function troubleshoot( $request ) {
|
||
$params = $request->get_params();
|
||
$appointment_type = SSA_Appointment_Type_Object::instance( (int) $params['appointment_type_id'] );
|
||
try {
|
||
$appointment_type_title = $appointment_type->title;
|
||
} catch ( Exception $e ) {
|
||
return 'appointment_type_not_found';
|
||
}
|
||
|
||
$start_date = ssa_datetime( $params['start_date'] ); // TODO: handle invalid dates?
|
||
|
||
if ( ssa_datetime() > $start_date ) {
|
||
return array(
|
||
'is_bookable' => false,
|
||
'reasons' => array(
|
||
array(
|
||
'reason' => 'start_date_past',
|
||
'reason_title' => __( 'That time has already passed', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date you selected is in the past, so a customer can\'t book it – without a time machine :)', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
)
|
||
)
|
||
);
|
||
}
|
||
|
||
if ( ! empty( $params['end_date'] ) ) {
|
||
$end_date = ssa_datetime( $params['end_date'] );
|
||
$period = new Period( $start_date, $end_date );
|
||
} else {
|
||
$period = new Period( $start_date, $start_date->add( $appointment_type->get_duration_interval() ) );
|
||
}
|
||
|
||
$appointment = SSA_Appointment_Factory::create( $appointment_type, array(
|
||
'id' => 0,
|
||
'start_date' => $start_date->format( 'Y-m-d H:i:s' ),
|
||
) );
|
||
$public_query = new SSA_Availability_Query(
|
||
$appointment_type,
|
||
$period,
|
||
array(
|
||
'cache_level_read' => false,
|
||
'cache_level_write' => false,
|
||
)
|
||
);
|
||
|
||
if ( $public_query->is_prospective_appointment_bookable( $appointment ) ) {
|
||
return array(
|
||
'is_bookable' => true,
|
||
'reasons' => array(
|
||
array(
|
||
'is_bookable' => true,
|
||
'reason' => 'available',
|
||
'reason_title' => __( 'Time slot is available', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is available to be booked.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
)
|
||
)
|
||
);
|
||
}
|
||
|
||
// the time isn't bookable, so let's find out why
|
||
$case = new SSA_Availability_Detective_Case(
|
||
$appointment_type,
|
||
$period,
|
||
$appointment
|
||
);
|
||
$case = $case->investigate();
|
||
if ( ! empty( $case->culprits ) ) {
|
||
$response = array(
|
||
'is_bookable' => false,
|
||
'cleared' => $case->cleared,
|
||
'culprits' => $case->culprits,
|
||
'reasons' => array()
|
||
);
|
||
|
||
// Show Appointment Type Reason
|
||
$found_appt_type_reason = false;
|
||
if ( in_array( 'appointment_type.separate_appointment_type_availability', $case->culprits ) ) {
|
||
$found_appt_type_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments',
|
||
'reason_title' => __( 'Separate Appointment Type Availability', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of a booked appointment in another appointment type.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'appointment_type.min_booking_notice', $case->culprits ) ) {
|
||
$found_appt_type_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointment_type.min_booking_notice',
|
||
'reason_title' => __( 'Minimum booking notice', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is sooner than your minimum booking notice.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'appointment_type.max_booking_notice', $case->culprits ) ) {
|
||
$found_appt_type_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointment_type.max_booking_notice',
|
||
'reason_title' => __( 'Advance', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is later than your maximum advance booking notice.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'appointment_type.max_per_day', $case->culprits ) ) {
|
||
$found_appt_type_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointment_type.max_per_day',
|
||
'reason_title' => __( 'Maximum # Per Day', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is on a date that has already reached your setting for the maximum number of appointments per day.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'appointment_type.availability_window', $case->culprits ) ) {
|
||
$found_appt_type_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointment_type.availability_window',
|
||
'reason_title' => __( 'Availability Window', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is outside the availability window you set for this appointment type.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
// Show Appointments Reason
|
||
$found_appointment_reason = false;
|
||
$most_wanted_culprits = array('appointment_type.appointments.pending_payment', 'appointment_type.appointments.pending_form');
|
||
if( count( array_intersect( $case->culprits, $most_wanted_culprits ) ) > 0 ) {
|
||
$case->culprits = array_filter($case->culprits, static function ($element) {
|
||
return $element !== "appointment_type.appointments.booked";
|
||
});
|
||
}
|
||
|
||
if( !$found_appt_type_reason ) {
|
||
// JOIN Pending Form/Payment
|
||
if ( in_array( 'appointment_type.appointments.pending_form', $case->culprits ) && in_array( 'appointment_type.appointments.pending_payment', $case->culprits )) {
|
||
$found_appointment_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments.pending_form',
|
||
'reason_title' => __( 'Pending Appointments', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of pending appointments.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
$case->culprits = array_filter($case->culprits, static function ($element) {
|
||
return $element !== "appointment_type.appointments.pending_form" && $element !== "appointment_type.appointments.pending_payment";
|
||
});
|
||
}
|
||
|
||
// Booked
|
||
if ( in_array( 'appointment_type.appointments.booked', $case->culprits ) ) {
|
||
$found_appointment_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments',
|
||
'reason_title' => __( 'Booked Appointment', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of a booked appointment.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
// Pending Form
|
||
if ( in_array( 'appointment_type.appointments.pending_form', $case->culprits ) ) {
|
||
$found_appointment_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments.pending_form',
|
||
'reason_title' => __( 'Pending Form Submissions', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of pending form submissions.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
// Pending Payment
|
||
if ( in_array( 'appointment_type.appointments.pending_payment', $case->culprits ) ) {
|
||
$found_appointment_reason = true;
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments.pending_payment',
|
||
'reason_title' => __( 'Pending Payments', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of pending payments (a customer booked this time but has not completed the payment yet).', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
// Appointments
|
||
if ( !$found_appointment_reason && in_array( 'appointment_type.appointments', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointments',
|
||
'reason_title' => __( 'Appointment', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of appointments.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
// Appointment Type
|
||
if ( !$found_appointment_reason && in_array( 'appointment_type', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'appointment_type',
|
||
'reason_title' => __( 'Appointment Type settings', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'Please contact support to get help looking at your appointment type settings.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( in_array( 'staff', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'staff_appointments',
|
||
'reason_title' => __( 'Team Member', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because there are no team members available.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'resources', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'resources_appointments',
|
||
'reason_title' => __( 'Resources', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because there are no resources available.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'blackout_dates', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'blackout_dates',
|
||
'reason_title' => __( 'Blackout Dates', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is on a global blackout date.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
if ( in_array( 'google_calendar', $case->culprits ) ) {
|
||
$response['reasons'][] = array(
|
||
'reason' => 'google_calendar',
|
||
'reason_title' => __( 'Google Calendar', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'The date and time you selected is unavailable because of an event on an excluded Google Calendar.', 'simply-schedule-appointments' ),
|
||
'help_center_url' => '',
|
||
'ssa_path' => '',
|
||
);
|
||
}
|
||
|
||
return $response;
|
||
}
|
||
|
||
// we couldn't detect a reason, so let's recommend contacting support
|
||
return array(
|
||
'is_bookable' => false,
|
||
'reasons' => array(
|
||
array(
|
||
'reason' => 'unknown',
|
||
'reason_title' => __( 'Please contact support', 'simply-schedule-appointments' ),
|
||
'reason_description' => __( 'This time is unavailable, but we are unable to detect the reason automatically. Please contact support.', 'simply-schedule-appointments' ),
|
||
'ssa_path' => '/ssa/support/help',
|
||
)
|
||
)
|
||
);
|
||
}
|
||
|
||
public function investigate( $case, SSA_Appointment_Type_Object $appointment_type, Period $period, SSA_Appointment_Object $appointment ) {
|
||
// try required but not cleared
|
||
// if no uncleared requirements, try cleared + next suspect, one by one
|
||
// try combinations of suspects (google calendar + staff)
|
||
if ( empty( $case['suspects'] ) ) {
|
||
return $case;
|
||
}
|
||
|
||
$suspect_under_interrogation = array_shift( $case['suspects'] );
|
||
$solo_interrogation_args = array_merge(
|
||
$case['required'],
|
||
array( $suspect_under_interrogation )
|
||
);
|
||
|
||
$solo_interrogation_query = new SSA_Availability_Query(
|
||
$appointment_type,
|
||
$period,
|
||
array_merge( $solo_interrogation_args, array(
|
||
'cache_level_read' => false,
|
||
'cache_level_write' => false,
|
||
) )
|
||
);
|
||
|
||
if ( $solo_interrogation_query->is_prospective_appointment_bookable( $appointment ) ) {
|
||
$case['cleared'][] = $suspect_under_interrogation;
|
||
} else {
|
||
$case['culprits'][] = $suspect_under_interrogation;
|
||
}
|
||
|
||
return $this->investigate( $case, $appointment_type, $period, $appointment );
|
||
}
|
||
}
|