<?php /** * @property OsCustomerModel $customer * @property OsAgentModel $agent * @property OsServiceModel $service * @property OsLocationModel $location */ class OsBookingModel extends OsModel { public $id, $booking_code, $service_id, $customer_id, $agent_id, $location_id, $recurrence_id, $buffer_before = 0, $buffer_after = 0, $status, $start_date, $end_date, $start_time, $end_time, $start_datetime_utc, $end_datetime_utc, $duration, $total_attendees = 1, $total_attendees_sum = 1, $total_customers = 1, $cart_item_id = null, $order_item_id, $server_timezone, $customer_timezone, $meta_class = 'OsBookingMetaModel', $keys_to_manage = [], $generate_recurrent_sequence = [], $updated_at, $created_at; function __construct( $id = false ) { parent::__construct(); $this->table_name = LATEPOINT_TABLE_BOOKINGS; $this->nice_names = array( 'service_id' => __( 'Service', 'latepoint' ), 'agent_id' => __( 'Agent', 'latepoint' ) ); if ( $id ) { $this->load_by_id( $id ); } } /** * @return mixed|void * * Returns full amount to charge in database format 1999.0000 * */ public function full_amount_to_charge() { return OsBookingHelper::calculate_full_amount_for_booking( $this ); } /** * @return mixed|void * * Returns deposit amount to charge in database format 1999.0000 * */ public function deposit_amount_to_charge() { return OsBookingHelper::calculate_deposit_amount_to_charge( $this ); } public function get_key_to_manage_for(string $for): string { if($this->is_new_record()) return ''; if(!empty($this->keys_to_manage[$for])) return $this->keys_to_manage[$for]; $key = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_' . $for, $this->id ); if ( empty( $key ) ) { $key = OsUtilHelper::generate_key_to_manage(); OsMetaHelper::save_booking_meta_by_key( 'key_to_manage_for_' . $for, $key, $this->id ); } $this->keys_to_manage[$for] = $key; return $key; } public function manage_by_key_url(string $for = 'customer'): string{ return OsBookingHelper::generate_direct_manage_booking_url($this, $for); } public function get_service_name_for_summary() { $service_name = $this->service_id ? $this->service->name : ''; /** * Get service name to be displayed on a booking summary * * @param {string} $service_name Service name to be filtered * @param {OsBookingModel} $booking Booking model which service name is requested * * @returns {string} Filtered service name * @since 5.0.0 * @hook latepoint_booking_get_service_name_for_summary * */ return apply_filters( 'latepoint_booking_get_service_name_for_summary', $service_name, $this ); } public function get_order() { if ( $this->order_item_id ) { if ( ! isset( $this->order_item ) || ( $this->order_item->id != $this->order_item_id ) ) { $this->order_item = new OsOrderItemModel( $this->order_item_id ); if ( ! isset( $this->order ) || ( $this->order->id != $this->order_item->order_id ) ) { $this->order = new OsOrderModel( $this->order_item->order_id ); } } } else { $this->order = new OsOrderModel(); } return $this->order; } public function get_order_item( ) { if (!isset( $this->order_item )) { $this->order_item = new OsOrderItemModel( $this->order_item_id ); } return $this->order_item; } public function filter_allowed_records(): OsModel { if ( ! OsRolesHelper::are_all_records_allowed() ) { if ( ! OsRolesHelper::are_all_records_allowed( 'agent' ) ) { $this->filter_where_conditions( [ 'agent_id' => OsRolesHelper::get_allowed_records( 'agent' ) ] ); } if ( ! OsRolesHelper::are_all_records_allowed( 'location' ) ) { $this->filter_where_conditions( [ 'location_id' => OsRolesHelper::get_allowed_records( 'location' ) ] ); } if ( ! OsRolesHelper::are_all_records_allowed( 'service' ) ) { $this->filter_where_conditions( [ 'service_id' => OsRolesHelper::get_allowed_records( 'service' ) ] ); } } return $this; } public function properties_to_query(): array { return [ 'service_id' => __( 'Service', 'latepoint' ), 'agent_id' => __( 'Agent', 'latepoint' ), 'status' => __( 'Status', 'latepoint' ), 'start_datetime_utc' => __( 'Start Time', 'latepoint' ), ]; } public function generate_item_data() { return wp_json_encode( $this->generate_params_for_booking_form() ); } public function generate_params_for_booking_form() { $params = [ "id" => $this->id, "customer_id" => $this->customer_id, "agent_id" => $this->agent_id, "location_id" => $this->location_id, "service_id" => $this->service_id, "recurrence_id" => $this->recurrence_id, "start_date" => $this->start_date, "start_time" => $this->start_time, "end_date" => $this->end_date, "end_time" => $this->end_time, "status" => $this->status, "buffer_before" => $this->buffer_before, "buffer_after" => $this->buffer_after, "duration" => $this->duration, "generate_recurrent_sequence" => $this->generate_recurrent_sequence, ]; /** * Returns an array of params generated from OsBookingModel to be used in a booking form * * @param {array} $params Array of booking params * @param {OsBookingModel} $booking Instance of <code>OsBookingModel</code> that params are being generated for * * @returns {array} Filtered array of booking params * @since 5.0.0 * @hook latepoint_generated_params_for_booking_form * */ return apply_filters( 'latepoint_generated_params_for_booking_form', $params, $this ); } public function get_formatted_price(){ $order_item = new OsOrderItemModel( $this->order_item_id ); return OsMoneyHelper::format_price($order_item->get_total()); } public function generate_first_level_data_vars() : array{ $vars = [ 'id' => $this->id, 'booking_code' => $this->booking_code, 'start_datetime' => $this->format_start_date_and_time_rfc3339(), 'end_datetime' => $this->format_end_date_and_time_rfc3339(), 'service_name' => $this->service->name, 'duration' => $this->duration, 'customer_comment' => $this->order->customer_comment, 'status' => $this->status, 'start_date' => $this->format_start_date(), 'start_time' => OsTimeHelper::minutes_to_hours_and_minutes( $this->start_time ), 'timezone' => OsTimeHelper::get_wp_timezone_name(), 'agent' => $this->agent->get_data_vars(), 'created_datetime' => $this->format_created_datetime_rfc3339(), 'manage_booking_for_agent' => OsBookingHelper::generate_direct_manage_booking_url( $this, 'agent' ), 'manage_booking_for_customer' => OsBookingHelper::generate_direct_manage_booking_url( $this, 'customer' ), ]; return $vars; } public function generate_data_vars(): array { $vars = $this->get_first_level_data_vars(); $vars['customer'] = $this->customer->get_data_vars(); $vars['transactions'] = []; $vars['order'] = $this->order->get_first_level_data_vars(); $transactions = $this->order->get_transactions(); if ( $transactions ) { foreach ( $transactions as $transaction ) { $vars['transactions'][] = $transaction->get_data_vars(); } } return $vars; } public function is_ready_for_summary() { return ( $this->agent_id && $this->agent_id != LATEPOINT_ANY_AGENT && OsAgentHelper::count_agents() > 1 ) || $this->service_id; } public function is_part_of_bundle(): bool { if ( $this->order_item_id ) { $order_item = new OsOrderItemModel( $this->order_item_id ); return $order_item->is_bundle(); } return false; } public function is_upcoming(): bool { if ( empty( $this->start_datetime_utc ) ) { return false; } $start_time_utc = new OsWpDateTime( $this->start_datetime_utc, new DateTimeZone( 'UTC' ) ); $now_time_utc = new OsWpDateTime( 'now', new DateTimeZone( 'UTC' ) ); return ( $start_time_utc > $now_time_utc ); } public function set_utc_datetimes(bool $save = false) { if ( empty( $this->start_date ) || empty( $this->end_date ) || empty( $this->start_time ) || empty( $this->end_time ) ) { return; } $this->start_datetime_utc = $this->get_start_datetime('UTC')->format(LATEPOINT_DATETIME_DB_FORMAT); $this->end_datetime_utc = $this->get_end_datetime('UTC')->format(LATEPOINT_DATETIME_DB_FORMAT); if ( $save ) { $this->update_attributes(['start_datetime_utc' => $this->start_datetime_utc, 'end_datetime_utc' => $this->end_datetime_utc]); } } public function delete( $id = false ) { if ( ! $id && isset( $this->id ) ) { $id = $this->id; } $booking_metas = new OsBookingMetaModel(); $booking_metas->delete_where( [ 'object_id' => $id ] ); $process_jobs = new OsProcessJobModel(); $process_jobs->delete_where( [ 'object_id' => $id, 'object_model_type' => 'booking' ] ); return parent::delete( $id ); } public function delete_meta_by_key( $meta_key ) { if ( $this->is_new_record() ) { return true; } $meta = new OsBookingMetaModel(); return $meta->delete_by_key( $meta_key, $this->id ); } public function get_url_for_add_to_calendar_button( string $calendar_type ): string { switch ( $calendar_type ) { case 'google': $url = 'https://calendar.google.com/calendar/render'; $params = [ 'action' => 'TEMPLATE', 'text' => $this->service->name, 'dates' => $this->get_start_datetime_object( new DateTimeZone( 'UTC' ) )->format( 'Ymd\THis\Z' ) . '/' . $this->get_end_datetime_object( new DateTimeZone( 'UTC' ) )->format( 'Ymd\THis\Z' ) ]; if ( ! empty( $this->location->full_address ) ) { $params['location'] = $this->location->full_address; } break; case 'outlook': $url = 'https://outlook.office.com/calendar/0/deeplink/compose'; $params = [ 'path' => '/calendar/action/compose', 'rru' => 'addevent', 'startdt' => $this->get_start_datetime_object( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d\TH:i:s\Z' ), 'enddt' => $this->get_end_datetime_object( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d\TH:i:s\Z' ), 'subject' => $this->service->name, ]; break; } /** * Generate params for the add to calendar link * * @param {array} $params Array of parameters that will be converted into a param query * @param {string} $calendar_type Type of calendar the link is requested for * @param {OsBookingModel} $booking A booking object * @returns {array} The filtered array of appointment attributes * * @since 4.8.1 * @hook latepoint_build_add_to_calendar_link_params * */ $params = apply_filters( 'latepoint_build_add_to_calendar_link_params', $params, $calendar_type, $this ); $url = $url . '?' . http_build_query( $params ); /** * URL for the link for a button to add appointment to calendar * * @param {array} $params Array of parameters that will be converted into a param query * @param {string} $calendar_type Type of calendar the link is requested for * @param {OsBookingModel} $booking A booking object * @returns {string} The filtered url of adding appointment to calendar * * @since 4.8.1 * @hook latepoint_build_add_to_calendar_link_url * */ return apply_filters( 'latepoint_build_add_to_calendar_link_url', $url, $calendar_type, $this ); } public function get_ical_download_link( $key = false ) { return ( $key ) ? OsRouterHelper::build_admin_post_link( [ 'manage_booking_by_key', 'ical_download' ], [ 'key' => $key ] ) : OsRouterHelper::build_admin_post_link( [ 'customer_cabinet', 'ical_download' ], [ 'latepoint_booking_id' => $this->id ] ); } public function get_print_link( $key = false ) { return ( $key ) ? OsRouterHelper::build_admin_post_link( [ 'manage_booking_by_key', 'print'], [ 'key' => $key ] ) : OsRouterHelper::build_admin_post_link( [ 'customer_cabinet', 'print_booking_info' ], [ 'latepoint_booking_id' => $this->id ] ); } public function get_meta_by_key( $meta_key, $default = false ) { if ( $this->is_new_record() ) { return $default; } $meta = new OsBookingMetaModel(); return $meta->get_by_key( $meta_key, $this->id, $default ); } public function get_coupon_code( ) { $order = $this->get_order(); return $order->coupon_code; } public function get_coupon_discount( ): string { $order_item = $this->get_order_item(); $coupon_discount = $order_item->get_coupon_discount(); return $coupon_discount > 0 ? OsMoneyHelper::format_price($order_item->get_coupon_discount()) : ''; } public function save_meta_by_key( $meta_key, $meta_value ) { if ( $this->is_new_record() ) { return false; } $meta = new OsBookingMetaModel(); return $meta->save_by_key( $meta_key, $meta_value, $this->id ); } public function calculate_end_date() { if ( empty( $this->start_time ) || empty( $this->start_date ) ) { return $this->start_date; } if ( ( $this->start_time + $this->get_total_duration() ) >= ( 24 * 60 ) ) { $date_obj = new OsWpDateTime( $this->start_date ); $end_date = $date_obj->modify( '+1 day' )->format( 'Y-m-d' ); } else { $end_date = $this->start_date; } return $end_date; } public function calculate_end_time() { $end_time = (int) $this->start_time + (int) $this->get_total_duration(); // continues to next day? if ( $end_time > ( 24 * 60 ) ) { $end_time = $end_time - ( 24 * 60 ); } return $end_time; } public function calculate_end_date_and_time() { $this->end_time = $this->calculate_end_time(); $this->end_date = $this->calculate_end_date(); } public function after_data_was_set( $data ) { if ( empty( $this->end_time ) ) { $this->calculate_end_date_and_time(); } if ( empty( $this->end_date ) ) { $this->calculate_end_date(); } } public function set_buffers() { if ( $this->service_id ) { $service = new OsServiceModel( $this->service_id ); if ( $service ) { $this->buffer_before = $service->buffer_before; $this->buffer_after = $service->buffer_after; } } } public function get_total_duration( $calculate_from_start_and_end = false ) { if ( $calculate_from_start_and_end ) { if ( $this->start_date == $this->end_date ) { // same day $total_duration = $this->end_time - $this->start_time; } else { // TODO calculate how many days difference there is, if difference is more than 1 day - account for that $total_duration = 60 * 24 - $this->start_time + $this->end_time; } } else { if ( $this->duration ) { $total_duration = $this->duration; } else { $total_duration = ( $this->service_id ) ? $this->service->duration : 60; } $total_duration = apply_filters( 'latepoint_calculated_total_duration', $total_duration, $this ); } return (int) $total_duration; } public function get_nice_created_at( $include_time = true ) { $format = $include_time ? OsSettingsHelper::get_readable_date_format() . ' ' . OsSettingsHelper::get_readable_time_format() : OsSettingsHelper::get_readable_date_format(); $utc_date = date_create_from_format( LATEPOINT_DATETIME_DB_FORMAT, $this->created_at ); $wp_timezone_date = $utc_date->setTimezone(OsTimeHelper::get_wp_timezone()); return date_format( $wp_timezone_date, $format ); } public function is_bookable( array $settings = [] ): bool { $defaults = [ 'skip_customer_check' => false, 'log_errors' => true ]; $settings = OsUtilHelper::merge_default_atts( $defaults, $settings ); $customer = $this->customer_id ? new OsCustomerModel( $this->customer_id ) : false; // check if customer has to be assigned to a booking, or a guest booking is fine at this point if($settings['skip_customer_check']){ $customer_requirement_satisfied = true; }else{ $customer_requirement_satisfied = ($this->customer_id && $customer && $customer->id && ( $this->customer_id == $customer->id )); } // agent, service and customer should be set if ( $this->service_id && $this->agent_id && $customer_requirement_satisfied) { if ( $this->agent_id == LATEPOINT_ANY_AGENT && $this->location_id == LATEPOINT_ANY_LOCATION ) { // both location and agent are set to any $connections = new OsConnectorModel(); $connection_groups = $connections->select( LATEPOINT_TABLE_AGENTS_SERVICES . '.agent_id, ' . LATEPOINT_TABLE_AGENTS_SERVICES . '.location_id' ) ->where( [ 'service_id' => $this->service_id, LATEPOINT_TABLE_AGENTS . '.status' => LATEPOINT_AGENT_STATUS_ACTIVE, LATEPOINT_TABLE_LOCATIONS . '.status' => LATEPOINT_LOCATION_STATUS_ACTIVE ] ) ->join( LATEPOINT_TABLE_AGENTS, [ 'id' => LATEPOINT_TABLE_AGENTS_SERVICES . '.agent_id' ] ) ->join( LATEPOINT_TABLE_LOCATIONS, [ 'id' => LATEPOINT_TABLE_AGENTS_SERVICES . '.location_id' ] ) ->get_results( ARRAY_A ); if ( empty( $connection_groups ) ) { // no active locations and agents are connected to this service $this->add_error( 'send_to_step', __( 'Unfortunately there are no active resources that can offer selected service, please select another service.', 'latepoint' ), 'booking__service' ); return false; } else { foreach ( $connection_groups as $connection ) { $this->location_id = $connection['location_id']; $this->agent_id = OsBookingHelper::get_any_agent_for_booking_by_rule( $this ); // available agent found in this location - break the loop if ( $this->agent_id ) { break; } } if ( ! $this->agent_id ) { $this->add_error( 'send_to_step', __( 'Unfortunately the selected time slot is not available anymore, please select another timeslot.', 'latepoint' ), 'booking__datepicker' ); return false; } } } elseif ( $this->agent_id == LATEPOINT_ANY_AGENT ) { $this->agent_id = OsBookingHelper::get_any_agent_for_booking_by_rule( $this ); if ( ! $this->agent_id ) { $this->add_error( 'send_to_step', __( 'Unfortunately the selected time slot is not available anymore, please select another timeslot.', 'latepoint' ), 'booking__datepicker' ); return false; } } elseif ( $this->location_id == LATEPOINT_ANY_LOCATION ) { $this->location_id = OsBookingHelper::get_any_location_for_booking_by_rule( $this ); if ( ! $this->location_id ) { $this->add_error( 'send_to_step', __( 'Unfortunately the selected time slot is not available anymore, please select another timeslot.', 'latepoint' ), 'booking__datepicker' ); return false; } } else { // check if booking time is still available if ( ! OsBookingHelper::is_booking_request_available( \LatePoint\Misc\BookingRequest::create_from_booking_model( $this ) ) ) { // translators: %1$s is the timeslot date and time // translators: %2$s is the service name $error_message = sprintf( __( 'Unfortunately the selected time slot "%1$s" for "%2$s" is not available anymore, please select another timeslot.', 'latepoint' ), $this->get_nice_start_datetime_for_customer(), $this->service->name ); $this->add_error( 'send_to_step', $error_message, 'booking__datepicker' ); return false; } } if(!$this->validate(false, ['order_item_id', 'status', 'customer_id'])){ return false; } return true; } else { if ( ! $this->service_id ) { $this->add_error( 'missing_service', __( 'You have to select a service', 'latepoint' ) ); } if ( ! $this->agent_id ) { $this->add_error( 'missing_agent', __( 'You have to select an agent', 'latepoint' ) ); } if ( ! $this->customer_id && !$settings['skip_customer_check'] ) { $this->add_error( 'missing_customer', __( 'Customer Not Found', 'latepoint' ) ); if($settings['log_errors']){ OsDebugHelper::log( 'Customer not found', 'customer_error', print_r( $customer, true ) ); } } if ( ! $customer && !$settings['skip_customer_check'] ) { $this->add_error( 'missing_customer', __( 'You have to be logged in', 'latepoint' ) ); if($settings['log_errors']){ OsDebugHelper::log( 'Customer not logged in', 'customer_error', print_r( $customer, true ) ); } } if($settings['log_errors']){ OsDebugHelper::log( 'Error saving booking', 'booking_error', 'Agent: ' . $this->agent_id . ', Service: ' . $this->service_id . ', Booking Customer: ' . $this->customer_id ); } return false; } } public function get_nice_status() { return OsBookingHelper::get_nice_status_name( $this->status ); } public function get_latest_bookings_sorted_by_status( $args = array() ) { $args = array_merge( array( 'service_id' => false, 'customer_id' => false, 'agent_id' => false, 'location_id' => false, 'limit' => false, 'offset' => false ), $args ); $bookings = new OsBookingModel(); $query_args = array(); if ( $args['service_id'] ) { $query_args['service_id'] = $args['service_id']; } if ( $args['customer_id'] ) { $query_args['customer_id'] = $args['customer_id']; } if ( $args['agent_id'] ) { $query_args['agent_id'] = $args['agent_id']; } if ( $args['location_id'] ) { $query_args['location_id'] = $args['location_id']; } if ( $args['limit'] ) { $bookings->set_limit( $args['limit'] ); } if ( $args['offset'] ) { $bookings->set_offset( $args['offset'] ); } return $bookings->where( $query_args )->should_not_be_cancelled()->order_by( "status != '" . LATEPOINT_BOOKING_STATUS_PENDING . "' asc, start_date asc, start_time asc" )->get_results_as_models(); } public function should_not_be_cancelled() { return $this->where( [ $this->table_name . '.status !=' => LATEPOINT_BOOKING_STATUS_CANCELLED ] ); } public function should_be_cancelled() { return $this->where( [ $this->table_name . '.status' => LATEPOINT_BOOKING_STATUS_CANCELLED ] ); } public function should_be_approved() { return $this->where( [ $this->table_name . '.status' => LATEPOINT_BOOKING_STATUS_APPROVED ] ); } public function should_be_in_future() { return $this->where( [ 'OR' => [ 'start_date >' => OsTimeHelper::today_date( 'Y-m-d' ), 'AND' => [ 'start_date' => OsTimeHelper::today_date( 'Y-m-d' ), 'start_time >' => OsTimeHelper::get_current_minutes() ] ] ] ); } public function get_upcoming_bookings( $agent_id = false, $customer_id = false, $service_id = false, $location_id = false, int $limit = 3 ) { $bookings = new OsBookingModel(); $args = array( 'OR' => array( 'start_date >' => OsTimeHelper::today_date( 'Y-m-d' ), 'AND' => array( 'start_date' => OsTimeHelper::today_date( 'Y-m-d' ), 'start_time >' => OsTimeHelper::get_current_minutes() ) ) ); if ( $service_id ) { $args['service_id'] = $service_id; } if ( $customer_id ) { $args['customer_id'] = $customer_id; } if ( $agent_id ) { $args['agent_id'] = $agent_id; } if ( $location_id ) { $args['location_id'] = $location_id; } $args = OsAuthHelper::get_current_user()->clean_query_args( $args ); $allowed_statuses = OsCalendarHelper::get_booking_statuses_to_display_on_calendar(); if (empty($allowed_statuses)) { return []; } return $bookings->select( '*, count(id) as total_customers, sum(total_attendees) as total_attendees_sum' ) ->where_in('status', $allowed_statuses ) ->group_by( 'start_datetime_utc, agent_id, service_id, location_id' ) ->where( $args ) ->set_limit( $limit ) ->order_by( 'start_datetime_utc asc' ) ->get_results_as_models(); } public function get_nice_start_time_for_customer() { return $this->format_start_date_and_time( OsTimeHelper::get_time_format(), false, $this->get_customer_timezone() ); } public function get_nice_end_time_for_customer() { return $this->format_end_date_and_time( OsTimeHelper::get_time_format(), false, $this->get_customer_timezone() ); } public function get_nice_start_date_for_customer($customer_timezone = false, $hide_year = false) { if(!$customer_timezone) $customer_timezone = $this->get_customer_timezone(); return OsUtilHelper::translate_months($this->format_start_date_and_time( OsSettingsHelper::get_readable_date_format($hide_year), false, $customer_timezone )); } public function get_nice_start_datetime_for_customer($customer_timezone = false) { if(!$customer_timezone) $customer_timezone = $this->get_customer_timezone(); return OsUtilHelper::translate_months($this->format_start_date_and_time( OsSettingsHelper::get_readable_datetime_format(), false, $customer_timezone )); } public function get_start_datetime_for_customer() : OsWpDateTime{ return $this->get_start_datetime($this->get_customer_timezone_name()); } public function get_end_datetime_for_customer() : OsWpDateTime{ return $this->get_end_datetime($this->get_customer_timezone_name()); } public function get_customer_timezone() : DateTimeZone{ if(OsSettingsHelper::is_on('steps_show_timezone_selector')){ return ($this->customer_id) ? $this->customer->get_selected_timezone_obj() : OsTimeHelper::get_timezone_from_session(); }else{ return OsTimeHelper::get_wp_timezone(); } } public function get_customer_timezone_name() : string{ if(OsSettingsHelper::is_on('steps_show_timezone_selector')) { return ( $this->customer_id ) ? $this->customer->get_selected_timezone_name() : OsTimeHelper::get_timezone_name_from_session(); }else{ return OsTimeHelper::get_wp_timezone_name(); } } /** * * Returns time in WP timezone, because start_time is stored in WP timezone, do not use it for customer facing outputs * * @return string|null */ public function get_nice_start_time() { return OsTimeHelper::minutes_to_hours_and_minutes( $this->start_time ); } /** * * Returns time in WP timezone, because end_time is stored in WP timezone, do not use it for customer facing outputs * * @return string|null */ public function get_nice_end_time() { return OsTimeHelper::minutes_to_hours_and_minutes( $this->end_time ); } /** * * Returns time in WP timezone, because start_date is stored in WP timezone, do not use it for customer facing outputs * * @return string|null */ public function get_nice_end_date( $hide_year_if_current = false ) { $datetime = OsWpDateTime::os_createFromFormat( "Y-m-d", $this->end_date ); OsTimeHelper::format_to_nice_date($datetime, $hide_year_if_current); } /** * * Returns time in WP timezone, because end_date is stored in WP timezone, do not use it for customer facing outputs * * @return string|null */ public function get_nice_start_date( $hide_year_if_current = false ) : string { $datetime = OsWpDateTime::os_createFromFormat( "Y-m-d", $this->start_date ); return OsTimeHelper::format_to_nice_date($datetime, $hide_year_if_current); } /** * * Returns time in WP timezone, because start_date is stored in WP timezone, do not use it for customer facing outputs * * @param $hide_if_today bool * @param $hide_year_if_current bool * * @return string */ public function get_nice_start_datetime( bool $hide_if_today = true, bool $hide_year_if_current = true ): string { if ( $hide_if_today && $this->start_date == OsTimeHelper::today_date( 'Y-m-d' ) ) { $date = __( 'Today', 'latepoint' ); } else { $date = $this->get_nice_start_date( $hide_year_if_current ); } return implode( ', ', array_filter( [ $date, $this->get_nice_start_time() ] ) ); } public function is_bundle_scheduling() : bool { return ! empty( $this->order_item_id ); } public function get_connected_recurring_bookings() : array{ if(empty($this->recurrence_id) || $this->is_new_record()) return []; $bookings = new OsBookingModel(); return $bookings->where(['recurrence_id' => $this->recurrence_id, 'id !=' => $this->id])->order_by('start_datetime_utc asc')->get_results_as_models(); } public function get_nice_datetime_for_summary(string $viewer = 'customer'){ $nice_datetime = ''; if($this->start_date){ $nice_datetime = $this->get_nice_start_datetime(false); if(OsSettingsHelper::is_on( 'show_booking_end_time') && !empty($this->end_time) && !empty($this->start_time)){ $nice_datetime = $nice_datetime.' - '.$this->get_nice_end_time(); } } /** * Get a formatted start and end time (if needed) * * @since 5.1.0 * @hook latepoint_get_nice_datetime_for_summary * * @param {string} $statuses Nice datetime * @param {OsBookingModel} $booking An object of booking model * * @returns {string} Filtered nice datetime */ $nice_datetime = apply_filters('latepoint_get_nice_datetime_for_summary', $nice_datetime, $this, $viewer); return $nice_datetime; } public function format_end_date_and_time( $format = LATEPOINT_DATETIME_DB_FORMAT, $input_timezone = false, $output_timezone = false ) { if ( ! $input_timezone ) { $input_timezone = OsTimeHelper::get_wp_timezone(); } if ( ! $output_timezone ) { $output_timezone = OsTimeHelper::get_wp_timezone(); } $date = OsWpDateTime::os_createFromFormat( LATEPOINT_DATETIME_DB_FORMAT, $this->end_date . ' ' . OsTimeHelper::minutes_to_army_hours_and_minutes( $this->end_time ) . ':00', $input_timezone ); $date->setTimeZone( $output_timezone ); return OsUtilHelper::translate_months( $date->format( $format ) ); } public function format_start_date() { if ( empty( $this->start_date ) ) { $date = new OsWpDateTime(); $this->start_date = $date->format( 'Y-m-d' ); } else { $date = OsWpDateTime::os_createFromFormat( "Y-m-d", $this->start_date ); } return $date->format( OsSettingsHelper::get_date_format() ); } public function format_start_date_and_time( $format = LATEPOINT_DATETIME_DB_FORMAT, $input_timezone = false, $output_timezone = false ) { if ( ! $input_timezone ) { $input_timezone = OsTimeHelper::get_wp_timezone(); } if ( ! $output_timezone ) { $output_timezone = OsTimeHelper::get_wp_timezone(); } if ( is_null( $this->start_time ) || $this->start_time === '' ) { // no time set yet (could be because summary is reloaded when date is picked, before the time is picked) $date = OsWpDateTime::os_createFromFormat( "Y-m-d", $this->start_date ); if ( $date ) { return OsUtilHelper::translate_months( $date->format( OsSettingsHelper::get_readable_date_format() ) ); } else { return __( 'Invalid Date/Time', 'latepoint' ); } } else { // both date & time are set, update timezone and translate $date = OsWpDateTime::os_createFromFormat( LATEPOINT_DATETIME_DB_FORMAT, $this->start_date . ' ' . OsTimeHelper::minutes_to_army_hours_and_minutes( $this->start_time ) . ':00', $input_timezone ); if ( $date ) { $date->setTimeZone( $output_timezone ); return OsUtilHelper::translate_months( $date->format( $format ) ); } else { return __( 'Invalid Date/Time', 'latepoint' ); } } } public function format_start_date_and_time_rfc3339() { return $this->format_start_date_and_time( \DateTime::RFC3339 ); } public function format_end_date_and_time_rfc3339() { return $this->format_end_date_and_time( \DateTime::RFC3339 ); } public function format_start_date_and_time_for_google() { return $this->format_start_date_and_time( \DateTime::RFC3339 ); } public function format_end_date_and_time_for_google() { return $this->format_end_date_and_time( \DateTime::RFC3339 ); } /* * Checks if the booking has passed */ public function time_status() { try { $now_datetime = OsTimeHelper::now_datetime_utc(); if(empty($this->start_datetime_utc) || empty($this->end_datetime_utc)){ $this->set_utc_datetimes(true); } $booking_start = new OsWpDateTime( $this->start_datetime_utc, new DateTimeZone( 'UTC' ) ); $booking_end = new OsWpDateTime( $this->end_datetime_utc, new DateTimeZone( 'UTC' ) ); if ( ( $now_datetime <= $booking_end ) && ( $now_datetime >= $booking_start ) ) { return 'now'; } elseif ( $now_datetime <= $booking_start ) { return 'upcoming'; } else { return 'past'; } } catch ( Exception $e ) { return 'past'; } } public function start_datetime_in_format( string $format, string $output_in_timezone_name ) : string { if(empty($this->start_datetime_utc)) return ''; $booking_start_datetime = OsTimeHelper::date_from_db( $this->start_datetime_utc ); $booking_start_datetime->setTimezone( new DateTimeZone($output_in_timezone_name) ); return $booking_start_datetime->format( $format ); } public function is_start_date_and_time_set() : bool { return ($this->start_date != '' && $this->start_time != ''); } protected function get_time_left() { $now_datetime = new OsWpDateTime( 'now' ); $booking_datetime = OsWpDateTime::os_createFromFormat( LATEPOINT_DATETIME_DB_FORMAT, $this->format_start_date_and_time() ); $css_class = 'left-days'; if ( $booking_datetime ) { $diff = $now_datetime->diff( $booking_datetime ); if ( $diff->d > 0 || $diff->m > 0 || $diff->y > 0 ) { $left = $diff->format( '%a ' . __( 'days', 'latepoint' ) ); } else { if ( $diff->h > 0 ) { $css_class = 'left-hours'; $left = $diff->format( '%h ' . __( 'hours', 'latepoint' ) ); } else { $css_class = 'left-minutes'; $left = $diff->format( '%i ' . __( 'minutes', 'latepoint' ) ); } } } else { $left = 'n/a'; } return '<span class="time-left ' . esc_attr($css_class) . '">' . esc_html($left) . '</span>'; } protected function get_agent() { if ( $this->agent_id ) { if ( ! isset( $this->agent ) || ( isset( $this->agent ) && ( $this->agent->id != $this->agent_id ) ) ) { $this->agent = new OsAgentModel( $this->agent_id ); } } else { $this->agent = new OsAgentModel(); } return $this->agent; } public function get_agent_full_name() { if ( $this->agent_id == LATEPOINT_ANY_AGENT ) { return __( 'Any Available Agent', 'latepoint' ); } else { return $this->agent->full_name; } } public function get_location() { if ( $this->location_id ) { // if location has not been initialized yet, or location_id is different from the one initialized - init again if ( empty( $this->location ) || ( $this->location->id != $this->location_id ) ) { $this->location = new OsLocationModel( $this->location_id ); } } else { $this->location = new OsLocationModel(); } return $this->location; } protected function get_customer() { if ( $this->customer_id ) { if ( ! isset( $this->customer ) || ( isset( $this->customer ) && ( $this->customer->id != $this->customer_id ) ) ) { $this->customer = new OsCustomerModel( $this->customer_id ); } } else { $this->customer = new OsCustomerModel(); } return $this->customer; } protected function get_service() { if ( $this->service_id ) { if ( ! isset( $this->service ) || ( isset( $this->service ) && ( $this->service->id != $this->service_id ) ) ) { $this->service = new OsServiceModel( $this->service_id ); } } else { $this->service = new OsServiceModel(); } return $this->service; } public function get_nice_start_date_in_timezone(string $timezone_name = '', $hide_year_if_current = false) : string{ $datetime = $this->get_start_datetime($timezone_name); return OsTimeHelper::format_to_nice_date($datetime, $hide_year_if_current); } public function get_nice_end_date_in_timezone(string $timezone_name = '', $hide_year_if_current = false) : string{ $datetime = $this->get_end_datetime($timezone_name); return OsTimeHelper::format_to_nice_date($datetime, $hide_year_if_current); } public function get_nice_start_time_in_timezone(string $timezone_name = '') : string{ $datetime = $this->get_start_datetime($timezone_name); return OsTimeHelper::format_to_nice_time($datetime); } public function get_nice_end_time_in_timezone(string $timezone_name = '') : string{ $datetime = $this->get_end_datetime($timezone_name); return OsTimeHelper::format_to_nice_time($datetime); } public function get_start_datetime_object( ?DateTimeZone $timezone = null ) { if ( empty( $timezone ) ) { $timezone = OsTimeHelper::get_wp_timezone(); } if ( empty( $this->start_datetime_utc ) ) { // fix data, probably an older booking from the time when we didn't store UTC date $this->start_datetime_utc = $this->generate_start_datetime_in_db_format(); } $booking_start_datetime = OsTimeHelper::date_from_db( $this->start_datetime_utc ); if ( $booking_start_datetime ) { $booking_start_datetime->setTimezone( $timezone ); } else { OsDebugHelper::log( 'Error generating start date and time for booking ID: ' . $this->id, 'corrupt_booking_data' ); } return $booking_start_datetime; } public function get_end_datetime_object( ?DateTimeZone $timezone = null ) { if ( empty( $timezone ) ) { $timezone = OsTimeHelper::get_wp_timezone(); } if ( empty( $this->end_datetime_utc ) ) { // fix data, probably an older booking from the time when we didn't store UTC date $this->end_datetime_utc = $this->generate_end_datetime_in_db_format(); } $booking_end_datetime = OsTimeHelper::date_from_db( $this->end_datetime_utc ); if ( $booking_end_datetime ) { $booking_end_datetime->setTimezone( $timezone ); } else { OsDebugHelper::log( 'Error generating end date and time for booking ID: ' . $this->id, 'corrupt_booking_data' ); } return $booking_end_datetime; } public function get_start_datetime( string $set_timezone = 'UTC') : OsWpDateTime{ try{ // start_time and start_date is legacy stored in wordpress timezone $dateTime = new OsWpDateTime( $this->start_date . ' 00:00:00', OsTimeHelper::get_wp_timezone() ); if($this->start_time > 0){ $dateTime->modify( '+' . $this->start_time . ' minutes' ); } if($set_timezone) $dateTime->setTimezone( new DateTimeZone( $set_timezone ) ); return $dateTime; }catch(Exception $e){ return new OsWpDateTime('now'); } } public function get_end_datetime( string $set_timezone = 'UTC') : OsWpDateTime{ try{ // start_time and start_date is legacy stored in wordpress timezone $dateTime = new OsWpDateTime( $this->end_date . ' 00:00:00', OsTimeHelper::get_wp_timezone() ); if($this->end_time > 0){ $dateTime->modify( '+' . $this->end_time . ' minutes' ); } if($set_timezone) $dateTime->setTimezone( new DateTimeZone( $set_timezone ) ); return $dateTime; }catch(Exception $e){ return new OsWpDateTime('now'); } } public function generate_start_datetime_in_db_format( string $timezone = 'UTC' ): string { $dateTime = $this->get_start_datetime($timezone); return $dateTime->format( LATEPOINT_DATETIME_DB_FORMAT ); } public function generate_end_datetime_in_db_format( string $timezone = 'UTC' ): string { $dateTime = $this->get_end_datetime($timezone); return $dateTime->format( LATEPOINT_DATETIME_DB_FORMAT ); } protected function before_save() { // TODO check for uniqueness if ( empty( $this->booking_code ) ) { $this->booking_code = strtoupper( OsUtilHelper::random_text( 'distinct', 7 ) ); } if ( empty( $this->end_date ) ) { $this->end_date = $this->calculate_end_date(); } if ( empty( $this->status ) ) { $this->status = $this->get_default_booking_status(); } if ( empty( $this->total_attendees ) ) { $this->total_attendees = 1; } if ( empty( $this->duration ) && $this->service_id ) { $service = new OsServiceModel( $this->service_id ); $this->duration = $service->duration; } } public function get_default_booking_status() { return OsBookingHelper::get_default_booking_status( $this->service_id ); } public function update_status( $new_status ) { if ( $new_status == $this->status ) { return true; } else { if ( ! in_array( $new_status, array_keys( OsBookingHelper::get_statuses_list() ) ) ) { $this->add_error( 'invalid_booking_status', 'Invalid booking status' ); return false; } $old_booking = clone $this; $this->status = $new_status; $result = $this->update_attributes( [ 'status' => $new_status ] ); if ( $result ) { do_action( 'latepoint_booking_updated', $this, $old_booking ); return true; } else { return false; } } } public function convert_start_datetime_into_server_timezone(string $input_timezone, bool $set_as_customer_timezone = true){ $this->server_timezone = OsTimeHelper::get_wp_timezone_name(); if($set_as_customer_timezone) $this->customer_timezone = $input_timezone; if ( $this->is_start_date_and_time_set() && $this->server_timezone != $input_timezone ) { try { // convert from submitted customer timezone into WP timezone $start_datetime = new OsWpDateTime( $this->start_date . ' 00:00:00', new DateTimeZone( $input_timezone ) ); if ( $this->start_time > 0 ) { $start_datetime->modify( '+' . $this->start_time . ' minutes' ); } $start_datetime->setTimezone( OsTimeHelper::get_wp_timezone() ); $this->start_date = $start_datetime->format( 'Y-m-d' ); $this->start_time = OsTimeHelper::convert_datetime_to_minutes( $start_datetime ); } catch ( Exception $e ) { } } } public function save_avatar( $image_id = false ) { if ( ( false === $image_id ) && $this->image_id ) { $image_id = $this->image_id; } if ( $image_id && $this->post_id ) { set_post_thumbnail( $this->post_id, $image_id ); $this->image_id = $image_id; } return $this->image_id; } protected function allowed_params( $role = 'admin' ) { $allowed_params = array( 'service_id', 'booking_code', 'agent_id', 'customer_id', 'location_id', 'start_date', 'end_date', 'start_time', 'end_time', 'start_datetime_utc', 'end_datetime_utc', 'buffer_before', 'duration', 'buffer_after', 'total_attendees', 'total_attendees_sum', 'total_customers', 'cart_item_id', 'order_item_id', 'status', 'form_id', 'server_timezone', 'customer_timezone', 'generate_recurrent_sequence', 'recurrence_id' ); return $allowed_params; } protected function params_to_save( $role = 'admin' ) { $params_to_save = array( 'service_id', 'booking_code', 'agent_id', 'customer_id', 'location_id', 'start_date', 'end_date', 'start_time', 'end_time', 'start_datetime_utc', 'end_datetime_utc', 'duration', 'buffer_before', 'buffer_after', 'total_attendees', 'status', 'order_item_id', 'server_timezone', 'customer_timezone', 'recurrence_id' ); return $params_to_save; } protected function properties_to_validate() { $validations = array( 'order_item_id' => array( 'presence' ), 'service_id' => array( 'presence' ), 'agent_id' => array( 'presence' ), 'location_id' => array( 'presence' ), 'customer_id' => array( 'presence' ), 'start_date' => array( 'presence' ), 'end_date' => array( 'presence' ), 'status' => array( 'presence' ), ); return $validations; } }