activities_helper.php
3 months ago
agent_helper.php
3 months ago
analytics_helper.php
1 week ago
auth_helper.php
6 days ago
blocks_helper.php
3 weeks ago
booking_helper.php
4 days ago
bricks_helper.php
3 months ago
bundles_helper.php
4 days ago
calendar_helper.php
3 days ago
carts_helper.php
3 months ago
connector_helper.php
3 months ago
csv_helper.php
3 months ago
customer_helper.php
1 month ago
customer_import_helper.php
1 month ago
database_helper.php
1 week ago
debug_helper.php
3 months ago
defaults_helper.php
3 months ago
elementor_helper.php
3 months ago
email_helper.php
3 months ago
encrypt_helper.php
3 months ago
events_helper.php
3 months ago
form_helper.php
3 months ago
icalendar_helper.php
3 months ago
image_helper.php
3 months ago
invoices_helper.php
3 weeks ago
license_helper.php
3 months ago
location_helper.php
3 months ago
marketing_systems_helper.php
3 months ago
meeting_systems_helper.php
3 months ago
menu_helper.php
3 weeks ago
meta_helper.php
3 months ago
migrations_helper.php
3 months ago
money_helper.php
3 months ago
notifications_helper.php
3 months ago
nps_survey_helper.php
3 months ago
order_intent_helper.php
2 months ago
orders_helper.php
3 months ago
otp_helper.php
3 months ago
pages_helper.php
3 months ago
params_helper.php
3 months ago
payments_helper.php
2 months ago
plugin_version_update_helper.php
2 months ago
price_breakdown_helper.php
3 months ago
process_jobs_helper.php
3 months ago
processes_helper.php
3 months ago
razorpay_connect_helper.php
1 week ago
replacer_helper.php
3 months ago
resource_helper.php
4 days ago
roles_helper.php
3 weeks ago
router_helper.php
3 months ago
service_helper.php
3 months ago
sessions_helper.php
3 months ago
settings_helper.php
1 week ago
short_links_systems_helper.php
3 months ago
shortcodes_helper.php
3 weeks ago
sms_helper.php
3 months ago
steps_helper.php
1 week ago
stripe_connect_helper.php
1 week ago
styles_helper.php
3 months ago
support_topics_helper.php
3 months ago
time_helper.php
2 months ago
timeline_helper.php
2 months ago
transaction_helper.php
1 month ago
transaction_intent_helper.php
3 months ago
util_helper.php
1 month ago
version_specific_updates_helper.php
3 months ago
whatsapp_helper.php
1 month ago
work_periods_helper.php
3 months ago
wp_datetime.php
3 months ago
wp_user_helper.php
3 months ago
booking_helper.php
2351 lines
| 1 | <?php |
| 2 | |
| 3 | class OsBookingHelper { |
| 4 | |
| 5 | |
| 6 | /** |
| 7 | * @param OsBookingModel $booking |
| 8 | * |
| 9 | * @return mixed|void |
| 10 | * |
| 11 | * Returns full amount to charge in database format 1999.0000 |
| 12 | * |
| 13 | */ |
| 14 | public static function calculate_full_amount_for_booking( OsBookingModel $booking ) { |
| 15 | if ( ! $booking->service_id ) { |
| 16 | return 0; |
| 17 | } |
| 18 | $amount = self::calculate_full_amount_for_service( $booking ); |
| 19 | $amount = apply_filters( 'latepoint_calculate_full_amount_for_booking', $amount, $booking ); |
| 20 | $amount = OsMoneyHelper::pad_to_db_format( $amount ); |
| 21 | |
| 22 | return $amount; |
| 23 | } |
| 24 | |
| 25 | |
| 26 | /** |
| 27 | * @param OsBookingModel $booking |
| 28 | * |
| 29 | * @return mixed|void |
| 30 | * |
| 31 | */ |
| 32 | public static function calculate_full_amount_for_service( OsBookingModel $booking ) { |
| 33 | if ( ! $booking->service_id ) { |
| 34 | return 0; |
| 35 | } |
| 36 | $service = new OsServiceModel( $booking->service_id ); |
| 37 | $amount_for_service = $service->get_full_amount_for_duration( $booking->duration ); |
| 38 | $amount_for_service = apply_filters( 'latepoint_full_amount_for_service', $amount_for_service, $booking ); |
| 39 | |
| 40 | return $amount_for_service; |
| 41 | } |
| 42 | |
| 43 | |
| 44 | /** |
| 45 | * @param OsBookingModel $booking |
| 46 | * |
| 47 | * @return mixed|void |
| 48 | * |
| 49 | * Returns deposit amount to charge in database format 1999.0000 |
| 50 | * |
| 51 | */ |
| 52 | public static function calculate_deposit_amount_to_charge( OsBookingModel $booking ) { |
| 53 | if ( ! $booking->service_id ) { |
| 54 | return 0; |
| 55 | } |
| 56 | $service = new OsServiceModel( $booking->service_id ); |
| 57 | $amount = $service->get_deposit_amount_for_duration( $booking->duration ); |
| 58 | $amount = apply_filters( 'latepoint_deposit_amount_for_service', $amount, $booking ); |
| 59 | $amount = OsMoneyHelper::pad_to_db_format( $amount ); |
| 60 | |
| 61 | return $amount; |
| 62 | } |
| 63 | |
| 64 | |
| 65 | /** |
| 66 | * @param array $item_data |
| 67 | * |
| 68 | * @return OsBookingModel |
| 69 | */ |
| 70 | public static function build_booking_model_from_item_data( array $item_data ): OsBookingModel { |
| 71 | $booking = new OsBookingModel(); |
| 72 | if ( $item_data['id'] ) { |
| 73 | $booking = $booking->load_by_id( $item_data['id'] ); |
| 74 | if ( ! $booking ) { |
| 75 | $booking = new OsBookingModel(); |
| 76 | } |
| 77 | } |
| 78 | $booking->set_data( $item_data ); |
| 79 | // get buffers from service and set to booking object |
| 80 | if ( ! isset( $item_data['buffer_before'] ) && ! isset( $item_data['buffer_after'] ) ) { |
| 81 | $booking->set_buffers(); |
| 82 | } |
| 83 | if ( empty( $booking->end_time ) ) { |
| 84 | $booking->calculate_end_date_and_time(); |
| 85 | } |
| 86 | if ( empty( $booking->end_date ) ) { |
| 87 | $booking->calculate_end_date(); |
| 88 | } |
| 89 | $booking->set_utc_datetimes(); |
| 90 | |
| 91 | return $booking; |
| 92 | } |
| 93 | |
| 94 | public static function get_booking_id_and_manage_ability_by_key( string $key ) { |
| 95 | $booking_id = OsMetaHelper::get_booking_id_by_meta_value( 'key_to_manage_for_agent', $key ); |
| 96 | if ( $booking_id ) { |
| 97 | return [ |
| 98 | 'booking_id' => $booking_id, |
| 99 | 'for' => 'agent', |
| 100 | ]; |
| 101 | } |
| 102 | |
| 103 | $booking_id = OsMetaHelper::get_booking_id_by_meta_value( 'key_to_manage_for_customer', $key ); |
| 104 | if ( $booking_id ) { |
| 105 | return [ |
| 106 | 'booking_id' => $booking_id, |
| 107 | 'for' => 'customer', |
| 108 | ]; |
| 109 | } |
| 110 | |
| 111 | return false; |
| 112 | } |
| 113 | |
| 114 | public static function is_action_allowed( string $action, OsBookingModel $booking, string $key = '' ) { |
| 115 | $is_allowed = false; |
| 116 | if ( empty( $booking->id ) ) { |
| 117 | return false; |
| 118 | } |
| 119 | if ( ! in_array( $action, [ 'cancel', 'reschedule' ] ) ) { |
| 120 | return false; |
| 121 | } |
| 122 | $action_result = false; |
| 123 | switch ( $action ) { |
| 124 | case 'cancel': |
| 125 | $action_result = OsCustomerHelper::can_cancel_booking( $booking ); |
| 126 | break; |
| 127 | case 'reschedule': |
| 128 | $action_result = OsCustomerHelper::can_reschedule_booking( $booking ); |
| 129 | break; |
| 130 | } |
| 131 | if ( ! empty( $key ) ) { |
| 132 | // key is passed, check if allowed through key |
| 133 | $agent_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_agent', $booking->id ); |
| 134 | $customer_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_customer', $booking->id ); |
| 135 | if ( $key == $agent_key_meta ) { |
| 136 | // agent can do everything, no need to check for action |
| 137 | $is_allowed = true; |
| 138 | } elseif ( $key == $customer_key_meta ) { |
| 139 | // customer |
| 140 | $is_allowed = $action_result; |
| 141 | } |
| 142 | } elseif ( OsAuthHelper::get_logged_in_customer_id() == $booking->customer_id ) { |
| 143 | $is_allowed = $action_result; |
| 144 | } |
| 145 | |
| 146 | return $is_allowed; |
| 147 | } |
| 148 | |
| 149 | public static function generate_add_to_calendar_links( OsBookingModel $booking, $key = false ): string { |
| 150 | $html = '<div class="add-to-calendar-types"> |
| 151 | <div class="atc-heading-wrapper"> |
| 152 | <div class="atc-heading">' . esc_html__( 'Calendar Type', 'latepoint' ) . '</div> |
| 153 | <div class="close-calendar-types"></div> |
| 154 | </div> |
| 155 | <a href="' . esc_url( $booking->get_ical_download_link( $key ) ) . '" target="_blank" class="atc-type atc-type-apple"> |
| 156 | <div class="atc-type-image"></div> |
| 157 | <div class="atc-type-name">' . esc_html__( 'Apple Calendar', 'latepoint' ) . '</div> |
| 158 | </a> |
| 159 | <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'google' ) ) . '" target="_blank" class="atc-type atc-type-google"> |
| 160 | <div class="atc-type-image"></div> |
| 161 | <div class="atc-type-name">' . esc_html__( 'Google Calendar', 'latepoint' ) . '</div> |
| 162 | </a> |
| 163 | <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'outlook' ) ) . '" target="_blank" class="atc-type atc-type-outlook"> |
| 164 | <div class="atc-type-image"></div> |
| 165 | <div class="atc-type-name">' . esc_html__( 'Outlook.com', 'latepoint' ) . '</div> |
| 166 | </a> |
| 167 | <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'outlook' ) ) . '" target="_blank" class="atc-type atc-type-office-365"> |
| 168 | <div class="atc-type-image"></div> |
| 169 | <div class="atc-type-name">' . esc_html__( 'Microsoft 365', 'latepoint' ) . '</div> |
| 170 | </a> |
| 171 | </div>'; |
| 172 | |
| 173 | return $html; |
| 174 | } |
| 175 | |
| 176 | |
| 177 | public static function get_bookings_for_select( $should_be_in_future = false ) { |
| 178 | $bookings = new OsBookingModel(); |
| 179 | if ( $should_be_in_future ) { |
| 180 | $bookings = $bookings->should_be_in_future(); |
| 181 | } |
| 182 | $bookings = $bookings->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models(); |
| 183 | $bookings_options = []; |
| 184 | foreach ( $bookings as $booking ) { |
| 185 | $name = $booking->service->name . ', ' . $booking->agent->full_name . ', ' . $booking->customer->full_name . ' [' . $booking->booking_code . ' : ' . $booking->id . ']'; |
| 186 | $bookings_options[] = [ |
| 187 | 'value' => $booking->id, |
| 188 | 'label' => esc_html( $name ), |
| 189 | ]; |
| 190 | } |
| 191 | |
| 192 | return $bookings_options; |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * |
| 197 | * Determine whether to show |
| 198 | * |
| 199 | * @param $rows |
| 200 | * |
| 201 | * @return bool |
| 202 | */ |
| 203 | public static function is_breakdown_free( $rows ) { |
| 204 | return ( ( empty( $rows['subtotal']['raw_value'] ) || ( (float) $rows['subtotal']['raw_value'] <= 0 ) ) && ( empty( $rows['total']['raw_value'] ) || ( (float) $rows['total']['raw_value'] <= 0 ) ) ); |
| 205 | } |
| 206 | |
| 207 | public static function output_price_breakdown( $rows ) { |
| 208 | foreach ( $rows['before_subtotal'] as $row ) { |
| 209 | self::output_price_breakdown_row( $row ); |
| 210 | } |
| 211 | // if there is nothing between subtotal and total - don't show subtotal as it will be identical to total |
| 212 | if ( ! empty( $rows['after_subtotal'] ) ) { |
| 213 | if ( ! empty( $rows['subtotal'] ) ) { |
| 214 | echo '<div class="subtotal-separator"></div>'; |
| 215 | self::output_price_breakdown_row( $rows['subtotal'] ); |
| 216 | } |
| 217 | foreach ( $rows['after_subtotal'] as $row ) { |
| 218 | self::output_price_breakdown_row( $row ); |
| 219 | } |
| 220 | } |
| 221 | if ( ! empty( $rows['total'] ) ) { |
| 222 | self::output_price_breakdown_row( $rows['total'] ); |
| 223 | } |
| 224 | if ( ! empty( $rows['payments'] ) ) { |
| 225 | foreach ( $rows['payments'] as $row ) { |
| 226 | self::output_price_breakdown_row( $row ); |
| 227 | } |
| 228 | } |
| 229 | if ( ! empty( $rows['balance'] ) ) { |
| 230 | self::output_price_breakdown_row( $rows['balance'] ); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | public static function output_price_breakdown_row( $row ) { |
| 235 | if ( ! empty( $row['items'] ) ) { |
| 236 | if ( ! empty( $row['heading'] ) ) { |
| 237 | echo '<div class="summary-box-heading"><div class="sbh-item">' . esc_html( $row['heading'] ) . '</div><div class="sbh-line"></div></div>'; |
| 238 | } |
| 239 | foreach ( $row['items'] as $row_item ) { |
| 240 | self::output_price_breakdown_row( $row_item ); |
| 241 | } |
| 242 | } else { |
| 243 | $extra_class = ''; |
| 244 | if ( isset( $row['style'] ) && $row['style'] == 'strong' ) { |
| 245 | $extra_class .= ' spi-strong'; |
| 246 | } |
| 247 | if ( isset( $row['style'] ) && $row['style'] == 'total' ) { |
| 248 | $extra_class .= ' spi-total'; |
| 249 | } |
| 250 | if ( isset( $row['type'] ) && $row['type'] == 'credit' ) { |
| 251 | $extra_class .= ' spi-positive'; |
| 252 | } |
| 253 | ?> |
| 254 | <div class="summary-price-item-w <?php echo esc_attr( $extra_class ); ?>"> |
| 255 | <div class="spi-name"> |
| 256 | <?php echo $row['label']; ?> |
| 257 | <?php if ( ! empty( $row['note'] ) ) { |
| 258 | echo '<span class="pi-note">' . esc_html( $row['note'] ) . '</span>'; |
| 259 | } ?> |
| 260 | <?php if ( ! empty( $row['badge'] ) ) { |
| 261 | echo '<span class="pi-badge">' . esc_html( $row['badge'] ) . '</span>'; |
| 262 | } ?> |
| 263 | </div> |
| 264 | <div class="spi-price"><?php echo esc_html( $row['value'] ); ?></div> |
| 265 | </div> |
| 266 | <?php |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | public static function output_price_breakdown_row_as_input_field( $row, $base_name ) { |
| 271 | $field_name = $base_name . '[' . OsUtilHelper::random_text( 'alnum', 8 ) . ']'; |
| 272 | if ( ! empty( $row['items'] ) ) { |
| 273 | echo OsFormHelper::hidden_field( $field_name . '[heading]', $row['heading'] ?? '' ); |
| 274 | foreach ( $row['items'] as $row_item ) { |
| 275 | self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[items]' ); |
| 276 | } |
| 277 | } else { |
| 278 | $is_negative = ( $row['raw_value'] < 0 ); |
| 279 | $wrapper_class = $is_negative ? [ 'class' => 'green-value-input' ] : []; |
| 280 | $label = $row['label'] ?? ''; |
| 281 | if ( ! empty( $row['note'] ) ) { |
| 282 | $label .= ' ' . $row['note']; |
| 283 | } |
| 284 | // Use abs() for money_field because inputmask cannot parse negative initial values; the sign is preserved via the 'type' hidden field |
| 285 | $field_value = $is_negative ? abs( $row['raw_value'] ) : $row['raw_value']; |
| 286 | echo OsFormHelper::money_field( $field_name . '[value]', $label, $field_value, [ 'theme' => 'right-aligned' ], [], $wrapper_class ); |
| 287 | echo OsFormHelper::hidden_field( $field_name . '[label]', $row['label'] ?? '' ); |
| 288 | echo OsFormHelper::hidden_field( $field_name . '[style]', $row['style'] ?? '' ); |
| 289 | echo OsFormHelper::hidden_field( $field_name . '[type]', $row['type'] ?? '' ); |
| 290 | echo OsFormHelper::hidden_field( $field_name . '[note]', $row['note'] ?? '' ); |
| 291 | echo OsFormHelper::hidden_field( $field_name . '[badge]', $row['badge'] ?? '' ); |
| 292 | } |
| 293 | if ( ! empty( $row['sub_items'] ) ) { |
| 294 | foreach ( $row['sub_items'] as $row_item ) { |
| 295 | self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[sub_items]' ); |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * @param \LatePoint\Misc\Filter $filter |
| 302 | * @param bool $accessed_from_backend |
| 303 | * |
| 304 | * @return array |
| 305 | */ |
| 306 | public static function get_blocked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter, bool $accessed_from_backend = false ): array { |
| 307 | $grouped_blocked_periods = []; |
| 308 | |
| 309 | if ( $filter->date_from ) { |
| 310 | $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from ); |
| 311 | $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from ); |
| 312 | |
| 313 | # Loop through days to fill in days that might have no bookings |
| 314 | for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) { |
| 315 | $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] = []; |
| 316 | } |
| 317 | } |
| 318 | if ( ! $accessed_from_backend ) { |
| 319 | $today = new OsWpDateTime( 'today' ); |
| 320 | $earliest_possible_booking = OsSettingsHelper::get_earliest_possible_booking_restriction( $filter->service_id ); |
| 321 | |
| 322 | $block_end_datetime = OsTimeHelper::now_datetime_object(); |
| 323 | if ( $earliest_possible_booking ) { |
| 324 | try { |
| 325 | $block_end_datetime->modify( $earliest_possible_booking ); |
| 326 | } catch ( Exception $e ) { |
| 327 | $block_end_datetime = OsTimeHelper::now_datetime_object(); |
| 328 | } |
| 329 | } |
| 330 | for ( $day = clone $today; $day->format( 'Y-m-d' ) <= $block_end_datetime->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) { |
| 331 | // loop days from now to the earliest possible booking and block timeslots if these days were actually requested |
| 332 | if ( isset( $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] ) ) { |
| 333 | $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ][] = new \LatePoint\Misc\BlockedPeriod( |
| 334 | [ |
| 335 | 'start_time' => 0, |
| 336 | 'end_time' => ( $day->format( 'Y-m-d' ) < $block_end_datetime->format( 'Y-m-d' ) ) ? 24 * 60 : OsTimeHelper::convert_datetime_to_minutes( $block_end_datetime ), |
| 337 | 'start_date' => $day->format( 'Y-m-d' ), |
| 338 | 'end_date' => $day->format( 'Y-m-d' ), |
| 339 | ] |
| 340 | ); |
| 341 | } |
| 342 | } |
| 343 | $latest_possible_booking = OsSettingsHelper::get_latest_possible_booking_restriction( $filter->service_id ); |
| 344 | if ( $latest_possible_booking ) { |
| 345 | try { |
| 346 | $latest_booking_datetime = OsTimeHelper::now_datetime_object(); |
| 347 | $latest_booking_datetime->modify( $latest_possible_booking ); |
| 348 | } catch ( Exception $e ) { |
| 349 | $latest_booking_datetime = null; |
| 350 | } |
| 351 | if ( $latest_booking_datetime && $filter->date_from ) { |
| 352 | $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from ); |
| 353 | // Start from the latest_booking_datetime day |
| 354 | for ( $day = clone $latest_booking_datetime; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) { |
| 355 | if ( isset( $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] ) ) { |
| 356 | $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ][] = new \LatePoint\Misc\BlockedPeriod( |
| 357 | [ |
| 358 | 'start_time' => ( $day->format( 'Y-m-d' ) == $latest_booking_datetime->format( 'Y-m-d' ) ) ? OsTimeHelper::convert_datetime_to_minutes( $latest_booking_datetime ) : 0, |
| 359 | 'end_time' => 24 * 60, |
| 360 | 'start_date' => $day->format( 'Y-m-d' ), |
| 361 | 'end_date' => $day->format( 'Y-m-d' ), |
| 362 | ] |
| 363 | ); |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | $grouped_blocked_periods = apply_filters( 'latepoint_blocked_periods_for_range', $grouped_blocked_periods, $filter, $accessed_from_backend ); |
| 371 | |
| 372 | return $grouped_blocked_periods; |
| 373 | } |
| 374 | |
| 375 | /** |
| 376 | * @param \LatePoint\Misc\Filter $filter |
| 377 | * |
| 378 | * @return array |
| 379 | */ |
| 380 | public static function get_booked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter ): array { |
| 381 | $booked_periods = self::get_booked_periods( $filter ); |
| 382 | |
| 383 | $grouped_booked_periods = []; |
| 384 | if ( $filter->date_from ) { |
| 385 | $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from ); |
| 386 | $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from ); |
| 387 | |
| 388 | # Loop through days to fill in days that might have no bookings |
| 389 | for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) { |
| 390 | $grouped_booked_periods[ $day->format( 'Y-m-d' ) ] = []; |
| 391 | } |
| 392 | foreach ( $booked_periods as $booked_period ) { |
| 393 | $grouped_booked_periods[ $booked_period->start_date ][] = $booked_period; |
| 394 | // if event spans multiple days - add to other days as well |
| 395 | if ( $booked_period->end_date && ( $booked_period->start_date != $booked_period->end_date ) ) { |
| 396 | $grouped_booked_periods[ $booked_period->end_date ][] = $booked_period; |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | return $grouped_booked_periods; |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * @param \LatePoint\Misc\Filter $filter |
| 406 | * |
| 407 | * @return \LatePoint\Misc\BookedPeriod[] |
| 408 | */ |
| 409 | public static function get_booked_periods( \LatePoint\Misc\Filter $filter ): array { |
| 410 | |
| 411 | |
| 412 | $bookings = self::get_bookings( $filter, true ); |
| 413 | $booked_periods = []; |
| 414 | |
| 415 | foreach ( $bookings as $booking ) { |
| 416 | $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $booking ); |
| 417 | } |
| 418 | |
| 419 | |
| 420 | if ( $filter->consider_cart_items ) { |
| 421 | $cart = OsCartsHelper::get_or_create_cart(); |
| 422 | $bookings_in_cart = $cart->get_bookings_from_cart_items(); |
| 423 | |
| 424 | foreach ( $bookings_in_cart as $cart_booking ) { |
| 425 | $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $cart_booking ); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | // TODO Update all filters to accept new "filter" variable (In Google Calendar addon) |
| 430 | $booked_periods = apply_filters( 'latepoint_get_booked_periods', $booked_periods, $filter ); |
| 431 | |
| 432 | return $booked_periods; |
| 433 | } |
| 434 | |
| 435 | |
| 436 | /** |
| 437 | * @param \LatePoint\Misc\Filter $filter |
| 438 | * @param bool $as_models |
| 439 | * |
| 440 | * @return array |
| 441 | */ |
| 442 | public static function get_bookings( \LatePoint\Misc\Filter $filter, bool $as_models = false ): array { |
| 443 | $bookings = new OsBookingModel(); |
| 444 | if ( $filter->date_from ) { |
| 445 | if ( $filter->date_from && $filter->date_to ) { |
| 446 | # both start and end date provided - means it's a range |
| 447 | $bookings->where( |
| 448 | [ |
| 449 | 'start_date >=' => $filter->date_from, |
| 450 | 'start_date <=' => $filter->date_to, |
| 451 | ] |
| 452 | ); |
| 453 | } else { |
| 454 | # only start_date provided - means it's a specific date requested |
| 455 | $bookings->where( [ 'start_date' => $filter->date_from ] ); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | |
| 460 | if ( $filter->connections ) { |
| 461 | $connection_conditions = []; |
| 462 | foreach ( $filter->connections as $connection ) { |
| 463 | $connection_conditions[] = [ |
| 464 | 'AND' => |
| 465 | [ |
| 466 | 'agent_id' => $connection->agent_id, |
| 467 | 'service_id' => $connection->service_id, |
| 468 | 'location_id' => $connection->location_id, |
| 469 | ], |
| 470 | ]; |
| 471 | } |
| 472 | $bookings->where( [ 'OR' => $connection_conditions ] ); |
| 473 | } else { |
| 474 | if ( $filter->agent_id ) { |
| 475 | $bookings->where( [ 'agent_id' => $filter->agent_id ] ); |
| 476 | } |
| 477 | if ( $filter->location_id ) { |
| 478 | $bookings->where( [ 'location_id' => $filter->location_id ] ); |
| 479 | } |
| 480 | if ( $filter->service_id ) { |
| 481 | $bookings->where( [ 'service_id' => $filter->service_id ] ); |
| 482 | } |
| 483 | } |
| 484 | if ( $filter->statuses ) { |
| 485 | $bookings->where( [ 'status' => $filter->statuses ] ); |
| 486 | } |
| 487 | if ( $filter->exclude_booking_ids ) { |
| 488 | $bookings->where( [ 'id NOT IN' => $filter->exclude_booking_ids ] ); |
| 489 | } |
| 490 | $bookings->order_by( 'start_time asc, end_time asc, service_id asc' ); |
| 491 | $bookings = ( $as_models ) ? $bookings->get_results_as_models() : $bookings->get_results(); |
| 492 | |
| 493 | // make sure to return empty array if nothing is found |
| 494 | if ( empty( $bookings ) ) { |
| 495 | $bookings = []; |
| 496 | } |
| 497 | |
| 498 | return $bookings; |
| 499 | } |
| 500 | |
| 501 | public static function generate_ical_event_string( $booking ) { |
| 502 | // translators: %1$s is agent name, %2$s is service name |
| 503 | $booking_description = sprintf( __( 'Appointment with %1$s for %2$s', 'latepoint' ), $booking->agent->full_name, $booking->service->name ); |
| 504 | |
| 505 | $ics = new ICS( |
| 506 | array( |
| 507 | 'location' => $booking->location->full_address, |
| 508 | 'description' => '', |
| 509 | 'dtstart' => $booking->format_start_date_and_time_for_google(), |
| 510 | 'dtend' => $booking->format_end_date_and_time_for_google(), |
| 511 | 'summary' => $booking_description, |
| 512 | 'url' => get_site_url(), |
| 513 | ) |
| 514 | ); |
| 515 | |
| 516 | return $ics->to_string(); |
| 517 | } |
| 518 | |
| 519 | /** |
| 520 | * @param \LatePoint\Misc\BookingRequest $booking_request |
| 521 | * |
| 522 | * @return bool |
| 523 | * |
| 524 | * Checks if requested booking slot is available, loads work periods and booked periods from database and checks availability against them |
| 525 | */ |
| 526 | public static function is_booking_request_available( \LatePoint\Misc\BookingRequest $booking_request, $settings = [] ): bool { |
| 527 | try { |
| 528 | $requested_date = new OsWpDateTime( $booking_request->start_date ); |
| 529 | } catch ( Exception $e ) { |
| 530 | return false; |
| 531 | } |
| 532 | $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $requested_date, $requested_date, $settings ); |
| 533 | if ( empty( $resources[ $requested_date->format( 'Y-m-d' ) ] ) ) { |
| 534 | return false; |
| 535 | } |
| 536 | $is_available = false; |
| 537 | |
| 538 | |
| 539 | // check if satisfies earliest and latest bookings - check per-service settings first, then global |
| 540 | $earliest_possible_booking = OsSettingsHelper::get_earliest_possible_booking_restriction( $booking_request->service_id ); |
| 541 | $latest_possible_booking = OsSettingsHelper::get_latest_possible_booking_restriction( $booking_request->service_id ); |
| 542 | |
| 543 | |
| 544 | if ( $earliest_possible_booking || $latest_possible_booking ) { |
| 545 | // check earliest |
| 546 | if ( ! empty( $earliest_possible_booking ) ) { |
| 547 | try { |
| 548 | $earliest_possible_booking_date = new OsWpDateTime( $earliest_possible_booking ); |
| 549 | if ( $earliest_possible_booking_date > $booking_request->get_start_datetime() ) { |
| 550 | return false; |
| 551 | } |
| 552 | } catch ( Exception $e ) { |
| 553 | |
| 554 | } |
| 555 | } |
| 556 | if ( ! empty( $latest_possible_booking ) ) { |
| 557 | // check latest |
| 558 | try { |
| 559 | $latest_possible_booking_date = new OsWpDateTime( $latest_possible_booking ); |
| 560 | if ( $latest_possible_booking_date < $booking_request->get_start_datetime() ) { |
| 561 | return false; |
| 562 | } |
| 563 | } catch ( Exception $e ) { |
| 564 | |
| 565 | } |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | foreach ( $resources[ $requested_date->format( 'Y-m-d' ) ] as $resource ) { |
| 570 | foreach ( $resource->slots as $slot ) { |
| 571 | if ( $slot->start_time == $booking_request->start_time && $slot->can_accomodate( $booking_request->total_attendees ) ) { |
| 572 | $is_available = true; |
| 573 | } |
| 574 | if ( $is_available ) { |
| 575 | break; |
| 576 | } |
| 577 | } |
| 578 | if ( $is_available ) { |
| 579 | break; |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | return $is_available; |
| 584 | } |
| 585 | |
| 586 | /** |
| 587 | * |
| 588 | * Checks if two bookings are part of the same group appointment |
| 589 | * |
| 590 | * @param bool|OsBookingModel $booking |
| 591 | * @param bool|OsBookingModel $compare_booking |
| 592 | * |
| 593 | * @return bool |
| 594 | */ |
| 595 | public static function check_if_group_bookings( $booking, $compare_booking ): bool { |
| 596 | if ( $booking && $compare_booking && ( $compare_booking->start_time == $booking->start_time ) && ( $compare_booking->end_time == $booking->end_time ) && ( $compare_booking->service_id == $booking->service_id ) && ( $compare_booking->location_id == $booking->location_id ) ) { |
| 597 | return true; |
| 598 | } else { |
| 599 | return false; |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | |
| 604 | public static function process_actions_after_save( $booking_id ) { |
| 605 | } |
| 606 | |
| 607 | /** |
| 608 | * @param DateTime $start_date |
| 609 | * @param DateTime $end_date |
| 610 | * @param \LatePoint\Misc\BookingRequest $booking_request |
| 611 | * @param \LatePoint\Misc\BookingResource[] $resources |
| 612 | * @param array $settings |
| 613 | * |
| 614 | * @return string |
| 615 | * @throws Exception |
| 616 | */ |
| 617 | public static function get_quick_availability_days( DateTime $start_date, DateTime $end_date, \LatePoint\Misc\BookingRequest $booking_request, array $resources = [], array $settings = [] ) { |
| 618 | $default_settings = [ |
| 619 | 'work_boundaries' => false, |
| 620 | 'exclude_booking_ids' => [], |
| 621 | ]; |
| 622 | $settings = array_merge( $default_settings, $settings ); |
| 623 | |
| 624 | $html = ''; |
| 625 | |
| 626 | if ( ! $resources ) { |
| 627 | $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $start_date, $end_date, $settings ); |
| 628 | } |
| 629 | if ( ! $settings['work_boundaries'] ) { |
| 630 | $settings['work_boundaries'] = OsResourceHelper::get_work_boundaries_for_groups_of_resources( $resources ); |
| 631 | } |
| 632 | |
| 633 | if ( $start_date->format( 'j' ) != '1' ) { |
| 634 | $html .= '<div class="ma-month-label">' . OsUtilHelper::get_month_name_by_number( $start_date->format( 'n' ) ) . '</div>'; |
| 635 | } |
| 636 | |
| 637 | for ( $day_date = clone $start_date; $day_date <= $end_date; $day_date->modify( '+1 day' ) ) { |
| 638 | // first day of month, output month name |
| 639 | if ( $day_date->format( 'j' ) == '1' ) { |
| 640 | $html .= '<div class="ma-month-label">' . OsUtilHelper::get_month_name_by_number( $day_date->format( 'n' ) ) . '</div>'; |
| 641 | } |
| 642 | $html .= '<div class="ma-day ma-day-number-' . $day_date->format( 'N' ) . '">'; |
| 643 | $html .= '<div class="ma-day-info">'; |
| 644 | $html .= '<span class="ma-day-number">' . $day_date->format( 'j' ) . '</span>'; |
| 645 | $html .= '<span class="ma-day-weekday">' . OsUtilHelper::get_weekday_name_by_number( $day_date->format( 'N' ), true ) . '</span>'; |
| 646 | $html .= '</div>'; |
| 647 | $html .= OsTimelineHelper::availability_timeline( $booking_request, $settings['work_boundaries'], $resources[ $day_date->format( 'Y-m-d' ) ], [ 'book_on_click' => false ] ); |
| 648 | $html .= '</div>'; |
| 649 | } |
| 650 | |
| 651 | return $html; |
| 652 | } |
| 653 | |
| 654 | public static function count_pending_bookings() { |
| 655 | $bookings = new OsBookingModel(); |
| 656 | |
| 657 | return $bookings->filter_allowed_records()->where( [ 'status IN' => OsBookingHelper::get_booking_statuses_for_pending_page() ] )->count(); |
| 658 | } |
| 659 | |
| 660 | |
| 661 | public static function generate_bundles_folder( ?array $bundles = null, array $restrictions = [] ): void { |
| 662 | if ( null === $bundles ) { |
| 663 | $bundles_model = new OsBundleModel(); |
| 664 | $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models(); |
| 665 | } |
| 666 | |
| 667 | /** |
| 668 | * Filters the list of bundles shown on the services step of the booking form. |
| 669 | * |
| 670 | * @since 5.6.3 |
| 671 | * @hook latepoint_booking_generate_bundles_folder |
| 672 | * |
| 673 | * @param {OsBundleModel[]} $bundles List of active, non-hidden bundles to be displayed |
| 674 | * |
| 675 | * @returns {OsBundleModel[]} Filtered list of bundles to display |
| 676 | */ |
| 677 | $bundles = apply_filters( 'latepoint_booking_generate_bundles_folder', $bundles ); |
| 678 | |
| 679 | if ( $bundles ) { |
| 680 | /** |
| 681 | * Whether to skip the "Bundle & Save" folder header and render bundle |
| 682 | * tiles directly (without a collapsible category wrapper). |
| 683 | * |
| 684 | * @param bool $skip Whether to skip the folder wrapper. Default false. |
| 685 | * @param array $restrictions Active booking-form restrictions. |
| 686 | * |
| 687 | * @hook latepoint_bundles_folder_skip_wrapper |
| 688 | */ |
| 689 | $skip_folder_wrapper = (bool) apply_filters( 'latepoint_bundles_folder_skip_wrapper', false, $restrictions ); |
| 690 | |
| 691 | if ( ! $skip_folder_wrapper ) { |
| 692 | ?> |
| 693 | <div class="os-item-category-w os-items os-as-rows os-animated-child"> |
| 694 | <div class="os-item-category-info-w os-item os-animated-self with-plus"> |
| 695 | <div class="os-item-category-info os-item-i" tabindex="0"> |
| 696 | <div class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></div> |
| 697 | <div class="os-item-name-w"> |
| 698 | <div class="os-item-name"><?php echo esc_html__( 'Bundle & Save', 'latepoint' ); ?></div> |
| 699 | </div> |
| 700 | <?php if ( OsSettingsHelper::is_on( 'show_service_categories_count' ) && count( $bundles ) ) { ?> |
| 701 | <div class="os-item-child-count"> |
| 702 | <span><?php echo count( $bundles ); ?></span> <?php esc_html_e( 'Bundles', 'latepoint' ); ?> |
| 703 | </div> |
| 704 | <?php } ?> |
| 705 | </div> |
| 706 | </div> |
| 707 | <?php |
| 708 | } |
| 709 | ?> |
| 710 | <div class="os-bundles os-animated-parent os-items os-as-rows os-selectable-items"> |
| 711 | <?php |
| 712 | foreach ( $bundles as $bundle ) { ?> |
| 713 | <div class="os-animated-child os-item os-selectable-item <?php echo ( $bundle->charge_amount ) ? 'os-priced-item' : ''; ?> <?php if ( $bundle->short_description ) { |
| 714 | echo 'with-description'; } ?>" |
| 715 | tabindex="0" |
| 716 | data-item-price="<?php echo esc_attr( $bundle->charge_amount ); ?>" |
| 717 | data-priced-item-type="bundle" |
| 718 | data-summary-field-name="bundle" |
| 719 | data-summary-value="<?php echo esc_attr( $bundle->name ); ?>" |
| 720 | data-item-id="<?php echo esc_attr( $bundle->id ); ?>" |
| 721 | data-cart-item-item-data-key="bundle_id" |
| 722 | data-os-call-func="latepoint_bundle_selected"> |
| 723 | <div class="os-service-selector os-item-i os-animated-self" |
| 724 | data-bundle-id="<?php echo esc_attr( $bundle->id ); ?>"> |
| 725 | <span class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></span> |
| 726 | <span class="os-item-name-w"> |
| 727 | <span class="os-item-name"><?php echo esc_html( $bundle->name ); ?></span> |
| 728 | <?php if ( $bundle->short_description ) { ?> |
| 729 | <span class="os-item-desc"><?php echo wp_kses_post( $bundle->short_description ); ?></span> |
| 730 | <?php } ?> |
| 731 | </span> |
| 732 | |
| 733 | <?php if ( $bundle->price_min > 0 ) { ?> |
| 734 | <span class="os-item-price-w"> |
| 735 | <span class="os-item-price"> |
| 736 | <?php |
| 737 | /** |
| 738 | * Filters the display price value shown on the bundle tile on a booking form |
| 739 | * |
| 740 | * @since 5.1.94 |
| 741 | * @hook latepoint_booking_form_display_bundle_price |
| 742 | * |
| 743 | * @param {string} $price displayed price that will be outputted |
| 744 | * @param {OsBundleModel} $bundle Bundle that the price is displayed for |
| 745 | * |
| 746 | * @returns {string} Filtered displayed price |
| 747 | */ |
| 748 | $display_price = apply_filters( 'latepoint_booking_form_display_bundle_price', $bundle->price_min_formatted, $bundle ); |
| 749 | echo esc_html( $display_price ); ?> |
| 750 | </span> |
| 751 | <?php if ( $bundle->price_min != $bundle->price_max ) { ?> |
| 752 | <span class="os-item-price-label"><?php esc_html_e( 'Starts From', 'latepoint' ); ?></span> |
| 753 | <?php } ?> |
| 754 | </span> |
| 755 | <?php } elseif ( $bundle->charge_amount > 0 ) { ?> |
| 756 | <span class="os-item-price-w"> |
| 757 | <span class="os-item-price"> |
| 758 | <?php echo esc_html( OsMoneyHelper::format_price( $bundle->charge_amount ) ); ?> |
| 759 | </span> |
| 760 | </span> |
| 761 | <?php } ?> |
| 762 | </div> |
| 763 | </div> |
| 764 | <?php |
| 765 | } |
| 766 | ?> |
| 767 | </div> |
| 768 | <?php |
| 769 | if ( ! $skip_folder_wrapper ) { |
| 770 | echo '</div>'; |
| 771 | } |
| 772 | } |
| 773 | } |
| 774 | |
| 775 | public static function generate_services_list( $services = false, $preselected_service = false ) { |
| 776 | if ( $services && is_array( $services ) && ! empty( $services ) ) { ?> |
| 777 | <div class="os-services os-animated-parent os-items os-as-rows os-selectable-items"> |
| 778 | <?php foreach ( $services as $service ) { |
| 779 | // if service is preselected - only output that service, skip the rest |
| 780 | if ( $preselected_service && $service->id != $preselected_service->id ) { |
| 781 | continue; |
| 782 | } |
| 783 | $service_durations = $service->get_all_durations_arr(); |
| 784 | $is_priced = ( ! ( count( $service_durations ) > 1 ) && $service->charge_amount ) ? true : false; |
| 785 | ?> |
| 786 | <div class="os-animated-child os-item os-selectable-item <?php echo ( $preselected_service && $service->id == $preselected_service->id ) ? 'selected is-preselected' : ''; ?> <?php echo ( $is_priced ) ? 'os-priced-item' : ''; ?> <?php if ( $service->short_description ) { |
| 787 | echo 'with-description'; } ?>" |
| 788 | tabindex="0" |
| 789 | data-item-price="<?php echo esc_attr( $service->charge_amount ); ?>" |
| 790 | data-priced-item-type="service" |
| 791 | data-summary-field-name="service" |
| 792 | data-summary-value="<?php echo esc_attr( $service->name ); ?>" |
| 793 | data-item-id="<?php echo esc_attr( $service->id ); ?>" |
| 794 | data-cart-item-item-data-key="service_id" |
| 795 | data-os-call-func="latepoint_service_selected" |
| 796 | data-id-holder=".latepoint_service_id"> |
| 797 | <div class="os-service-selector os-item-i os-animated-self" data-service-id="<?php echo esc_attr( $service->id ); ?>"> |
| 798 | <?php if ( $service->selection_image_id ) { ?> |
| 799 | <span class="os-item-img-w" style="background-image: url(<?php echo esc_url( $service->selection_image_url ); ?>);"></span> |
| 800 | <?php } ?> |
| 801 | <span class="os-item-name-w"> |
| 802 | <span class="os-item-name"><?php echo esc_html( $service->name ); ?></span> |
| 803 | <?php if ( $service->short_description ) { ?> |
| 804 | <span class="os-item-desc"><?php echo wp_kses_post( $service->short_description ); ?></span> |
| 805 | <?php } ?> |
| 806 | </span> |
| 807 | <?php if ( $service->price_min > 0 ) { ?> |
| 808 | <span class="os-item-price-w"> |
| 809 | <span class="os-item-price"> |
| 810 | <?php |
| 811 | /** |
| 812 | * Filters the display price value shown on the service tile on a booking form |
| 813 | * |
| 814 | * @since 5.1.94 |
| 815 | * @hook latepoint_booking_form_display_service_price |
| 816 | * |
| 817 | * @param {string} $price displayed price that will be outputted |
| 818 | * @param {OsServiceModel} $service Service that the price is displayed for |
| 819 | * |
| 820 | * @returns {string} Filtered displayed price |
| 821 | */ |
| 822 | $display_price = apply_filters( 'latepoint_booking_form_display_service_price', $service->price_min_formatted, $service ); |
| 823 | echo esc_html( $display_price ) ?> |
| 824 | </span> |
| 825 | <?php if ( $service->price_min != $service->price_max ) { ?> |
| 826 | <span class="os-item-price-label"><?php esc_html_e( 'Starts From', 'latepoint' ); ?></span> |
| 827 | <?php } ?> |
| 828 | </span> |
| 829 | <?php } ?> |
| 830 | </div> |
| 831 | </div> |
| 832 | <?php } ?> |
| 833 | </div> |
| 834 | <?php } |
| 835 | } |
| 836 | |
| 837 | public static function generate_services_bundles_and_categories_list( $parent_id = false, array $settings = [] ) { |
| 838 | $default_settings = [ |
| 839 | 'show_service_categories_arr' => false, |
| 840 | 'show_services_arr' => false, |
| 841 | 'preselected_service' => false, |
| 842 | 'preselected_category' => false, |
| 843 | 'bundles' => null, |
| 844 | 'service_display_mode' => 'all', |
| 845 | ]; |
| 846 | $settings = array_merge( $default_settings, $settings ); |
| 847 | |
| 848 | if ( $settings['preselected_service'] ) { |
| 849 | OsBookingHelper::generate_services_list( [ $settings['preselected_service'] ], $settings['preselected_service'] ); |
| 850 | |
| 851 | return; |
| 852 | } |
| 853 | |
| 854 | $service_categories = new OsServiceCategoryModel(); |
| 855 | $args = array(); |
| 856 | if ( $settings['show_service_categories_arr'] && is_array( $settings['show_service_categories_arr'] ) ) { |
| 857 | if ( $parent_id ) { |
| 858 | $service_categories->where( [ 'parent_id' => $parent_id ] ); |
| 859 | } else { |
| 860 | if ( $settings['preselected_category'] ) { |
| 861 | $service_categories->where( [ 'id' => $settings['preselected_category'] ] ); |
| 862 | } else { |
| 863 | $service_categories->where_in( 'id', $settings['show_service_categories_arr'] ); |
| 864 | $service_categories->where( |
| 865 | [ |
| 866 | 'parent_id' => [ |
| 867 | 'OR' => [ |
| 868 | 'IS NULL', |
| 869 | ' NOT IN' => $settings['show_service_categories_arr'], |
| 870 | ], |
| 871 | ], |
| 872 | ] |
| 873 | ); |
| 874 | } |
| 875 | } |
| 876 | } else { |
| 877 | if ( $settings['preselected_category'] ) { |
| 878 | $service_categories->where( [ 'id' => $settings['preselected_category'] ] ); |
| 879 | } else { |
| 880 | $args['parent_id'] = $parent_id ? $parent_id : 'IS NULL'; |
| 881 | } |
| 882 | } |
| 883 | $service_categories = $service_categories->where( $args )->order_by( 'order_number asc' )->get_results_as_models(); |
| 884 | |
| 885 | $main_parent_class = ( $parent_id ) ? 'os-animated-parent' : 'os-item-categories-main-parent os-animated-parent'; |
| 886 | if ( ! $settings['preselected_category'] ) { |
| 887 | echo '<div class="os-item-categories-holder ' . esc_attr( $main_parent_class ) . '">'; |
| 888 | } |
| 889 | |
| 890 | // generate services that have no category |
| 891 | if ( $parent_id == false && $settings['preselected_category'] == false ) { ?> |
| 892 | <?php |
| 893 | $services_without_category = new OsServiceModel(); |
| 894 | if ( $settings['show_services_arr'] ) { |
| 895 | $services_without_category->where_in( 'id', $settings['show_services_arr'] ); |
| 896 | } |
| 897 | $services_without_category = $services_without_category->where( [ 'category_id' => 0 ] )->should_be_active()->should_not_be_hidden()->get_results_as_models(); |
| 898 | /** This filter is documented in lib/helpers/steps_helper.php */ |
| 899 | $services_without_category = apply_filters( 'latepoint_step_services', $services_without_category, $settings['bundles'], [ 'service_display_mode' => $settings['service_display_mode'] ] ); |
| 900 | if ( $services_without_category ) { |
| 901 | OsBookingHelper::generate_services_list( $services_without_category, false ); |
| 902 | } |
| 903 | } |
| 904 | |
| 905 | if ( is_array( $service_categories ) ) { |
| 906 | foreach ( $service_categories as $service_category ) { ?> |
| 907 | <?php |
| 908 | $services = []; |
| 909 | $category_services = $service_category->get_active_services(); |
| 910 | if ( is_array( $category_services ) ) { |
| 911 | // if show selected services restriction is set - filter |
| 912 | if ( $settings['show_services_arr'] ) { |
| 913 | foreach ( $category_services as $category_service ) { |
| 914 | if ( in_array( $category_service->id, $settings['show_services_arr'] ) ) { |
| 915 | $services[] = $category_service; |
| 916 | } |
| 917 | } |
| 918 | } else { |
| 919 | $services = $category_services; |
| 920 | } |
| 921 | } |
| 922 | /** This filter is documented in lib/helpers/steps_helper.php */ |
| 923 | $services = apply_filters( 'latepoint_step_services', $services, $settings['bundles'], [ 'service_display_mode' => $settings['service_display_mode'] ] ); |
| 924 | $child_categories = new OsServiceCategoryModel(); |
| 925 | $count_child_categories = $child_categories->where( [ 'parent_id' => $service_category->id ] )->count(); |
| 926 | // show only if it has either at least one child category or service |
| 927 | if ( $count_child_categories || count( $services ) ) { |
| 928 | // preselected category, just show contents, not the wrapper |
| 929 | if ( $service_category->id == $settings['preselected_category'] ) { |
| 930 | OsBookingHelper::generate_services_list( $services, false ); |
| 931 | OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) ); |
| 932 | } else { ?> |
| 933 | <div class="os-item-category-w os-items os-as-rows os-animated-child" |
| 934 | data-id="<?php echo esc_attr( $service_category->id ); ?>"> |
| 935 | <div class="os-item-category-info-w os-item os-animated-self with-plus"> |
| 936 | <div class="os-item-category-info os-item-i"> |
| 937 | <div class="os-item-img-w" |
| 938 | style="background-image: url(<?php echo esc_url( $service_category->selection_image_url ); ?>);"></div> |
| 939 | <div class="os-item-name-w"> |
| 940 | <div class="os-item-name"><?php echo esc_html( $service_category->name ); ?></div> |
| 941 | <?php if ( ! empty( $service_category->short_description ) ) { ?> |
| 942 | <div class="os-item-desc"><?php echo $service_category->short_description; ?></div> |
| 943 | <?php } ?> |
| 944 | </div> |
| 945 | <?php if ( OsSettingsHelper::is_on( 'show_service_categories_count' ) && count( $services ) ) { ?> |
| 946 | <div class="os-item-child-count"> |
| 947 | <span><?php echo count( $services ); ?></span> <?php esc_html_e( 'Services', 'latepoint' ); ?> |
| 948 | </div> |
| 949 | <?php } ?> |
| 950 | </div> |
| 951 | </div> |
| 952 | <?php OsBookingHelper::generate_services_list( $services, false ); ?> |
| 953 | <?php OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) ); ?> |
| 954 | </div><?php |
| 955 | } |
| 956 | } |
| 957 | } |
| 958 | } |
| 959 | if ( ! $settings['preselected_category'] && ! $parent_id ) { |
| 960 | $category_bundles = $settings['bundles']; |
| 961 | if ( null === $category_bundles ) { |
| 962 | $bundles_model = new OsBundleModel(); |
| 963 | $category_bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models(); |
| 964 | /** This filter is documented in lib/helpers/steps_helper.php */ |
| 965 | $category_bundles = apply_filters( 'latepoint_step_bundles', $category_bundles, [], [ 'service_display_mode' => $settings['service_display_mode'] ] ); |
| 966 | } |
| 967 | OsBookingHelper::generate_bundles_folder( $category_bundles, [ 'service_display_mode' => $settings['service_display_mode'] ] ); |
| 968 | } |
| 969 | if ( ! $settings['preselected_category'] ) { |
| 970 | echo '</div>'; |
| 971 | } |
| 972 | } |
| 973 | |
| 974 | public static function group_booking_btn_html( $booking_id = false ) { |
| 975 | $html = 'data-os-params="' . esc_attr( http_build_query( [ 'booking_id' => $booking_id ] ) ) . '" |
| 976 | data-os-action="' . esc_attr( OsRouterHelper::build_route_name( 'bookings', 'grouped_bookings_quick_view' ) ) . '" |
| 977 | data-os-output-target="lightbox" |
| 978 | data-os-lightbox-classes="width-500" |
| 979 | data-os-after-call="latepoint_init_grouped_bookings_form"'; |
| 980 | |
| 981 | return $html; |
| 982 | } |
| 983 | |
| 984 | public static function quick_booking_btn_html( $booking_id = false, $params = array() ) { |
| 985 | $html = ''; |
| 986 | if ( $booking_id ) { |
| 987 | $params['booking_id'] = $booking_id; |
| 988 | } |
| 989 | $route = OsRouterHelper::build_route_name( 'orders', 'quick_edit' ); |
| 990 | |
| 991 | $params_str = http_build_query( $params ); |
| 992 | $html = 'data-os-params="' . esc_attr( $params_str ) . '" |
| 993 | data-os-action="' . esc_attr( $route ) . '" |
| 994 | data-os-output-target="side-panel" |
| 995 | data-os-after-call="latepoint_init_quick_order_form"'; |
| 996 | |
| 997 | return $html; |
| 998 | } |
| 999 | |
| 1000 | |
| 1001 | /** |
| 1002 | * @param OsBookingModel $booking |
| 1003 | * |
| 1004 | * @return false|mixed |
| 1005 | * |
| 1006 | * Search for available location based on booking requirements. Will return false if no available location found. |
| 1007 | */ |
| 1008 | public static function get_any_location_for_booking_by_rule( OsBookingModel $booking ) { |
| 1009 | // ANY LOCATION SELECTED |
| 1010 | // get available locations |
| 1011 | $connected_ids = OsLocationHelper::get_location_ids_for_service_and_agent( $booking->service_id, $booking->agent_id ); |
| 1012 | |
| 1013 | // If date/time is selected - filter locations who are available at that time |
| 1014 | if ( $booking->start_date && $booking->start_time ) { |
| 1015 | $available_location_ids = []; |
| 1016 | $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ); |
| 1017 | foreach ( $connected_ids as $location_id ) { |
| 1018 | $booking_request->location_id = $location_id; |
| 1019 | if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) { |
| 1020 | $available_location_ids[] = $location_id; |
| 1021 | } |
| 1022 | } |
| 1023 | $connected_ids = array_intersect( $available_location_ids, $connected_ids ); |
| 1024 | } |
| 1025 | |
| 1026 | |
| 1027 | $locations_model = new OsLocationModel(); |
| 1028 | if ( ! empty( $connected_ids ) ) { |
| 1029 | $locations_model->where_in( 'id', $connected_ids ); |
| 1030 | $locations = $locations_model->should_be_active()->get_results_as_models(); |
| 1031 | } else { |
| 1032 | $locations = []; |
| 1033 | } |
| 1034 | |
| 1035 | if ( empty( $locations ) ) { |
| 1036 | return false; |
| 1037 | } |
| 1038 | |
| 1039 | $selected_location_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ]; |
| 1040 | $booking->location_id = $selected_location_id; |
| 1041 | |
| 1042 | return $selected_location_id; |
| 1043 | } |
| 1044 | |
| 1045 | /** |
| 1046 | * @param OsBookingModel $booking |
| 1047 | * |
| 1048 | * @return false|mixed |
| 1049 | * |
| 1050 | * Search for available agent based on booking requirements and agent picking preferences. Will return false if no available agent found. |
| 1051 | */ |
| 1052 | public static function get_any_agent_for_booking_by_rule( OsBookingModel $booking ) { |
| 1053 | // ANY AGENT SELECTED |
| 1054 | // get available agents |
| 1055 | $connected_ids = OsAgentHelper::get_agent_ids_for_service_and_location( $booking->service_id, $booking->location_id ); |
| 1056 | |
| 1057 | // If date/time is selected - filter agents who are available at that time |
| 1058 | if ( $booking->start_date && $booking->start_time ) { |
| 1059 | $available_agent_ids = []; |
| 1060 | $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ); |
| 1061 | foreach ( $connected_ids as $agent_id ) { |
| 1062 | $booking_request->agent_id = $agent_id; |
| 1063 | if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) { |
| 1064 | $available_agent_ids[] = $agent_id; |
| 1065 | } |
| 1066 | } |
| 1067 | $connected_ids = array_intersect( $available_agent_ids, $connected_ids ); |
| 1068 | } |
| 1069 | |
| 1070 | |
| 1071 | /** |
| 1072 | * Get IDs of agents that are eligible to be assigned a booking that has "ANY" agent pre-selected |
| 1073 | * |
| 1074 | * @param {array} $connected_ids Array of eligible Agent IDs |
| 1075 | * @param {OsBookingModel} $booking Booking that needs agent ID |
| 1076 | * |
| 1077 | * @returns {array} Filtered array of IDs of eligible agents |
| 1078 | * @since 4.7.6 |
| 1079 | * @hook latepoint_agent_ids_assignable_to_any_agent_booking |
| 1080 | * |
| 1081 | */ |
| 1082 | $connected_ids = apply_filters( 'latepoint_agent_ids_assignable_to_any_agent_booking', $connected_ids, $booking ); |
| 1083 | |
| 1084 | if ( ! empty( $connected_ids ) ) { |
| 1085 | $agents_model = new OsAgentModel(); |
| 1086 | $agents_model->where_in( 'id', $connected_ids ); |
| 1087 | $agents = $agents_model->should_be_active()->get_results_as_models(); |
| 1088 | } else { |
| 1089 | $agents = []; |
| 1090 | } |
| 1091 | |
| 1092 | if ( empty( $agents ) ) { |
| 1093 | return false; |
| 1094 | } |
| 1095 | |
| 1096 | |
| 1097 | $selected_agent_id = false; |
| 1098 | $agent_order_rule = OsSettingsHelper::get_any_agent_order(); |
| 1099 | switch ( $agent_order_rule ) { |
| 1100 | case LATEPOINT_ANY_AGENT_ORDER_RANDOM: |
| 1101 | $selected_agent_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ]; |
| 1102 | break; |
| 1103 | case LATEPOINT_ANY_AGENT_ORDER_PRICE_HIGH: |
| 1104 | $highest_price = false; |
| 1105 | foreach ( $agents as $agent ) { |
| 1106 | $booking->agent_id = $agent->id; |
| 1107 | $price = OsBookingHelper::calculate_full_amount_for_booking( $booking ); |
| 1108 | if ( $highest_price === false && $selected_agent_id === false ) { |
| 1109 | $highest_price = $price; |
| 1110 | $selected_agent_id = $agent->id; |
| 1111 | } else { |
| 1112 | if ( $highest_price < $price ) { |
| 1113 | $highest_price = $price; |
| 1114 | $selected_agent_id = $agent->id; |
| 1115 | } |
| 1116 | } |
| 1117 | } |
| 1118 | break; |
| 1119 | case LATEPOINT_ANY_AGENT_ORDER_PRICE_LOW: |
| 1120 | $lowest_price = false; |
| 1121 | foreach ( $agents as $agent ) { |
| 1122 | $booking->agent_id = $agent->id; |
| 1123 | $price = OsBookingHelper::calculate_full_amount_for_booking( $booking ); |
| 1124 | if ( $lowest_price === false && $selected_agent_id === false ) { |
| 1125 | $lowest_price = $price; |
| 1126 | $selected_agent_id = $agent->id; |
| 1127 | } else { |
| 1128 | if ( $lowest_price > $price ) { |
| 1129 | $lowest_price = $price; |
| 1130 | $selected_agent_id = $agent->id; |
| 1131 | } |
| 1132 | } |
| 1133 | } |
| 1134 | break; |
| 1135 | case LATEPOINT_ANY_AGENT_ORDER_BUSY_HIGH: |
| 1136 | $max_bookings = false; |
| 1137 | foreach ( $agents as $agent ) { |
| 1138 | $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] ); |
| 1139 | if ( $max_bookings === false && $selected_agent_id === false ) { |
| 1140 | $max_bookings = $agent_total_bookings; |
| 1141 | $selected_agent_id = $agent->id; |
| 1142 | } else { |
| 1143 | if ( $max_bookings < $agent_total_bookings ) { |
| 1144 | $max_bookings = $agent_total_bookings; |
| 1145 | $selected_agent_id = $agent->id; |
| 1146 | } |
| 1147 | } |
| 1148 | } |
| 1149 | break; |
| 1150 | case LATEPOINT_ANY_AGENT_ORDER_BUSY_LOW: |
| 1151 | $min_bookings = false; |
| 1152 | foreach ( $agents as $agent ) { |
| 1153 | $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] ); |
| 1154 | if ( $min_bookings === false && $selected_agent_id === false ) { |
| 1155 | $min_bookings = $agent_total_bookings; |
| 1156 | $selected_agent_id = $agent->id; |
| 1157 | } else { |
| 1158 | if ( $min_bookings > $agent_total_bookings ) { |
| 1159 | $min_bookings = $agent_total_bookings; |
| 1160 | $selected_agent_id = $agent->id; |
| 1161 | } |
| 1162 | } |
| 1163 | } |
| 1164 | break; |
| 1165 | } |
| 1166 | /** |
| 1167 | * Get ID of agent that will be assigned to a booking, depending on order rules, where agent is set to ANY |
| 1168 | * |
| 1169 | * @param {integer} $selected_agent_id Currently selected agent ID |
| 1170 | * @param {OsAgentModel[]} $agents Array of eligible agent models to pick from |
| 1171 | * @param {OsBookingModel} $booking Booking that needs agent ID |
| 1172 | * @param {string} $agent_order_rule Rule of agent ordering |
| 1173 | * |
| 1174 | * @returns {integer} ID of the agent that will be assigned to this booking |
| 1175 | * @since 4.7.6 |
| 1176 | * @hook latepoint_get_any_agent_id_for_booking_by_rule |
| 1177 | * |
| 1178 | */ |
| 1179 | $selected_agent_id = apply_filters( 'latepoint_get_any_agent_id_for_booking_by_rule', $selected_agent_id, $agents, $booking, $agent_order_rule ); |
| 1180 | $booking->agent_id = $selected_agent_id; |
| 1181 | |
| 1182 | return $selected_agent_id; |
| 1183 | } |
| 1184 | |
| 1185 | |
| 1186 | public static function get_total_bookings_for_date( $date, $conditions = [], $grouped = false ) { |
| 1187 | $args = [ 'start_date' => $date ]; |
| 1188 | if ( isset( $conditions['agent_id'] ) && $conditions['agent_id'] ) { |
| 1189 | $args['agent_id'] = $conditions['agent_id']; |
| 1190 | } |
| 1191 | if ( isset( $conditions['service_id'] ) && $conditions['service_id'] ) { |
| 1192 | $args['service_id'] = $conditions['service_id']; |
| 1193 | } |
| 1194 | if ( isset( $conditions['location_id'] ) && $conditions['location_id'] ) { |
| 1195 | $args['location_id'] = $conditions['location_id']; |
| 1196 | } |
| 1197 | |
| 1198 | |
| 1199 | $bookings = new OsBookingModel(); |
| 1200 | if ( $grouped ) { |
| 1201 | $bookings->group_by( 'start_date, start_time, end_time, service_id, location_id' ); |
| 1202 | } |
| 1203 | $bookings = $bookings->where( $args ); |
| 1204 | |
| 1205 | return $bookings->count(); |
| 1206 | } |
| 1207 | |
| 1208 | |
| 1209 | /** |
| 1210 | * |
| 1211 | * Get list of statuses that block timeslot availability |
| 1212 | * |
| 1213 | * @return array |
| 1214 | */ |
| 1215 | public static function get_timeslot_blocking_statuses(): array { |
| 1216 | $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'timeslot_blocking_statuses', '' ) ); |
| 1217 | |
| 1218 | /** |
| 1219 | * Get list of statuses that block timeslot availability |
| 1220 | * |
| 1221 | * @param {array} $statuses array of status codes that block timeslot availability |
| 1222 | * @returns {array} The filtered array of status codes |
| 1223 | * |
| 1224 | * @since 4.7.0 |
| 1225 | * @hook latepoint_get_timeslot_blocking_statuses |
| 1226 | * |
| 1227 | */ |
| 1228 | return apply_filters( 'latepoint_get_timeslot_blocking_statuses', $statuses ); |
| 1229 | } |
| 1230 | |
| 1231 | |
| 1232 | /** |
| 1233 | * |
| 1234 | * Get list of statuses that appear on pending page |
| 1235 | * |
| 1236 | * @return array |
| 1237 | */ |
| 1238 | public static function get_booking_statuses_for_pending_page(): array { |
| 1239 | $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'need_action_statuses', '' ) ); |
| 1240 | |
| 1241 | /** |
| 1242 | * Get list of statuses that appear on pending page |
| 1243 | * |
| 1244 | * @param {array} $statuses array of status codes that appear on pending page |
| 1245 | * @returns {array} The filtered array of status codes |
| 1246 | * |
| 1247 | * @since 4.7.0 |
| 1248 | * @hook latepoint_get_booking_statuses_for_pending_page |
| 1249 | * |
| 1250 | */ |
| 1251 | return apply_filters( 'latepoint_get_booking_statuses_for_pending_page', $statuses ); |
| 1252 | } |
| 1253 | |
| 1254 | /** |
| 1255 | * |
| 1256 | * Get list of statuses that are not cancelled |
| 1257 | * |
| 1258 | * @return array |
| 1259 | */ |
| 1260 | public static function get_non_cancelled_booking_statuses(): array { |
| 1261 | $statuses = self::get_statuses_list(); |
| 1262 | if ( isset( $statuses[ LATEPOINT_BOOKING_STATUS_CANCELLED ] ) ) { |
| 1263 | unset( $statuses[ LATEPOINT_BOOKING_STATUS_CANCELLED ] ); |
| 1264 | } |
| 1265 | $statuses = array_keys( $statuses ); |
| 1266 | |
| 1267 | /** |
| 1268 | * Get list of statuses that are not cancelled |
| 1269 | * |
| 1270 | * @param {array} $statuses array of status codes that are not cancelled |
| 1271 | * @returns {array} The filtered array of status codes |
| 1272 | * |
| 1273 | * @since 5.0.5 |
| 1274 | * @hook get_non_cancelled_booking_statuses |
| 1275 | * |
| 1276 | */ |
| 1277 | return apply_filters( 'get_non_cancelled_booking_statuses', $statuses ); |
| 1278 | } |
| 1279 | |
| 1280 | |
| 1281 | public static function get_default_booking_status( $service_id = false ) { |
| 1282 | if ( $service_id ) { |
| 1283 | $service = new OsServiceModel( $service_id ); |
| 1284 | if ( $service && ! empty( $service->id ) ) { |
| 1285 | return $service->get_default_booking_status(); |
| 1286 | } |
| 1287 | } |
| 1288 | $default_status = OsSettingsHelper::get_settings_value( 'default_booking_status' ); |
| 1289 | if ( $default_status ) { |
| 1290 | return $default_status; |
| 1291 | } else { |
| 1292 | return LATEPOINT_BOOKING_STATUS_APPROVED; |
| 1293 | } |
| 1294 | } |
| 1295 | |
| 1296 | |
| 1297 | public static function change_booking_status( $booking_id, $new_status ) { |
| 1298 | $booking = new OsBookingModel( $booking_id ); |
| 1299 | if ( ! $booking_id || ! $booking ) { |
| 1300 | return false; |
| 1301 | } |
| 1302 | |
| 1303 | if ( $new_status == $booking->status ) { |
| 1304 | return true; |
| 1305 | } else { |
| 1306 | $old_booking = clone $booking; |
| 1307 | if ( $booking->update_status( $new_status ) ) { |
| 1308 | do_action( 'latepoint_booking_updated', $booking, $old_booking ); |
| 1309 | |
| 1310 | return true; |
| 1311 | } else { |
| 1312 | return false; |
| 1313 | } |
| 1314 | } |
| 1315 | } |
| 1316 | |
| 1317 | |
| 1318 | /** |
| 1319 | * @param \LatePoint\Misc\BookingRequest $booking_request |
| 1320 | * @param \LatePoint\Misc\BookedPeriod[] |
| 1321 | * @param int $capacity |
| 1322 | * |
| 1323 | * @return bool |
| 1324 | */ |
| 1325 | public static function is_timeframe_in_booked_periods( \LatePoint\Misc\BookingRequest $booking_request, array $booked_periods, OsServiceModel $service ): bool { |
| 1326 | if ( empty( $booked_periods ) ) { |
| 1327 | return false; |
| 1328 | } |
| 1329 | $count_existing_attendees = 0; |
| 1330 | foreach ( $booked_periods as $period ) { |
| 1331 | if ( self::is_period_overlapping( $booking_request->get_start_time_with_buffer(), $booking_request->get_end_time_with_buffer(), $period->start_time_with_buffer(), $period->end_time_with_buffer() ) ) { |
| 1332 | // if it's the same service overlapping - count how many times |
| 1333 | // TODO maybe add an option to toggle on/off ability to share a timeslot capacity between two different services |
| 1334 | if ( $booking_request->service_id == $period->service_id ) { |
| 1335 | $count_existing_attendees += $period->total_attendees; |
| 1336 | } else { |
| 1337 | return true; |
| 1338 | } |
| 1339 | } |
| 1340 | } |
| 1341 | if ( $count_existing_attendees > 0 ) { |
| 1342 | // if there are attendees, check if they are below minimum need for timeslot to be blocked, if they are - then the slot is considered booked |
| 1343 | if ( ( $count_existing_attendees + $booking_request->total_attendees ) <= $service->get_capacity_needed_before_slot_is_blocked() ) { |
| 1344 | return false; |
| 1345 | } else { |
| 1346 | return true; |
| 1347 | } |
| 1348 | } else { |
| 1349 | // no attendees in the overlapping booked periods yet, just check if the requested number of attendees is within the service capacity |
| 1350 | if ( $booking_request->total_attendees <= $service->capacity_max ) { |
| 1351 | return false; |
| 1352 | } else { |
| 1353 | return true; |
| 1354 | } |
| 1355 | } |
| 1356 | } |
| 1357 | |
| 1358 | |
| 1359 | public static function is_period_overlapping( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) { |
| 1360 | // https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap/ |
| 1361 | return ( ( $period_one_start < $period_two_end ) && ( $period_two_start < $period_one_end ) ); |
| 1362 | } |
| 1363 | |
| 1364 | public static function is_period_inside_another( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) { |
| 1365 | return ( ( $period_one_start >= $period_two_start ) && ( $period_one_end <= $period_two_end ) ); |
| 1366 | } |
| 1367 | |
| 1368 | // args = [agent_id, 'service_id', 'location_id'] |
| 1369 | public static function get_bookings_for_date( $date, $args = [] ) { |
| 1370 | $bookings = new OsBookingModel(); |
| 1371 | $args['start_date'] = $date; |
| 1372 | // if any of these are false or 0 - remove it from arguments list |
| 1373 | if ( isset( $args['location_id'] ) && empty( $args['location_id'] ) ) { |
| 1374 | unset( $args['location_id'] ); |
| 1375 | } |
| 1376 | if ( isset( $args['agent_id'] ) && empty( $args['agent_id'] ) ) { |
| 1377 | unset( $args['agent_id'] ); |
| 1378 | } |
| 1379 | if ( isset( $args['service_id'] ) && empty( $args['service_id'] ) ) { |
| 1380 | unset( $args['service_id'] ); |
| 1381 | } |
| 1382 | |
| 1383 | $bookings->where( $args )->order_by( 'start_time asc, end_time asc, service_id asc' ); |
| 1384 | |
| 1385 | return $bookings->get_results_as_models(); |
| 1386 | } |
| 1387 | |
| 1388 | /** |
| 1389 | * @param \LatePoint\Misc\Filter $filter |
| 1390 | * |
| 1391 | * @return int |
| 1392 | */ |
| 1393 | public static function count_bookings( \LatePoint\Misc\Filter $filter ) { |
| 1394 | $bookings = new OsBookingModel(); |
| 1395 | $query_args = []; |
| 1396 | if ( $filter->date_from ) { |
| 1397 | $query_args['start_date'] = $filter->date_from; |
| 1398 | } |
| 1399 | if ( $filter->location_id ) { |
| 1400 | $query_args['location_id'] = $filter->location_id; |
| 1401 | } |
| 1402 | if ( $filter->agent_id ) { |
| 1403 | $query_args['agent_id'] = $filter->agent_id; |
| 1404 | } |
| 1405 | if ( $filter->service_id ) { |
| 1406 | $query_args['service_id'] = $filter->service_id; |
| 1407 | } |
| 1408 | |
| 1409 | return $bookings->should_not_be_cancelled()->where( $query_args )->count(); |
| 1410 | } |
| 1411 | |
| 1412 | |
| 1413 | public static function get_nice_status_name( $status ) { |
| 1414 | $statuses_list = OsBookingHelper::get_statuses_list(); |
| 1415 | if ( $status && isset( $statuses_list[ $status ] ) ) { |
| 1416 | return $statuses_list[ $status ]; |
| 1417 | } else { |
| 1418 | return __( 'Undefined Status', 'latepoint' ); |
| 1419 | } |
| 1420 | } |
| 1421 | |
| 1422 | |
| 1423 | public static function get_statuses_list() { |
| 1424 | $statuses = [ |
| 1425 | LATEPOINT_BOOKING_STATUS_APPROVED => __( 'Approved', 'latepoint' ), |
| 1426 | LATEPOINT_BOOKING_STATUS_PENDING => __( 'Pending Approval', 'latepoint' ), |
| 1427 | LATEPOINT_BOOKING_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ), |
| 1428 | LATEPOINT_BOOKING_STATUS_NO_SHOW => __( 'No Show', 'latepoint' ), |
| 1429 | LATEPOINT_BOOKING_STATUS_COMPLETED => __( 'Completed', 'latepoint' ), |
| 1430 | ]; |
| 1431 | $additional_statuses = array_map( 'trim', explode( ',', OsSettingsHelper::get_settings_value( 'additional_booking_statuses', '' ) ) ); |
| 1432 | if ( ! empty( $additional_statuses ) ) { |
| 1433 | foreach ( $additional_statuses as $status ) { |
| 1434 | if ( ! empty( $status ) ) { |
| 1435 | $statuses[ str_replace( ' ', '_', strtolower( $status ) ) ] = $status; |
| 1436 | } |
| 1437 | } |
| 1438 | } |
| 1439 | $statuses = apply_filters( 'latepoint_booking_statuses', $statuses ); |
| 1440 | |
| 1441 | return $statuses; |
| 1442 | } |
| 1443 | |
| 1444 | |
| 1445 | public static function get_weekdays_arr( $full_name = false ) { |
| 1446 | if ( $full_name ) { |
| 1447 | $weekdays = array( |
| 1448 | __( 'Monday', 'latepoint' ), |
| 1449 | __( 'Tuesday', 'latepoint' ), |
| 1450 | __( 'Wednesday', 'latepoint' ), |
| 1451 | __( 'Thursday', 'latepoint' ), |
| 1452 | __( 'Friday', 'latepoint' ), |
| 1453 | __( 'Saturday', 'latepoint' ), |
| 1454 | __( 'Sunday', 'latepoint' ), |
| 1455 | ); |
| 1456 | } else { |
| 1457 | $weekdays = array( |
| 1458 | __( 'Mon', 'latepoint' ), |
| 1459 | __( 'Tue', 'latepoint' ), |
| 1460 | __( 'Wed', 'latepoint' ), |
| 1461 | __( 'Thu', 'latepoint' ), |
| 1462 | __( 'Fri', 'latepoint' ), |
| 1463 | __( 'Sat', 'latepoint' ), |
| 1464 | __( 'Sun', 'latepoint' ), |
| 1465 | ); |
| 1466 | } |
| 1467 | |
| 1468 | return $weekdays; |
| 1469 | } |
| 1470 | |
| 1471 | public static function get_weekday_name_by_number( $weekday_number, $full_name = false ) { |
| 1472 | $weekdays = OsBookingHelper::get_weekdays_arr( $full_name ); |
| 1473 | if ( ! isset( $weekday_number ) || $weekday_number < 1 || $weekday_number > 7 ) { |
| 1474 | return ''; |
| 1475 | } else { |
| 1476 | return $weekdays[ $weekday_number - 1 ]; |
| 1477 | } |
| 1478 | } |
| 1479 | |
| 1480 | public static function get_stat( $stat, $args = [] ) { |
| 1481 | if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) { |
| 1482 | return false; |
| 1483 | } |
| 1484 | $defaults = [ |
| 1485 | 'customer_id' => false, |
| 1486 | 'agent_id' => false, |
| 1487 | 'service_id' => false, |
| 1488 | 'location_id' => false, |
| 1489 | 'date_from' => false, |
| 1490 | 'date_to' => false, |
| 1491 | 'group_by' => false, |
| 1492 | 'exclude_status' => false, |
| 1493 | ]; |
| 1494 | $args = array_merge( $defaults, $args ); |
| 1495 | $bookings = new OsBookingModel(); |
| 1496 | $query_args = array( $args['date_from'], $args['date_to'] ); |
| 1497 | switch ( $stat ) { |
| 1498 | case 'duration': |
| 1499 | $stat_query = 'SUM(end_time - start_time)'; |
| 1500 | break; |
| 1501 | case 'price': |
| 1502 | $stat_query = 'sum(total)'; |
| 1503 | break; |
| 1504 | case 'bookings': |
| 1505 | $stat_query = 'count(id)'; |
| 1506 | break; |
| 1507 | } |
| 1508 | $select_query = $stat_query . ' as stat'; |
| 1509 | if ( $args['group_by'] ) { |
| 1510 | $select_query .= ',' . $args['group_by']; |
| 1511 | } |
| 1512 | $bookings->select( $select_query ); |
| 1513 | |
| 1514 | |
| 1515 | if ( $args['date_from'] ) { |
| 1516 | $bookings->where( [ 'start_date >=' => $args['date_from'] ] ); |
| 1517 | } |
| 1518 | if ( $args['date_to'] ) { |
| 1519 | $bookings->where( [ 'start_date <=' => $args['date_to'] ] ); |
| 1520 | } |
| 1521 | if ( $args['service_id'] ) { |
| 1522 | $bookings->where( [ 'service_id' => $args['service_id'] ] ); |
| 1523 | } |
| 1524 | if ( $args['agent_id'] ) { |
| 1525 | $bookings->where( [ 'agent_id' => $args['agent_id'] ] ); |
| 1526 | } |
| 1527 | if ( $args['location_id'] ) { |
| 1528 | $bookings->where( [ 'location_id' => $args['location_id'] ] ); |
| 1529 | } |
| 1530 | if ( $args['customer_id'] ) { |
| 1531 | $bookings->where( [ 'customer_id' => $args['customer_id'] ] ); |
| 1532 | } |
| 1533 | if ( $args['group_by'] ) { |
| 1534 | $bookings->group_by( $args['group_by'] ); |
| 1535 | } |
| 1536 | // TODO, need to support custom status exclusions |
| 1537 | if ( $args['exclude_status'] == LATEPOINT_BOOKING_STATUS_CANCELLED ) { |
| 1538 | $bookings->should_not_be_cancelled(); |
| 1539 | } |
| 1540 | |
| 1541 | $stat_total = $bookings->get_results( ARRAY_A ); |
| 1542 | if ( $args['group_by'] ) { |
| 1543 | return $stat_total; |
| 1544 | } else { |
| 1545 | return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0; |
| 1546 | } |
| 1547 | } |
| 1548 | |
| 1549 | public static function get_new_customer_stat_for_period( DateTime $date_from, DateTime $date_to, \LatePoint\Misc\Filter $filter ) { |
| 1550 | // TODO make sure filter is respected |
| 1551 | $customers = new OsCustomerModel(); |
| 1552 | |
| 1553 | return $customers->filter_allowed_records()->where( |
| 1554 | [ |
| 1555 | 'created_at >=' => $date_from->format( 'Y-m-d' ), |
| 1556 | 'created_at <=' => $date_to->format( 'Y-m-d' ), |
| 1557 | ] |
| 1558 | )->count(); |
| 1559 | } |
| 1560 | |
| 1561 | public static function get_stat_for_period( $stat, $date_from, $date_to, \LatePoint\Misc\Filter $filter, $group_by = false ) { |
| 1562 | if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) { |
| 1563 | return false; |
| 1564 | } |
| 1565 | if ( ! in_array( $group_by, [ false, 'agent_id', 'service_id', 'location_id' ] ) ) { |
| 1566 | return false; |
| 1567 | } |
| 1568 | $bookings = new OsBookingModel(); |
| 1569 | switch ( $stat ) { |
| 1570 | case 'duration': |
| 1571 | $stat_query = 'SUM(end_time - start_time)'; |
| 1572 | break; |
| 1573 | case 'price': |
| 1574 | $stat_query = 'sum(' . LATEPOINT_TABLE_ORDER_ITEMS . '.total)'; |
| 1575 | $bookings->join( LATEPOINT_TABLE_ORDER_ITEMS, [ 'id' => $bookings->table_name . '.order_item_id' ] ); |
| 1576 | $bookings->join( LATEPOINT_TABLE_ORDERS, [ 'id' => LATEPOINT_TABLE_ORDER_ITEMS . '.order_id' ] ); |
| 1577 | break; |
| 1578 | case 'bookings': |
| 1579 | $stat_query = 'count(id)'; |
| 1580 | break; |
| 1581 | } |
| 1582 | $select_query = $stat_query . ' as stat'; |
| 1583 | if ( $group_by ) { |
| 1584 | $select_query .= ',' . $group_by; |
| 1585 | } |
| 1586 | $bookings->select( $select_query )->where( |
| 1587 | [ |
| 1588 | 'start_date >=' => $date_from, |
| 1589 | 'start_date <= ' => $date_to, |
| 1590 | ] |
| 1591 | ); |
| 1592 | |
| 1593 | if ( $filter->service_id ) { |
| 1594 | $bookings->where( [ 'service_id' => $filter->service_id ] ); |
| 1595 | } |
| 1596 | if ( $filter->agent_id ) { |
| 1597 | $bookings->where( [ 'agent_id' => $filter->agent_id ] ); |
| 1598 | } |
| 1599 | if ( $filter->location_id ) { |
| 1600 | $bookings->where( [ 'location_id' => $filter->location_id ] ); |
| 1601 | } |
| 1602 | |
| 1603 | $bookings->should_not_be_cancelled(); |
| 1604 | |
| 1605 | if ( $group_by ) { |
| 1606 | $bookings->group_by( $group_by ); |
| 1607 | } |
| 1608 | |
| 1609 | $stat_total = $bookings->get_results( ARRAY_A ); |
| 1610 | if ( $group_by ) { |
| 1611 | return $stat_total; |
| 1612 | } else { |
| 1613 | return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0; |
| 1614 | } |
| 1615 | } |
| 1616 | |
| 1617 | public static function get_total_bookings_per_day_for_period( $date_from, $date_to, \LatePoint\Misc\Filter $filter ) { |
| 1618 | $bookings = new OsBookingModel(); |
| 1619 | $bookings->select( 'count(id) as bookings_per_day, start_date' ) |
| 1620 | ->where( |
| 1621 | [ |
| 1622 | 'start_date >=' => $date_from, |
| 1623 | 'start_date <=' => $date_to, |
| 1624 | ] |
| 1625 | ) |
| 1626 | ->where( [ 'status NOT IN' => OsCalendarHelper::get_booking_statuses_hidden_from_calendar() ] ); |
| 1627 | if ( $filter->service_id ) { |
| 1628 | $bookings->where( [ 'service_id' => $filter->service_id ] ); |
| 1629 | } |
| 1630 | if ( $filter->agent_id ) { |
| 1631 | $bookings->where( [ 'agent_id' => $filter->agent_id ] ); |
| 1632 | } |
| 1633 | if ( $filter->location_id ) { |
| 1634 | $bookings->where( [ 'location_id' => $filter->location_id ] ); |
| 1635 | } |
| 1636 | $bookings->group_by( 'start_date' ); |
| 1637 | |
| 1638 | return $bookings->get_results(); |
| 1639 | } |
| 1640 | |
| 1641 | |
| 1642 | public static function get_min_max_work_periods( $specific_weekdays = false, $service_id = false, $agent_id = false ) { |
| 1643 | $select_string = 'MIN(start_time) as start_time, MAX(end_time) as end_time'; |
| 1644 | $work_periods = new OsWorkPeriodModel(); |
| 1645 | $work_periods = $work_periods->select( $select_string ); |
| 1646 | $query_args = array( |
| 1647 | 'service_id' => 0, |
| 1648 | 'agent_id' => 0, |
| 1649 | ); |
| 1650 | if ( $service_id ) { |
| 1651 | $query_args['service_id'] = $service_id; |
| 1652 | } |
| 1653 | if ( $agent_id ) { |
| 1654 | $query_args['agent_id'] = $agent_id; |
| 1655 | } |
| 1656 | if ( $specific_weekdays && ! empty( $specific_weekdays ) ) { |
| 1657 | $query_args['week_day'] = $specific_weekdays; |
| 1658 | } |
| 1659 | $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A ); |
| 1660 | if ( ( $service_id || $agent_id ) && empty( $results['min_start_time'] ) ) { |
| 1661 | if ( $service_id && empty( $results['min_start_time'] ) ) { |
| 1662 | $query_args['service_id'] = 0; |
| 1663 | $work_periods = new OsWorkPeriodModel(); |
| 1664 | $work_periods = $work_periods->select( $select_string ); |
| 1665 | $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A ); |
| 1666 | } |
| 1667 | if ( $agent_id && empty( $results['min_start_time'] ) ) { |
| 1668 | $query_args['agent_id'] = 0; |
| 1669 | $work_periods = new OsWorkPeriodModel(); |
| 1670 | $work_periods = $work_periods->select( $select_string ); |
| 1671 | $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A ); |
| 1672 | } |
| 1673 | } |
| 1674 | if ( $results ) { |
| 1675 | return array( $results['start_time'], $results['end_time'] ); |
| 1676 | } else { |
| 1677 | return false; |
| 1678 | } |
| 1679 | } |
| 1680 | |
| 1681 | |
| 1682 | public static function get_work_start_end_time_for_multiple_dates( $dates = false, $service_id = false, $agent_id = false ) { |
| 1683 | $specific_weekdays = array(); |
| 1684 | if ( $dates ) { |
| 1685 | foreach ( $dates as $date ) { |
| 1686 | $target_date = new OsWpDateTime( $date ); |
| 1687 | $weekday = $target_date->format( 'N' ); |
| 1688 | if ( ! in_array( $weekday, $specific_weekdays ) ) { |
| 1689 | $specific_weekdays[] = $weekday; |
| 1690 | } |
| 1691 | } |
| 1692 | } |
| 1693 | $work_minmax_start_end = self::get_min_max_work_periods( $specific_weekdays, $service_id, $agent_id ); |
| 1694 | |
| 1695 | return $work_minmax_start_end; |
| 1696 | } |
| 1697 | |
| 1698 | /** |
| 1699 | * @param int $minute |
| 1700 | * @param \LatePoint\Misc\WorkPeriod[] $work_periods_arr |
| 1701 | * |
| 1702 | * @return bool |
| 1703 | */ |
| 1704 | public static function is_minute_in_work_periods( int $minute, array $work_periods_arr ): bool { |
| 1705 | // print_r($work_periods_arr); |
| 1706 | if ( empty( $work_periods_arr ) ) { |
| 1707 | return false; |
| 1708 | } |
| 1709 | foreach ( $work_periods_arr as $work_period ) { |
| 1710 | // end of period does not count because we cant make appointment with 0 duration |
| 1711 | if ( $work_period->start_time <= $minute && $work_period->end_time > $minute ) { |
| 1712 | return true; |
| 1713 | } |
| 1714 | } |
| 1715 | |
| 1716 | return false; |
| 1717 | } |
| 1718 | |
| 1719 | public static function get_calendar_start_end_time( $bookings, $work_start_minutes, $work_end_minutes ) { |
| 1720 | $calendar_start_minutes = $work_start_minutes; |
| 1721 | $calendar_end_minutes = $work_end_minutes; |
| 1722 | if ( $bookings ) { |
| 1723 | foreach ( $bookings as $bookings_for_agent ) { |
| 1724 | if ( $bookings_for_agent ) { |
| 1725 | foreach ( $bookings_for_agent as $booking ) { |
| 1726 | if ( $booking->start_time < $calendar_start_minutes ) { |
| 1727 | $calendar_start_minutes = $booking->start_time; |
| 1728 | } |
| 1729 | if ( $booking->end_time > $calendar_end_minutes ) { |
| 1730 | $calendar_end_minutes = $booking->end_time; |
| 1731 | } |
| 1732 | } |
| 1733 | } |
| 1734 | } |
| 1735 | } |
| 1736 | |
| 1737 | return [ $calendar_start_minutes, $calendar_end_minutes ]; |
| 1738 | } |
| 1739 | |
| 1740 | public static function generate_direct_manage_booking_url( OsBookingModel $booking, string $for ): string { |
| 1741 | if ( ! in_array( $for, [ 'agent', 'customer' ] ) ) { |
| 1742 | return ''; |
| 1743 | } |
| 1744 | $key = $booking->get_key_to_manage_for( $for ); |
| 1745 | $url = OsRouterHelper::build_admin_post_link( [ 'manage_booking_by_key', 'show' ], [ 'key' => $key ] ); |
| 1746 | |
| 1747 | return $url; |
| 1748 | } |
| 1749 | |
| 1750 | /** |
| 1751 | * Renders the customer-facing "Cancel" button for a booking, behind a filter so addons |
| 1752 | * (e.g. the pro cancellation-reason feature) can replace its behavior without editing core. |
| 1753 | * |
| 1754 | * @param OsBookingModel $booking |
| 1755 | * @param string $route_prefix controller prefix for the cancel route ('customer_cabinet' or 'manage_booking_by_key') |
| 1756 | * @param string|null $key manage-by-key key (null for the logged-in customer cabinet) |
| 1757 | * @param string $css_classes button CSS classes |
| 1758 | */ |
| 1759 | public static function generate_cancel_booking_button( OsBookingModel $booking, string $route_prefix, ?string $key = null, string $css_classes = 'latepoint-btn latepoint-btn-danger latepoint-btn-link' ): string { |
| 1760 | $os_params = OsUtilHelper::build_os_params( $key ? [ 'key' => $key ] : [ 'id' => $booking->id ], 'cancel_booking_' . $booking->id ); |
| 1761 | $route = OsRouterHelper::build_route_name( $route_prefix, 'request_cancellation' ); |
| 1762 | ob_start(); |
| 1763 | ?> |
| 1764 | <a href="#" class="<?php echo esc_attr( $css_classes ); ?> os-confirm-alert" |
| 1765 | data-os-confirm-title="<?php esc_attr_e( 'Cancel Appointment?', 'latepoint' ); ?>" |
| 1766 | data-os-prompt="<?php esc_attr_e( 'Are you sure you want to cancel this appointment?', 'latepoint' ); ?>" |
| 1767 | data-os-confirm-button="<?php esc_attr_e( 'Confirm Cancellation', 'latepoint' ); ?>" |
| 1768 | data-os-success-action="reload" |
| 1769 | data-os-action="<?php echo esc_attr( $route ); ?>" |
| 1770 | data-os-params="<?php echo esc_attr( $os_params ); ?>"> |
| 1771 | <i class="latepoint-icon latepoint-icon-ui-24"></i> |
| 1772 | <span><?php esc_html_e( 'Cancel', 'latepoint' ); ?></span> |
| 1773 | </a> |
| 1774 | <?php |
| 1775 | $html = ob_get_clean(); |
| 1776 | |
| 1777 | /** |
| 1778 | * Filters the customer-facing cancel-booking button HTML. |
| 1779 | * |
| 1780 | * @param string $html |
| 1781 | * @param OsBookingModel $booking |
| 1782 | * @param string $route_prefix |
| 1783 | * @param string|null $key |
| 1784 | * |
| 1785 | * @since 5.2.0 |
| 1786 | * @hook latepoint_customer_cancel_booking_button |
| 1787 | */ |
| 1788 | return apply_filters( 'latepoint_customer_cancel_booking_button', $html, $booking, $route_prefix, $key ); |
| 1789 | } |
| 1790 | |
| 1791 | public static function generate_summary_actions_for_booking( OsBookingModel $booking, ?string $key = null ) { |
| 1792 | ?> |
| 1793 | <div class="booking-full-summary-actions"> |
| 1794 | <div class="add-to-calendar-wrapper"> |
| 1795 | <a href="#" class="open-calendar-types booking-summary-action-btn"><i class="latepoint-icon latepoint-icon-calendar"></i><span><?php esc_html_e( 'Add to Calendar', 'latepoint' ); ?></span></a> |
| 1796 | <?php echo OsBookingHelper::generate_add_to_calendar_links( $booking, $key ?? $booking->get_key_to_manage_for( 'customer' ) ); ?> |
| 1797 | </div> |
| 1798 | <a href="<?php echo esc_url( $booking->get_print_link( $key ?? $booking->get_key_to_manage_for( 'customer' ) ) ); ?>" class="print-booking-btn booking-summary-action-btn" target="_blank"><i class="latepoint-icon latepoint-icon-printer"></i><span><?php esc_html_e( 'Print', 'latepoint' ); ?></span></a> |
| 1799 | <?php |
| 1800 | if ( $booking->is_upcoming() ) { |
| 1801 | if ( OsCustomerHelper::can_reschedule_booking( $booking ) ) { ?> |
| 1802 | <a href="#" class="latepoint-request-booking-reschedule booking-summary-action-btn" data-os-after-call="latepoint_init_reschedule" data-os-lightbox-classes="width-450 reschedule-calendar-wrapper" data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'manage_booking_by_key', 'request_reschedule_calendar' ) ); ?>" data-os-params="<?php echo esc_attr( OsUtilHelper::build_os_params( [ 'key' => $key ?? $booking->get_key_to_manage_for( 'customer' ) ] ) ); ?>" data-os-output-target="lightbox"> |
| 1803 | <i class="latepoint-icon latepoint-icon-calendar"></i> |
| 1804 | <span><?php esc_html_e( 'Reschedule', 'latepoint' ); ?></span> |
| 1805 | </a> |
| 1806 | <?php |
| 1807 | } |
| 1808 | if ( OsCustomerHelper::can_cancel_booking( $booking ) ) { |
| 1809 | echo self::generate_cancel_booking_button( $booking, 'manage_booking_by_key', $key ?? $booking->get_key_to_manage_for( 'customer' ), 'booking-summary-action-btn cancel-appointment-btn' ); |
| 1810 | } |
| 1811 | } |
| 1812 | do_action( 'latepoint_booking_summary_after_booking_actions', $booking ); |
| 1813 | ?> |
| 1814 | </div> |
| 1815 | <?php |
| 1816 | } |
| 1817 | |
| 1818 | public static function generate_summary_for_booking( OsBookingModel $booking, $cart_item_id = false, ?string $viewer = 'customer' ): string { |
| 1819 | $summary_html = ''; |
| 1820 | $summary_html .= apply_filters( 'latepoint_booking_summary_before_summary_box', '', $booking ); |
| 1821 | $summary_html .= '<div class="summary-box main-box" ' . ( ( $cart_item_id ) ? 'data-cart-item-id="' . $cart_item_id . '"' : '' ) . '>'; |
| 1822 | $output_timezone_name = $viewer == 'customer' ? $booking->get_customer_timezone_name() : OsTimeHelper::get_wp_timezone_name(); |
| 1823 | if ( ! empty( $booking->start_datetime_utc ) ) { |
| 1824 | $summary_html .= '<div class="summary-box-booking-date-box">'; |
| 1825 | $summary_html .= '<div class="summary-box-booking-date-day">' . $booking->start_datetime_in_format( 'j', $output_timezone_name ) . '</div>'; |
| 1826 | $summary_html .= '<div class="summary-box-booking-date-month">' . OsUtilHelper::get_month_name_by_number( $booking->start_datetime_in_format( 'n', $output_timezone_name ), true ) . '</div>'; |
| 1827 | $summary_html .= '</div>'; |
| 1828 | } |
| 1829 | $summary_html .= '<div class="summary-box-inner">'; |
| 1830 | $service_headings = []; |
| 1831 | $service_headings = apply_filters( 'latepoint_booking_summary_service_headings', $service_headings, $booking ); |
| 1832 | if ( $service_headings ) { |
| 1833 | $summary_html .= '<div class="summary-box-heading">'; |
| 1834 | foreach ( $service_headings as $heading ) { |
| 1835 | $summary_html .= '<div class="sbh-item">' . $heading . '</div>'; |
| 1836 | } |
| 1837 | $summary_html .= '<div class="sbh-line"></div>'; |
| 1838 | $summary_html .= '</div>'; |
| 1839 | } |
| 1840 | $summary_html .= '<div class="summary-box-content os-cart-item">'; |
| 1841 | if ( $cart_item_id && OsCartsHelper::can_checkout_multiple_items() ) { |
| 1842 | $summary_html .= '<div class="os-remove-item-from-cart" role="button" tabindex="0" data-confirm-text="' . __( 'Are you sure you want to remove this item from your cart?', 'latepoint' ) . '" data-cart-item-id="' . $cart_item_id . '" data-nonce="' . esc_attr( wp_create_nonce( 'remove_item_from_cart' ) ) . '" data-route="' . OsRouterHelper::build_route_name( 'carts', 'remove_item_from_cart' ) . '"> |
| 1843 | <div class="os-remove-from-cart-icon"></div> |
| 1844 | </div>'; |
| 1845 | } |
| 1846 | $summary_html .= '<div class="sbc-big-item">' . $booking->get_service_name_for_summary() . '</div>'; |
| 1847 | if ( $booking->start_date ) { |
| 1848 | $summary_html .= '<div class="sbc-highlighted-item">' . $booking->get_nice_datetime_for_summary( $viewer ) . '</div>'; |
| 1849 | } |
| 1850 | /** |
| 1851 | * Output summary of the booking data after a start date and time |
| 1852 | * |
| 1853 | * @since 5.2.0 |
| 1854 | * @hook latepoint_summary_booking_info_after_start_date |
| 1855 | * |
| 1856 | * @param {string} $summary_html HTML of the summary |
| 1857 | * @param {OsBookingModel} $booking Booking object that is being outputted |
| 1858 | * @param {string} $cart_item_id ID of a cart item this booking belongs to |
| 1859 | * @param {string} $viewer determines who is viewing this summary, can be customer or agent |
| 1860 | * |
| 1861 | * @returns {string} Filtered HTML |
| 1862 | */ |
| 1863 | $summary_html = apply_filters( 'latepoint_summary_booking_info_after_start_date', $summary_html, $booking, $cart_item_id, $viewer ); |
| 1864 | $summary_html .= '</div>'; |
| 1865 | |
| 1866 | $service_attributes = []; |
| 1867 | $service_attributes = apply_filters( 'latepoint_booking_summary_service_attributes', $service_attributes, $booking ); |
| 1868 | if ( $service_attributes ) { |
| 1869 | $summary_html .= '<div class="summary-attributes sa-clean">'; |
| 1870 | foreach ( $service_attributes as $attribute ) { |
| 1871 | $summary_html .= '<span>' . $attribute['label'] . ': <strong>' . $attribute['value'] . '</strong></span>'; |
| 1872 | } |
| 1873 | $summary_html .= '</div>'; |
| 1874 | } |
| 1875 | $summary_html .= '</div>'; |
| 1876 | $summary_html .= apply_filters( 'latepoint_booking_summary_after_summary_box_inner', '', $booking ); |
| 1877 | $summary_html .= '</div>'; |
| 1878 | $summary_html .= apply_filters( 'latepoint_booking_summary_after_summary_box', '', $booking ); |
| 1879 | |
| 1880 | return $summary_html; |
| 1881 | } |
| 1882 | |
| 1883 | |
| 1884 | /** |
| 1885 | * @param OsBookingModel[] $bookings |
| 1886 | * |
| 1887 | * @return bool |
| 1888 | */ |
| 1889 | public static function bookings_have_same_agent( array $bookings ): bool { |
| 1890 | return ( count( array_unique( array_column( $bookings, 'agent_id' ) ) ) == 1 ); |
| 1891 | } |
| 1892 | |
| 1893 | /** |
| 1894 | * @param OsBookingModel[] $bookings |
| 1895 | * |
| 1896 | * @return bool |
| 1897 | */ |
| 1898 | public static function bookings_have_same_location( array $bookings ): bool { |
| 1899 | return ( count( array_unique( array_column( $bookings, 'location_id' ) ) ) == 1 ); |
| 1900 | } |
| 1901 | |
| 1902 | /** |
| 1903 | * @param OsBookingModel[] $bookings |
| 1904 | * |
| 1905 | * @return bool |
| 1906 | */ |
| 1907 | public static function bookings_have_same_service( array $bookings ): bool { |
| 1908 | return ( count( array_unique( array_column( $bookings, 'service_id' ) ) ) == 1 ); |
| 1909 | } |
| 1910 | |
| 1911 | public static function prepare_new_from_params( array $params ): OsBookingModel { |
| 1912 | $booking = new OsBookingModel(); |
| 1913 | |
| 1914 | $services = OsServiceHelper::get_allowed_active_services(); |
| 1915 | $agents = OsAgentHelper::get_allowed_active_agents(); |
| 1916 | |
| 1917 | // LOAD FROM PASSED PARAMS |
| 1918 | $booking->order_item_id = $params['order_item_id'] ?? ''; |
| 1919 | $booking->service_id = ! empty( $params['service_id'] ) ? OsUtilHelper::first_value_if_array( $params['service_id'] ) : ''; |
| 1920 | if ( empty( $booking->service_id ) && ! empty( $services ) ) { |
| 1921 | $booking->service_id = $services[0]->id; |
| 1922 | } |
| 1923 | |
| 1924 | $booking->agent_id = ! empty( $params['agent_id'] ) ? OsUtilHelper::first_value_if_array( $params['agent_id'] ) : ''; |
| 1925 | if ( empty( $booking->agent_id ) && ! empty( $agents ) ) { |
| 1926 | $booking->agent_id = $agents[0]->id; |
| 1927 | } |
| 1928 | |
| 1929 | if ( ! empty( $params['order_id'] ) ) { |
| 1930 | $order = new OsOrderModel( $params['order_id'] ); |
| 1931 | $booking->customer_id = $order->customer_id; |
| 1932 | } else { |
| 1933 | $booking->customer_id = ! empty( $params['customer_id'] ) ? OsUtilHelper::first_value_if_array( $params['customer_id'] ) : ''; |
| 1934 | } |
| 1935 | |
| 1936 | $booking->location_id = ! empty( $params['location_id'] ) ? OsUtilHelper::first_value_if_array( $params['location_id'] ) : OsLocationHelper::get_default_location_id( true ); |
| 1937 | $booking->start_date = $params['start_date'] ?? OsTimeHelper::today_date( 'Y-m-d' ); |
| 1938 | $booking->start_time = $params['start_time'] ?? 600; |
| 1939 | |
| 1940 | $booking->end_time = ( $booking->service_id ) ? $booking->calculate_end_time() : $booking->start_time + 60; |
| 1941 | $booking->end_date = $booking->calculate_end_date(); |
| 1942 | $booking->buffer_before = ( $booking->service_id ) ? $booking->service->buffer_before : 0; |
| 1943 | $booking->buffer_after = ( $booking->service_id ) ? $booking->service->buffer_after : 0; |
| 1944 | $booking->status = LATEPOINT_BOOKING_STATUS_APPROVED; |
| 1945 | |
| 1946 | return $booking; |
| 1947 | } |
| 1948 | |
| 1949 | /** |
| 1950 | * Returns the <th> HTML for a single column header cell in the bookings table. |
| 1951 | * |
| 1952 | * @param string $col_key Unified column key (e.g. 'id', 'datetime', 'customer.email'). |
| 1953 | * @param array $col_def Column definition from get_all_bookings_table_columns(). |
| 1954 | * @param string $order_key Currently active sort key. |
| 1955 | * @param string $order_dir Currently active sort direction ('asc'|'desc'). |
| 1956 | * @return string |
| 1957 | */ |
| 1958 | public static function render_table_header_cell( string $col_key, array $col_def, string $order_key, string $order_dir ): string { |
| 1959 | switch ( $col_key ) { |
| 1960 | case 'id': |
| 1961 | $active_class = ( $order_key === 'booking_id' ) ? ' ordered-' . esc_attr( $order_dir ) : ''; |
| 1962 | return '<th class="os-sortable-column' . $active_class . '" data-order-key="booking_id">' . esc_html__( 'ID', 'latepoint' ) . '</th>'; |
| 1963 | |
| 1964 | case 'service': |
| 1965 | return '<th>' . esc_html__( 'Service', 'latepoint' ) . '</th>'; |
| 1966 | |
| 1967 | case 'datetime': |
| 1968 | $active_class = ( $order_key === 'booking_start_datetime' ) ? ' ordered-' . esc_attr( $order_dir ) : ''; |
| 1969 | return '<th class="os-sortable-column' . $active_class . '" data-order-key="booking_start_datetime">' . esc_html__( 'Date/Time', 'latepoint' ) . '</th>'; |
| 1970 | |
| 1971 | case 'time_left': |
| 1972 | $active_class = ( $order_key === 'booking_time_left' ) ? ' ordered-' . esc_attr( $order_dir ) : ''; |
| 1973 | return '<th class="os-sortable-column' . $active_class . '" data-order-key="booking_time_left">' . esc_html__( 'Time Left', 'latepoint' ) . '</th>'; |
| 1974 | |
| 1975 | case 'agent': |
| 1976 | return '<th>' . esc_html__( 'Agent', 'latepoint' ) . '</th>'; |
| 1977 | |
| 1978 | case 'location': |
| 1979 | return '<th>' . esc_html__( 'Location', 'latepoint' ) . '</th>'; |
| 1980 | |
| 1981 | case 'customer': |
| 1982 | return '<th>' . esc_html__( 'Customer', 'latepoint' ) . '</th>'; |
| 1983 | |
| 1984 | case 'status': |
| 1985 | return '<th>' . esc_html__( 'Status', 'latepoint' ) . '</th>'; |
| 1986 | |
| 1987 | case 'payment_status': |
| 1988 | return '<th>' . esc_html__( 'Payment Status', 'latepoint' ) . '</th>'; |
| 1989 | |
| 1990 | case 'created_on': |
| 1991 | $active_class = ( $order_key === 'booking_created_on' ) ? ' ordered-' . esc_attr( $order_dir ) : ''; |
| 1992 | return '<th class="os-sortable-column' . $active_class . '" data-order-key="booking_created_on">' . esc_html__( 'Created On', 'latepoint' ) . '</th>'; |
| 1993 | |
| 1994 | default: |
| 1995 | // Extra columns. |
| 1996 | return '<th>' . esc_html( $col_def['label'] ) . '</th>'; |
| 1997 | } |
| 1998 | } |
| 1999 | |
| 2000 | /** |
| 2001 | * Returns the <th> HTML for a single column filter cell in the bookings table header. |
| 2002 | * |
| 2003 | * @param string $col_key Unified column key. |
| 2004 | * @param array $col_def Column definition. |
| 2005 | * @param array $services_list Keyed list of services for the service filter select. |
| 2006 | * @param array $agents_list Keyed list of agents for the agent filter select. |
| 2007 | * @param array $locations_list Keyed list of locations for the location filter select. |
| 2008 | * @return string |
| 2009 | */ |
| 2010 | public static function render_table_filter_cell( string $col_key, array $col_def, array $services_list, array $agents_list, array $locations_list ): string { |
| 2011 | switch ( $col_key ) { |
| 2012 | case 'id': |
| 2013 | return '<th>' . OsFormHelper::text_field( |
| 2014 | 'filter[id]', |
| 2015 | false, |
| 2016 | '', |
| 2017 | [ |
| 2018 | 'placeholder' => __( 'ID', 'latepoint' ), |
| 2019 | 'theme' => 'bordered', |
| 2020 | 'style' => 'width: 60px;', |
| 2021 | 'class' => 'os-table-filter', |
| 2022 | ] |
| 2023 | ) . '</th>'; |
| 2024 | |
| 2025 | case 'service': |
| 2026 | return '<th>' . OsFormHelper::select_field( |
| 2027 | 'filter[service_id]', |
| 2028 | false, |
| 2029 | $services_list, |
| 2030 | '', |
| 2031 | [ |
| 2032 | 'placeholder' => __( 'All Services', 'latepoint' ), |
| 2033 | 'class' => 'os-table-filter', |
| 2034 | ] |
| 2035 | ) . '</th>'; |
| 2036 | |
| 2037 | case 'datetime': |
| 2038 | ob_start(); |
| 2039 | ?> |
| 2040 | <th> |
| 2041 | <div class="os-form-group"> |
| 2042 | <div class="os-date-range-picker os-table-filter-datepicker" data-can-be-cleared="yes" data-no-value-label="<?php esc_attr_e( 'Search by Appointment Date', 'latepoint' ); ?>" data-clear-btn-label="<?php esc_attr_e( 'Reset Date Search', 'latepoint' ); ?>"> |
| 2043 | <span class="range-picker-value"><?php esc_html_e( 'Filter Date', 'latepoint' ); ?></span> |
| 2044 | <i class="latepoint-icon latepoint-icon-chevron-down"></i> |
| 2045 | <input type="hidden" class="os-table-filter os-datepicker-date-from" name="filter[booking_date_from]" value=""/> |
| 2046 | <input type="hidden" class="os-table-filter os-datepicker-date-to" name="filter[booking_date_to]" value=""/> |
| 2047 | </div> |
| 2048 | </div> |
| 2049 | </th> |
| 2050 | <?php |
| 2051 | return ob_get_clean(); |
| 2052 | |
| 2053 | case 'time_left': |
| 2054 | return '<th>' . OsFormHelper::select_field( |
| 2055 | 'filter[time_status]', |
| 2056 | false, |
| 2057 | [ |
| 2058 | 'upcoming' => __( 'Upcoming', 'latepoint' ), |
| 2059 | 'past' => __( 'Past', 'latepoint' ), |
| 2060 | 'now' => __( 'Happening Now', 'latepoint' ), |
| 2061 | ], |
| 2062 | '', |
| 2063 | [ |
| 2064 | 'placeholder' => __( 'Show All', 'latepoint' ), |
| 2065 | 'class' => 'os-table-filter', |
| 2066 | ] |
| 2067 | ) . '</th>'; |
| 2068 | |
| 2069 | case 'agent': |
| 2070 | return '<th>' . OsFormHelper::select_field( |
| 2071 | 'filter[agent_id]', |
| 2072 | false, |
| 2073 | $agents_list, |
| 2074 | '', |
| 2075 | [ |
| 2076 | 'placeholder' => __( 'All Agents', 'latepoint' ), |
| 2077 | 'class' => 'os-table-filter', |
| 2078 | ] |
| 2079 | ) . '</th>'; |
| 2080 | |
| 2081 | case 'location': |
| 2082 | return '<th>' . OsFormHelper::select_field( |
| 2083 | 'filter[location_id]', |
| 2084 | false, |
| 2085 | $locations_list, |
| 2086 | '', |
| 2087 | [ |
| 2088 | 'placeholder' => __( 'All Locations', 'latepoint' ), |
| 2089 | 'class' => 'os-table-filter', |
| 2090 | ] |
| 2091 | ) . '</th>'; |
| 2092 | |
| 2093 | case 'customer': |
| 2094 | return '<th>' . OsFormHelper::text_field( |
| 2095 | 'filter[customer][full_name]', |
| 2096 | false, |
| 2097 | '', |
| 2098 | [ |
| 2099 | 'class' => 'os-table-filter', |
| 2100 | 'theme' => 'bordered', |
| 2101 | 'placeholder' => __( 'Search by Customer', 'latepoint' ), |
| 2102 | ] |
| 2103 | ) . '</th>'; |
| 2104 | |
| 2105 | case 'status': |
| 2106 | return '<th>' . OsFormHelper::select_field( |
| 2107 | 'filter[status]', |
| 2108 | false, |
| 2109 | OsBookingHelper::get_statuses_list(), |
| 2110 | '', |
| 2111 | [ |
| 2112 | 'placeholder' => __( 'Show All', 'latepoint' ), |
| 2113 | 'class' => 'os-table-filter', |
| 2114 | ] |
| 2115 | ) . '</th>'; |
| 2116 | |
| 2117 | case 'payment_status': |
| 2118 | return '<th>' . OsFormHelper::select_field( |
| 2119 | 'filter[order][payment_status]', |
| 2120 | false, |
| 2121 | OsOrdersHelper::get_order_payment_statuses_list(), |
| 2122 | '', |
| 2123 | [ |
| 2124 | 'placeholder' => __( 'Show All', 'latepoint' ), |
| 2125 | 'class' => 'os-table-filter', |
| 2126 | ] |
| 2127 | ) . '</th>'; |
| 2128 | |
| 2129 | case 'created_on': |
| 2130 | ob_start(); |
| 2131 | ?> |
| 2132 | <th> |
| 2133 | <div class="os-form-group"> |
| 2134 | <div class="os-date-range-picker os-table-filter-datepicker" data-can-be-cleared="yes" data-no-value-label="<?php esc_attr_e( 'Filter Date', 'latepoint' ); ?>" data-clear-btn-label="<?php esc_attr_e( 'Reset Date Search', 'latepoint' ); ?>"> |
| 2135 | <span class="range-picker-value"><?php esc_html_e( 'Filter Date', 'latepoint' ); ?></span> |
| 2136 | <i class="latepoint-icon latepoint-icon-chevron-down"></i> |
| 2137 | <input type="hidden" class="os-table-filter os-datepicker-date-from" name="filter[created_date_from]" value=""/> |
| 2138 | <input type="hidden" class="os-table-filter os-datepicker-date-to" name="filter[created_date_to]" value=""/> |
| 2139 | </div> |
| 2140 | </div> |
| 2141 | </th> |
| 2142 | <?php |
| 2143 | return ob_get_clean(); |
| 2144 | |
| 2145 | default: |
| 2146 | // Extra columns. |
| 2147 | if ( 'extra' !== $col_def['type'] ) { |
| 2148 | return '<th></th>'; |
| 2149 | } |
| 2150 | $extra_type = $col_def['extra_type']; |
| 2151 | $extra_key = $col_def['extra_key']; |
| 2152 | $extra_label = $col_def['label']; |
| 2153 | $field_name = ( 'booking' !== $extra_type ) ? 'filter[' . $extra_type . '][' . $extra_key . ']' : 'filter[' . $extra_key . ']'; |
| 2154 | // Skip the filter input for magic properties that can't be queried. |
| 2155 | if ( 'booking' === $extra_type && ! property_exists( 'OsBookingModel', $extra_key ) ) { |
| 2156 | return '<th></th>'; |
| 2157 | } |
| 2158 | return '<th>' . OsFormHelper::text_field( |
| 2159 | $field_name, |
| 2160 | false, |
| 2161 | '', |
| 2162 | [ |
| 2163 | 'class' => 'os-table-filter', |
| 2164 | 'theme' => 'bordered', |
| 2165 | 'placeholder' => $extra_label, |
| 2166 | ] |
| 2167 | ) . '</th>'; |
| 2168 | } |
| 2169 | } |
| 2170 | |
| 2171 | /** |
| 2172 | * Returns the <td> HTML for a single data cell in the bookings table body. |
| 2173 | * |
| 2174 | * @param string $col_key Unified column key. |
| 2175 | * @param array $col_def Column definition. |
| 2176 | * @param OsBookingModel $booking Current booking row. |
| 2177 | * @return string |
| 2178 | */ |
| 2179 | public static function render_table_body_cell( string $col_key, array $col_def, OsBookingModel $booking ): string { |
| 2180 | switch ( $col_key ) { |
| 2181 | case 'id': |
| 2182 | return '<td class="os-column-faded text-right has-floating-button">' . |
| 2183 | esc_html( $booking->id ) . |
| 2184 | '<div class="os-floating-button"><i class="latepoint-icon latepoint-icon-edit-3"></i></div>' . |
| 2185 | '</td>'; |
| 2186 | |
| 2187 | case 'service': |
| 2188 | return '<td><div class="os-with-service-color"><span class="cell-link-content">' . |
| 2189 | '<span class="os-column-service-color" style="background-color: ' . esc_attr( $booking->service->bg_color ) . '"></span>' . |
| 2190 | '<span>' . esc_html( $booking->service->name ) . '</span>' . |
| 2191 | '</span></div></td>'; |
| 2192 | |
| 2193 | case 'datetime': |
| 2194 | return '<td><strong>' . esc_html( $booking->nice_start_date ) . '</strong> <span class="os-dot"></span> <span>' . esc_html( $booking->nice_start_time ) . '</span></td>'; |
| 2195 | |
| 2196 | case 'time_left': |
| 2197 | $time_html = ''; |
| 2198 | switch ( $booking->time_status() ) { |
| 2199 | case 'upcoming': |
| 2200 | $time_html = $booking->time_left; |
| 2201 | break; |
| 2202 | case 'now': |
| 2203 | $time_html = '<span class="time-left is-now">' . esc_html__( 'Now', 'latepoint' ) . '</span>'; |
| 2204 | break; |
| 2205 | case 'past': |
| 2206 | $time_html = '<span class="time-left is-past">' . esc_html__( 'Past', 'latepoint' ) . '</span>'; |
| 2207 | break; |
| 2208 | } |
| 2209 | return '<td><span class="in-table-time-left">' . $time_html . '</span></td>'; |
| 2210 | |
| 2211 | case 'agent': |
| 2212 | return '<td><div class="os-with-avatar">' . |
| 2213 | '<span class="cell-link-content">' . |
| 2214 | '<span class="os-avatar" style="background-image: url(' . esc_url( $booking->agent->get_avatar_url() ) . ')"></span>' . |
| 2215 | '<span class="os-name">' . esc_html( $booking->agent->full_name ) . '</span>' . |
| 2216 | '</span>' . |
| 2217 | '<div class="os-clickable-popup-trigger"' . |
| 2218 | ' data-route="' . esc_attr( OsRouterHelper::build_route_name( 'agents', 'mini_profile' ) ) . '"' . |
| 2219 | ' data-os-params="' . esc_attr( |
| 2220 | OsUtilHelper::build_os_params( |
| 2221 | [ |
| 2222 | 'agent_id' => $booking->agent_id, |
| 2223 | 'booking_id' => $booking->id, |
| 2224 | ] |
| 2225 | ) |
| 2226 | ) . '">' . |
| 2227 | '<i class="latepoint-icon latepoint-icon-more-horizontal"></i>' . |
| 2228 | '</div>' . |
| 2229 | '</div></td>'; |
| 2230 | |
| 2231 | case 'location': |
| 2232 | return '<td>' . esc_html( $booking->location->name ) . '</td>'; |
| 2233 | |
| 2234 | case 'customer': |
| 2235 | return '<td><div class="os-with-avatar">' . |
| 2236 | '<span class="cell-link-content">' . |
| 2237 | '<span class="os-avatar" style="background-image: url(' . esc_url( $booking->customer->get_avatar_url() ) . ')"></span>' . |
| 2238 | '<span class="os-name">' . esc_html( $booking->customer->full_name ) . '</span>' . |
| 2239 | '</span>' . |
| 2240 | '<div class="os-clickable-popup-trigger"' . |
| 2241 | ' data-route="' . esc_attr( OsRouterHelper::build_route_name( 'customers', 'mini_profile' ) ) . '"' . |
| 2242 | ' data-os-params="' . esc_attr( |
| 2243 | OsUtilHelper::build_os_params( |
| 2244 | [ |
| 2245 | 'customer_id' => $booking->customer_id, |
| 2246 | 'booking_id' => $booking->id, |
| 2247 | ] |
| 2248 | ) |
| 2249 | ) . '">' . |
| 2250 | '<i class="latepoint-icon latepoint-icon-more-horizontal"></i>' . |
| 2251 | '</div>' . |
| 2252 | '</div></td>'; |
| 2253 | |
| 2254 | case 'status': |
| 2255 | return '<td><span class="os-column-status os-column-status-' . esc_attr( $booking->status ) . '">' . esc_html( $booking->nice_status ) . '</span></td>'; |
| 2256 | |
| 2257 | case 'payment_status': |
| 2258 | return '<td><span class="os-column-status os-column-status-' . esc_attr( $booking->order->payment_status ) . '">' . esc_html( OsOrdersHelper::get_nice_order_payment_status_name( $booking->order->payment_status ) ) . '</span></td>'; |
| 2259 | |
| 2260 | case 'created_on': |
| 2261 | return '<td>' . esc_html( $booking->nice_created_at ) . '</td>'; |
| 2262 | |
| 2263 | default: |
| 2264 | // Extra columns: read from the appropriate model via property, getter, or meta. |
| 2265 | if ( 'extra' !== $col_def['type'] ) { |
| 2266 | return '<td></td>'; |
| 2267 | } |
| 2268 | $extra_type = $col_def['extra_type']; |
| 2269 | $extra_key = $col_def['extra_key']; |
| 2270 | $model_obj = ( 'booking' === $extra_type ) ? $booking : $booking->$extra_type; |
| 2271 | |
| 2272 | if ( property_exists( $model_obj, $extra_key ) || method_exists( $model_obj, 'get_' . $extra_key ) ) { |
| 2273 | return '<td>' . esc_html( $model_obj->$extra_key ) . '</td>'; |
| 2274 | } |
| 2275 | |
| 2276 | $column_value = $model_obj->get_meta_by_key( $extra_key ); |
| 2277 | if ( $column_value && |
| 2278 | ( $custom_fields_raw = OsSettingsHelper::get_settings_value( 'custom_fields_for_' . $extra_type, false ) ) && |
| 2279 | ( $custom_fields_arr = json_decode( $custom_fields_raw, true ) ) && |
| 2280 | isset( $custom_fields_arr[ $extra_key ]['type'] ) && |
| 2281 | 'multiselect' === $custom_fields_arr[ $extra_key ]['type'] |
| 2282 | ) { |
| 2283 | $decoded = json_decode( $column_value ); |
| 2284 | $column_value = is_array( $decoded ) ? implode( ', ', $decoded ) : $column_value; |
| 2285 | } |
| 2286 | return '<td>' . esc_html( $column_value ) . '</td>'; |
| 2287 | } |
| 2288 | } |
| 2289 | |
| 2290 | /** |
| 2291 | * Returns the <th> HTML for the bulk-selection column header (with the select-all checkbox). |
| 2292 | * |
| 2293 | * @return string |
| 2294 | */ |
| 2295 | public static function render_bulk_select_header_cell(): string { |
| 2296 | return '<th class="os-bulk-select-cell" onclick="event.stopPropagation();">' . |
| 2297 | '<label class="os-bulk-row-check-w">' . |
| 2298 | '<input type="checkbox" class="os-bulk-select-all" aria-label="' . esc_attr__( 'Select all appointments on this page', 'latepoint' ) . '" />' . |
| 2299 | '<span class="os-bulk-check-box"></span>' . |
| 2300 | '</label>' . |
| 2301 | '</th>'; |
| 2302 | } |
| 2303 | |
| 2304 | /** |
| 2305 | * Returns the empty spacer <th> HTML for the bulk-selection column in filter and footer rows. |
| 2306 | * |
| 2307 | * @return string |
| 2308 | */ |
| 2309 | public static function render_bulk_select_spacer_cell(): string { |
| 2310 | return '<th class="os-bulk-select-cell"></th>'; |
| 2311 | } |
| 2312 | |
| 2313 | /** |
| 2314 | * Returns the <td> HTML for the bulk-selection column on a single body row. |
| 2315 | * |
| 2316 | * @param OsBookingModel $booking Current booking row. |
| 2317 | * @return string |
| 2318 | */ |
| 2319 | public static function render_bulk_select_body_cell( OsBookingModel $booking ): string { |
| 2320 | /* translators: %d: appointment ID */ |
| 2321 | $aria_label = sprintf( __( 'Select appointment %d', 'latepoint' ), $booking->id ); |
| 2322 | return '<td class="os-bulk-select-cell" onclick="event.stopPropagation();">' . |
| 2323 | '<label class="os-bulk-row-check-w">' . |
| 2324 | '<input type="checkbox" class="os-bulk-row-check" value="' . esc_attr( $booking->id ) . '" aria-label="' . esc_attr( $aria_label ) . '" />' . |
| 2325 | '<span class="os-bulk-check-box"></span>' . |
| 2326 | '</label>' . |
| 2327 | '</td>'; |
| 2328 | } |
| 2329 | |
| 2330 | /** |
| 2331 | * Returns the HTML for the bulk-actions toolbar shown above the appointments table. |
| 2332 | * |
| 2333 | * @return string |
| 2334 | */ |
| 2335 | public static function render_bulk_actions_bar(): string { |
| 2336 | return '<div class="os-bulk-actions-bar" data-bulk-nonce="' . esc_attr( wp_create_nonce( 'bulk_destroy_bookings' ) ) . '">' . |
| 2337 | '<div class="os-bulk-actions-info">' . |
| 2338 | '<span class="os-bulk-selected-count">0</span>' . |
| 2339 | // Label is swapped between singular and plural by JS based on selection count. |
| 2340 | '<span class="os-bulk-selected-label">' . esc_html__( 'Appointments selected', 'latepoint' ) . '</span>' . |
| 2341 | '</div>' . |
| 2342 | '<div class="os-bulk-actions-controls">' . |
| 2343 | '<a href="#" class="latepoint-btn latepoint-btn-danger os-bulk-action-delete">' . |
| 2344 | '<i class="latepoint-icon latepoint-icon-trash-2"></i><span>' . esc_html__( 'Delete Selected', 'latepoint' ) . '</span>' . |
| 2345 | '</a>' . |
| 2346 | '<a href="#" class="os-bulk-actions-clear">' . esc_html__( 'Clear selection', 'latepoint' ) . '</a>' . |
| 2347 | '</div>' . |
| 2348 | '</div>'; |
| 2349 | } |
| 2350 | } |
| 2351 |