JezK
Edit File: bookings_controller.php
<?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } if ( ! class_exists( 'OsBookingsController' ) ) : class OsBookingsController extends OsController { private $booking; function __construct() { parent::__construct(); $this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'bookings/'; $this->vars['page_header'] = OsMenuHelper::get_menu_items_by_id( 'appointments' ); $this->vars['breadcrumbs'][] = array( 'label' => __( 'Appointments', 'latepoint' ), 'link' => OsRouterHelper::build_link( [ 'bookings', 'pending_approval' ] ), ); } public function view_booking_log() { $booking_id = absint( $this->params['booking_id'] ); $booking = new OsBookingModel( $booking_id ); if ( ! OsRolesHelper::can_user_make_action_on_model_record( $booking, 'view' ) ) { $this->access_not_allowed(); return; } $activities = new OsActivityModel(); $activities = $activities->where( [ 'booking_id' => $booking_id ] )->order_by( 'id desc' )->get_results_as_models(); $this->vars['booking'] = $booking; $this->vars['activities'] = $activities; $this->format_render( __FUNCTION__ ); } public function grouped_bookings_quick_view() { if ( ! isset( $this->params['booking_id'] ) ) { return false; } $booking = new OsBookingModel( $this->params['booking_id'] ); if ( ! OsRolesHelper::can_user_make_action_on_model_record( $booking, 'view' ) ) { $this->access_not_allowed(); return; } $this->vars['booking'] = $booking; $group_bookings = new OsBookingModel(); $group_bookings = $group_bookings->where( [ 'start_time' => $booking->start_time, 'start_date' => $booking->start_date, 'service_id' => $booking->service_id, 'location_id' => $booking->location_id, 'agent_id' => $booking->agent_id, ] )->should_not_be_cancelled()->get_results_as_models(); $total_attendees = 0; if ( $group_bookings ) { foreach ( $group_bookings as $group_booking ) { $total_attendees = $total_attendees + $group_booking->total_attendees; } } $this->vars['total_attendees'] = $total_attendees; $this->vars['group_bookings'] = $group_bookings; $this->format_render( __FUNCTION__ ); } public function pending_approval() { $this->vars['page_header'] = __( 'Pending Appointments', 'latepoint' ); $this->vars['breadcrumbs'][] = array( 'label' => __( 'Pending Appointments', 'latepoint' ), 'link' => false, ); $page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1; $per_page = OsSettingsHelper::get_number_of_records_per_page(); $offset = ( $page_number > 1 ) ? ( ( $page_number - 1 ) * $per_page ) : 0; $bookings = new OsBookingModel(); $query_args = [ 'status' => OsBookingHelper::get_booking_statuses_for_pending_page() ]; $bookings->where( $query_args )->filter_allowed_records(); $count_total_bookings = clone $bookings; $total_bookings = $count_total_bookings->count(); $this->vars['bookings'] = $bookings->set_limit( $per_page )->set_offset( $offset )->order_by( 'id desc' )->get_results_as_models(); $total_pages = ceil( $total_bookings / $per_page ); $this->vars['total_pages'] = $total_pages; $this->vars['total_bookings'] = $total_bookings; $this->vars['per_page'] = $per_page; $this->vars['current_page_number'] = $page_number; $this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1; $this->vars['showing_to'] = min( $page_number * $per_page, $this->vars['total_bookings'] ); $this->format_render( __FUNCTION__ ); } public function customize_table() { $this->vars['selected_columns'] = OsSettingsHelper::get_selected_columns_for_bookings_table(); $this->vars['available_columns'] = OsSettingsHelper::get_available_columns_for_bookings_table(); $this->vars['ordered_columns'] = OsSettingsHelper::get_ordered_bookings_table_columns(); $this->format_render( __FUNCTION__ ); } public function index() { $this->vars['page_header'] = false; $this->vars['breadcrumbs'][] = array( 'label' => __( 'All', 'latepoint' ), 'link' => false, ); $page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1; $per_page = OsSettingsHelper::get_number_of_records_per_page(); $offset = ( $page_number > 1 ) ? ( ( $page_number - 1 ) * $per_page ) : 0; $customer = new OsCustomerModel(); $bookings = new OsBookingModel(); $query_args = []; $selected_columns = OsSettingsHelper::get_selected_columns_for_bookings_table(); $available_columns = OsSettingsHelper::get_available_columns_for_bookings_table(); $ordered_columns = OsSettingsHelper::get_ordered_bookings_table_columns(); $filter = $this->params['filter'] ?? false; $order_by = [ 'key' => 'booking_id', 'direction' => 'desc', 'column' => 'id', ]; // TABLE SEARCH FILTERS if ( $filter ) { if ( ! empty( $filter['records_ordered_by_key'] ) && ! empty( $filter['records_ordered_by_direction'] ) ) { if ( in_array( $filter['records_ordered_by_direction'], [ 'desc', 'asc' ] ) ) { $order_by['direction'] = $filter['records_ordered_by_direction']; } $order_by['key'] = $filter['records_ordered_by_key']; switch ( $filter['records_ordered_by_key'] ) { case 'booking_id': $order_by['column'] = 'id'; break; case 'booking_start_datetime': $order_by['column'] = 'start_datetime_utc'; break; case 'booking_created_on': $order_by['column'] = 'created_at'; break; case 'booking_time_left': $order_by['column'] = 'start_datetime_utc'; break; } } if ( ! empty( $filter['service_id'] ) ) { $query_args['service_id'] = $filter['service_id']; } if ( ! empty( $filter['agent_id'] ) ) { $query_args['agent_id'] = $filter['agent_id']; } if ( ! empty( $filter['location_id'] ) ) { $query_args['location_id'] = $filter['location_id']; } if ( ! empty( $filter['time_status'] ) ) { switch ( $filter['time_status'] ) { case 'now': $query_args['start_datetime_utc <='] = OsTimeHelper::now_datetime_utc_in_db_format(); $query_args['end_datetime_utc >='] = OsTimeHelper::now_datetime_utc_in_db_format(); break; case 'upcoming': $query_args['start_datetime_utc >='] = OsTimeHelper::now_datetime_utc_in_db_format(); break; case 'past': $query_args['end_datetime_utc <='] = OsTimeHelper::now_datetime_utc_in_db_format(); break; } } if ( ! empty( $filter['status'] ) ) { $query_args[ LATEPOINT_TABLE_BOOKINGS . '.status' ] = $filter['status']; } if ( ! empty( $filter['payment_status'] ) ) { $query_args[ LATEPOINT_TABLE_BOOKINGS . '.payment_status' ] = $filter['payment_status']; } if ( ! empty( $filter['id'] ) ) { $query_args[ LATEPOINT_TABLE_BOOKINGS . '.id' ] = $filter['id']; } if ( ! empty( $filter['created_date_from'] ) && ! empty( $filter['created_date_to'] ) ) { $query_args[ LATEPOINT_TABLE_BOOKINGS . '.created_at >=' ] = $filter['created_date_from'] . ' 00:00:00'; $query_args[ LATEPOINT_TABLE_BOOKINGS . '.created_at <=' ] = $filter['created_date_to'] . ' 23:59:59'; } if ( ! empty( $filter['booking_date_from'] ) && ! empty( $filter['booking_date_to'] ) ) { $query_args[ LATEPOINT_TABLE_BOOKINGS . '.start_date >=' ] = $filter['booking_date_from']; $query_args[ LATEPOINT_TABLE_BOOKINGS . '.start_date <=' ] = $filter['booking_date_to']; } $selected_columns = OsSettingsHelper::get_selected_columns_for_bookings_table(); if ( ! empty( $filter['order'] ) ) { $bookings->select( LATEPOINT_TABLE_BOOKINGS . '.*' ); $bookings->join( LATEPOINT_TABLE_ORDER_ITEMS, [ 'id' => LATEPOINT_TABLE_BOOKINGS . '.order_item_id' ] ); $bookings->join( LATEPOINT_TABLE_ORDERS, [ 'id' => LATEPOINT_TABLE_ORDER_ITEMS . '.order_id' ] ); if ( ! empty( $filter['order']['payment_status'] ) ) { $bookings->select( LATEPOINT_TABLE_ORDERS . '.payment_status' ); $query_args[ LATEPOINT_TABLE_ORDERS . '.payment_status' ] = $filter['order']['payment_status']; } } if ( ! empty( $filter['customer'] ) ) { $bookings->select( LATEPOINT_TABLE_BOOKINGS . '.*' ); if ( ! empty( $filter['customer']['full_name'] ) ) { $bookings->select( LATEPOINT_TABLE_CUSTOMERS . '.first_name, ' . LATEPOINT_TABLE_CUSTOMERS . '.last_name' ); $query_args[ 'concat_ws(" ", ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name,' . LATEPOINT_TABLE_CUSTOMERS . '.last_name) LIKE' ] = '%' . $filter['customer']['full_name'] . '%'; $this->vars['customer_name_query'] = $filter['customer']['full_name']; } $bookings->join( LATEPOINT_TABLE_CUSTOMERS, [ 'id' => LATEPOINT_TABLE_BOOKINGS . '.customer_id' ] ); if ( ! empty( $selected_columns['customer'] ) ) { $meta_filter = []; foreach ( $selected_columns['customer'] as $customer_column_key ) { if ( isset( $available_columns['customer'][ $customer_column_key ] ) && ! empty( $filter['customer'][ $customer_column_key ] ) ) { if ( in_array( $customer_column_key, $customer->get_params_to_save() ) ) { // native field $bookings->select( LATEPOINT_TABLE_CUSTOMERS . '.' . $customer_column_key ); $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.' . $customer_column_key . ' LIKE' ] = '%' . $filter['customer'][ $customer_column_key ] . '%'; } else { // meta field $meta_filter[ $customer_column_key ] = $filter['customer'][ $customer_column_key ]; } } } if ( count( $meta_filter ) ) { $customers_ids = OsMetaHelper::get_customers_by_filter( $meta_filter ) ?: [ - 1 ]; $query_args[ LATEPOINT_TABLE_CUSTOMERS . '.id IN' ] = $customers_ids; } } } // filters for custom selected columns, only related to booking fields if ( ! empty( $selected_columns['booking'] ) ) { foreach ( $selected_columns['booking'] as $booking_column_key ) { if ( ! empty( $available_columns['booking'][ $booking_column_key ] ) && ! empty( $filter[ $booking_column_key ] ) ) { $query_args[ $booking_column_key . ' LIKE' ] = '%' . $filter[ $booking_column_key ] . '%'; } } } } $this->vars['agents_list'] = OsAgentHelper::get_agents_list( true ); $this->vars['locations_list'] = OsLocationHelper::get_locations_list( true ); $this->vars['services_list'] = OsServiceHelper::get_services_list( true ); $this->vars['selected_columns'] = $selected_columns; $this->vars['available_columns'] = $available_columns; $this->vars['ordered_columns'] = $ordered_columns; $this->vars['records_ordered_by_key'] = $order_by['key']; $this->vars['records_ordered_by_direction'] = $order_by['direction']; $this->vars['can_bulk_delete'] = OsRolesHelper::can_user_perform_model_action( 'OsBookingModel', 'delete' ); // OUTPUT CSV IF REQUESTED if ( isset( $this->params['download'] ) && $this->params['download'] == 'csv' ) { $csv_filename = 'all_bookings_' . OsUtilHelper::random_text() . '.csv'; header( 'Content-Type: text/csv' ); header( "Content-Disposition: attachment; filename={$csv_filename}" ); $labels_row = [ __( 'ID', 'latepoint' ), __( 'Service', 'latepoint' ), __( 'Start Date & Time', 'latepoint' ), __( 'Duration', 'latepoint' ), __( 'Customer', 'latepoint' ), __( 'Customer Phone', 'latepoint' ), __( 'Customer Email', 'latepoint' ), __( 'Agent', 'latepoint' ), __( 'Agent Phone', 'latepoint' ), __( 'Agent Email', 'latepoint' ), __( 'Status', 'latepoint' ), __( 'Price', 'latepoint' ), __( 'Booked On', 'latepoint' ), ]; $bookings_data = []; $bookings_data[] = $labels_row; $bookings_arr = $bookings->where( $query_args )->filter_allowed_records()->order_by( $order_by['column'] . ' ' . $order_by['direction'] )->get_results_as_models(); if ( $bookings_arr ) { foreach ( $bookings_arr as $booking ) { $order_item = new OsOrderItemModel( $booking->order_item_id ); $values_row = [ $booking->id, $booking->service->name, $booking->nice_start_datetime, $booking->get_total_duration(), $booking->customer->full_name, $booking->customer->phone, $booking->customer->email, $booking->agent->full_name, $booking->agent->phone, $booking->agent->email, $booking->nice_status, OsMoneyHelper::format_price( $order_item->get_total(), true, false ), $booking->nice_created_at, ]; $values_row = apply_filters( 'latepoint_booking_row_for_csv_export', $values_row, $booking, $this->params ); $bookings_data[] = $values_row; } } $bookings_data = apply_filters( 'latepoint_bookings_data_for_csv_export', $bookings_data, $this->params ); OsCSVHelper::array_to_csv( $bookings_data ); return; } $query_args = OsRolesHelper::filter_allowed_records_from_arguments_or_filter( $query_args ); $bookings->where( $query_args )->filter_allowed_records(); $count_total_bookings = clone $bookings; $total_bookings = $count_total_bookings->count(); $this->vars['bookings'] = $bookings->set_limit( $per_page )->set_offset( $offset )->order_by( $order_by['column'] . ' ' . $order_by['direction'] )->get_results_as_models(); $this->vars['total_bookings'] = $total_bookings; $total_pages = ceil( $total_bookings / $per_page ); $this->vars['total_pages'] = $total_pages; $this->vars['per_page'] = $per_page; $this->vars['current_page_number'] = $page_number; $this->vars['total_records'] = $total_bookings; $this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1; $this->vars['showing_to'] = min( $page_number * $per_page, $this->vars['total_bookings'] ); $this->format_render( [ 'json_view_name' => '_table_body', 'html_view_name' => __FUNCTION__, ], [], [ 'total_pages' => $total_pages, 'showing_from' => $this->vars['showing_from'], 'showing_to' => $this->vars['showing_to'], 'total_records' => $total_bookings, ] ); } function quick_availability() { $trigger_form_booking_id = $this->params['trigger_form_booking_id']; $trigger_form_order_item_id = $this->params['trigger_form_order_item_id']; $booking = OsOrdersHelper::create_booking_object_from_booking_data_form( $this->params['order_items'][ $trigger_form_order_item_id ]['bookings'][ $trigger_form_booking_id ] ); $calendar_start_date = isset( $this->params['start_date'] ) ? new OsWpDateTime( $this->params['start_date'] ) : new OsWpDateTime( $booking->start_date ); // show one more day before so the current selection does not look weird if ( isset( $this->params['previous_days'] ) ) { $calendar_end_date = clone $calendar_start_date; $calendar_start_date->modify( '-60 days' ); } else { if ( ! isset( $this->params['show_days_only'] ) ) { $calendar_start_date->modify( '-1 day' ); } $calendar_end_date = clone $calendar_start_date; $calendar_end_date->modify( '+60 days' ); } if ( OsAuthHelper::get_current_user()->is_single_record_allowed( 'agent' ) ) { $booking->agent_id = OsRolesHelper::get_allowed_records( 'agent' )[0]; } $work_periods = OsWorkPeriodsHelper::get_work_periods( new \LatePoint\Misc\Filter( [ 'date_from' => $calendar_start_date->format( 'Y-m-d' ), 'date_to' => $calendar_end_date->format( 'Y-m-d' ), 'service_id' => $booking->service_id, 'agent_id' => $booking->agent_id, 'location_id' => $booking->location_id, ] ) ); $work_start_end = OsWorkPeriodsHelper::get_work_start_end_time( $work_periods ); $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ); $settings = []; $settings['accessed_from_backend'] = true; if ( ! $booking->is_new_record() ) { $settings['exclude_booking_ids'] = [ $booking->id ]; } $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $calendar_start_date, $calendar_end_date, $settings ); $work_boundaries = OsResourceHelper::get_work_boundaries_for_groups_of_resources( $resources ); $this->vars['trigger_form_booking_id'] = $trigger_form_booking_id; $this->vars['trigger_form_order_item_id'] = $trigger_form_order_item_id; $this->vars['booking'] = $booking; $this->vars['work_boundaries'] = $work_boundaries; $this->vars['show_days_only'] = isset( $this->params['show_days_only'] ) ? true : false; $this->vars['timeblock_interval'] = $booking->service->get_timeblock_interval(); $this->vars['calendar_start_date'] = $calendar_start_date; $this->vars['calendar_end_date'] = $calendar_end_date; $this->vars['booking_request'] = $booking_request; $this->vars['resources'] = $resources; $agents = new OsAgentModel(); $this->vars['agents'] = $agents->filter_allowed_records()->get_results_as_models(); $this->format_render( __FUNCTION__ ); } /** * Maximum number of appointments accepted in a single bulk-delete request. * Acts as a guard against accidental or malicious oversized payloads. */ const BULK_DESTROY_MAX_IDS = 100; /** * Bulk-delete appointments by ID. * * Expects POST params: * - _wpnonce: nonce for action 'bulk_destroy_bookings'. * - ids: array of booking IDs (or comma-separated string). * * Verifies the user can delete bookings, validates input, then attempts to * delete each ID after a per-record capability check. Fires the standard * latepoint_booking_will_be_deleted / latepoint_booking_deleted hooks per * successful deletion and logs activity. * * Responds with JSON containing status, message, deleted_ids, failed_ids, * deleted_count and failed_count. * * @return void */ public function bulk_destroy() { $this->check_nonce( 'bulk_destroy_bookings' ); if ( ! OsRolesHelper::can_user_perform_model_action( 'OsBookingModel', 'delete' ) ) { $this->send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => __( 'You do not have permission to delete appointments.', 'latepoint' ), ) ); return; } $raw_ids = $this->params['ids'] ?? array(); if ( ! is_array( $raw_ids ) ) { $raw_ids = explode( ',', (string) $raw_ids ); } $ids = array(); foreach ( $raw_ids as $raw_id ) { $id = absint( $raw_id ); if ( $id ) { $ids[ $id ] = $id; } } if ( empty( $ids ) ) { $this->send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => __( 'No appointments selected.', 'latepoint' ), ) ); return; } if ( count( $ids ) > self::BULK_DESTROY_MAX_IDS ) { $this->send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => sprintf( /* translators: %d: maximum number of appointments allowed in a single bulk action */ __( 'Too many appointments selected. Please delete in batches of %d or fewer.', 'latepoint' ), self::BULK_DESTROY_MAX_IDS ), ) ); return; } ignore_user_abort( true ); if ( function_exists( 'set_time_limit' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged @set_time_limit( 0 ); } $deleted_ids = array(); $failed_ids = array(); foreach ( $ids as $id ) { $booking = new OsBookingModel( $id ); if ( $booking->is_new_record() ) { $failed_ids[] = $id; continue; } if ( ! OsRolesHelper::can_user_make_action_on_model_record( $booking, 'delete' ) ) { $failed_ids[] = $id; continue; } /** * Fires right before a booking is about to be deleted via bulk delete. * * @param {integer} $booking_id ID of the booking that will be deleted. * @since 5.2.0 * @hook latepoint_booking_will_be_deleted */ do_action( 'latepoint_booking_will_be_deleted', $id ); if ( $booking->delete() ) { /** * Fires right after a booking has been deleted via bulk delete. * * @param {integer} $booking_id ID of the booking that was deleted. * @since 5.2.0 * @hook latepoint_booking_deleted */ do_action( 'latepoint_booking_deleted', $id ); OsActivitiesHelper::log_booking_deleted( $booking ); $deleted_ids[] = $id; } else { $failed_ids[] = $id; } } $deleted_count = count( $deleted_ids ); $failed_count = count( $failed_ids ); if ( $deleted_count && ! $failed_count ) { $status = LATEPOINT_STATUS_SUCCESS; $message = sprintf( /* translators: %d: number of appointments deleted */ _n( '%d appointment deleted successfully.', '%d appointments deleted successfully.', $deleted_count, 'latepoint' ), $deleted_count ); } elseif ( $deleted_count && $failed_count ) { $status = LATEPOINT_STATUS_SUCCESS; $message = sprintf( /* translators: 1: number of appointments deleted, 2: number of appointments that could not be deleted */ __( '%1$d appointment(s) deleted. %2$d could not be deleted.', 'latepoint' ), $deleted_count, $failed_count ); } else { $status = LATEPOINT_STATUS_ERROR; $message = sprintf( /* translators: %d: number of appointments that could not be deleted */ _n( '%d appointment could not be deleted.', '%d appointments could not be deleted.', $failed_count, 'latepoint' ), $failed_count ); } $this->send_json( array( 'status' => $status, 'message' => $message, 'deleted_ids' => $deleted_ids, 'failed_ids' => $failed_ids, 'deleted_count' => $deleted_count, 'failed_count' => $failed_count, ) ); } function change_status() { if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) { $this->check_nonce( 'change_status_booking_' . $this->params['id'] ); $booking_id = $this->params['id']; $new_status = $this->params['status']; $booking = new OsBookingModel( $booking_id ); if ( ! OsRolesHelper::can_user_make_action_on_model_record( $booking, 'edit' ) ) { exit; } if ( $booking->update_status( $new_status ) ) { $status = LATEPOINT_STATUS_SUCCESS; $response_html = __( 'Appointment Status Updated', 'latepoint' ); } else { $status = LATEPOINT_STATUS_ERROR; $response_html = __( 'Error Updating Booking Status!', 'latepoint' ) . ' ' . implode( ',', $booking->get_error_messages() ); } } else { $status = LATEPOINT_STATUS_ERROR; $response_html = __( 'Error Updating Booking Status! Invalid ID', 'latepoint' ); } if ( $this->get_return_format() == 'json' ) { $this->send_json( array( 'status' => $status, 'message' => $response_html, ) ); } } } endif;