PluginProbe
Orderable – Restaurant & Food Ordering System / 1.19.0
Orderable – Restaurant & Food Ordering System v1.19.0
0.1.4 0.1.5 0.2.0 1.0.0 1.1.0 1.1.1 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.19.1 1.19.2 1.2.0 1.20.0 1.20.1 All 44 releases
orderable / inc / modules / location / class-location-single.php

class-location-single.php in Orderable – Restaurant & Food Ordering System 1.19.0, at inc/modules/location/class-location-single.php

1,175 lines 34.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Single location class.
4 *
5 * @package Orderable/Classes
6 */
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Single location class.
12 */
13 class Orderable_Location_Single {
14 /**
15 * Location data.
16 *
17 * @var array
18 */
19 public $location_data = array(
20 'location_id' => 0,
21 'override_default_open_hours' => false,
22 'asap_date' => '',
23 'asap_time' => '',
24 'address_line_1' => '',
25 'address_line_2' => '',
26 'city' => '',
27 'country_state' => '',
28 'postcode_zip' => '',
29 'open_hours' => array(),
30 'enable_default_holidays' => true,
31 'pickup_hours_same_as_delivery' => true,
32 );
33
34 /**
35 * Constructor.
36 *
37 * @param int|array|null $location Location ID or row data.
38 */
39 public function __construct( $location = null ) {
40 $location_data = array();
41
42 // Get selected location ID if none is passed in.
43 if ( empty( $location ) ) {
44 $location = Orderable_Location::get_selected_location_id();
45 }
46
47 // Get the main location data.
48 if ( empty( $location ) ) {
49 $location_data['open_hours'] = Orderable_Location::get_default_open_hours();
50 } else {
51 $location_data = is_numeric( $location ) ? Orderable_Location::get_location_data( $location ) : $location;
52 }
53
54 $this->location_data = wp_parse_args( $location_data, $this->location_data );
55
56 /**
57 * Action to run after the location object is initialized.
58 *
59 * @since 1.13.0
60 */
61 do_action( 'orderable_location_object_init', $this );
62 }
63
64 /**
65 * Get location ID.
66 *
67 * @return int
68 */
69 public function get_location_id() {
70 return absint( $this->location_data['location_id'] );
71 }
72
73 /**
74 * Get location name.
75 *
76 * @return int
77 */
78 public function get_title() {
79 return $this->location_data['title'];
80 }
81
82 /**
83 * Is service enabled?
84 *
85 * @param string $service_type Service type (delivery/pickup).
86 *
87 * @return bool
88 */
89 public function is_service_enabled( $service_type ) {
90 return ! empty( $this->location_data[ $service_type ] );
91 }
92
93 /**
94 * Get services.
95 *
96 * @return array
97 */
98 public function get_services() {
99 $services = array();
100
101 if ( $this->is_service_enabled( 'delivery' ) ) {
102 $services[] = 'delivery';
103 }
104
105 if ( $this->is_service_enabled( 'pickup' ) ) {
106 $services[] = 'pickup';
107 }
108
109 return $services;
110 }
111
112 /**
113 * Does the location have any services enabled.
114 *
115 * @return bool
116 */
117 public function has_services() {
118 return ! empty( $this->get_services() );
119 }
120
121 /**
122 * Get lead time period.
123 *
124 * @return string
125 */
126 public function get_lead_time_period() {
127 return ! empty( $this->location_data['lead_time_period'] ) ? $this->location_data['lead_time_period'] : 'days';
128 }
129
130 /**
131 * Get lead time.
132 *
133 * @param bool $in_seconds In seconds.
134 *
135 * @return int
136 */
137 public function get_lead_time( $in_seconds = false ) {
138 $lead_time = ! empty( $this->location_data['lead_time'] ) ? absint( $this->location_data['lead_time'] ) : 0;
139
140 if ( 0 === $lead_time || ! $in_seconds ) {
141 /**
142 * Filter to modify the lead time.
143 *
144 * @since 1.9.0
145 */
146 return apply_filters( 'orderable_location_get_lead_time', $lead_time );
147 }
148
149 $lead_time_period = $this->get_lead_time_period();
150
151 if ( 'days' === $lead_time_period ) {
152 $lead_time *= 86400;
153 } elseif ( 'hours' === $lead_time_period ) {
154 $lead_time *= 3600;
155 } elseif ( 'minutes' === $lead_time_period ) {
156 $lead_time *= 60;
157 }
158
159 /**
160 * Filter to modify the lead time.
161 *
162 * @since 1.9.0
163 */
164 return apply_filters( 'orderable_location_get_lead_time', $lead_time );
165 }
166
167 /**
168 * Get preorder days.
169 *
170 * @return int
171 */
172 public function get_preorder_days() {
173 return isset( $this->location_data['preorder'] ) ? absint( $this->location_data['preorder'] ) : 7;
174 }
175
176 /**
177 * Get delivery days calculation method.
178 *
179 * @return string
180 */
181 public function get_delivery_calculation_method() {
182 return ! empty( $this->location_data['delivery_days_calculation_method'] ) ? $this->location_data['delivery_days_calculation_method'] : 'all';
183 }
184
185 /**
186 * Get override default open hours setting.
187 *
188 * @return bool
189 */
190 public function get_override_default_open_hours() {
191 return ! in_array( $this->location_data['override_default_open_hours'], array( false, '0' ), true );
192 }
193
194 /**
195 * Get enable default holidays setting.
196 *
197 * @return bool
198 */
199 public function get_enable_default_holidays() {
200 return ! in_array( $this->location_data['enable_default_holidays'], array( false, '0' ), true );
201 }
202
203 /**
204 * Get pickup hours same as delivery setting.
205 *
206 * @return bool
207 */
208 public function get_pickup_hours_same_as_delivery() {
209 return ! in_array( $this->location_data['pickup_hours_same_as_delivery'], array( false, '0' ), true );
210 }
211
212 /**
213 * Get ASAP settings
214 *
215 * @return array
216 */
217 public function get_asap_settings() {
218 return array(
219 'date' => '1' === $this->location_data['asap_date'],
220 'time' => '1' === $this->location_data['asap_time'],
221 );
222 }
223
224 /**
225 * Get service days.
226 *
227 * @param string $service_type Service type (delivery/pickup).
228 *
229 * @return array
230 */
231 public function get_service_days( $service_type = 'delivery' ) {
232 $days = Orderable_Timings::get_days_of_the_week();
233
234 $settings = $this->get_service_hours( $service_type, false, true );
235 $service_days = array();
236
237 if ( empty( $settings ) ) {
238 return $service_days;
239 }
240
241 foreach ( $settings as $setting_row ) {
242 if ( empty( $setting_row['days'] ) ) {
243 continue;
244 }
245
246 foreach ( $setting_row['days'] as $day_number ) {
247 $service_days[ $day_number ] = $days[ $day_number ];
248 }
249 }
250
251 return $service_days;
252 }
253
254 /**
255 * Get service hours.
256 *
257 * @param null $service_type Service type (delivery/pickup).
258 * @param bool $is_admin Is this an admin request? If so, collect all data for location.
259 * @param bool $skip_zone Skip the zone ID.
260 *
261 * @return array
262 */
263 public function get_service_hours( $service_type = null, $is_admin = false, $skip_zone = false ) {
264 $zone_id = Orderable_Location_Zones::get_selected_shipping_zone_id();
265
266 if ( false === $zone_id && ! $is_admin ) {
267 /**
268 * Filter to modify the service hours.
269 *
270 * @param array $service_hours The service hours.
271 * @param Orderable_Location_Single $location Current location object.
272 * @param string|null $service_type The service type.
273 * @param bool $is_admin Is this an admin request?
274 * @param bool $skip_zone Skip the zone ID.
275 *
276 * @since 1.14.0
277 */
278 return apply_filters( 'orderable_get_service_hours', array(), $this, $service_type, $is_admin, $skip_zone );
279 }
280
281 // Switch service type to 'delivery' if pickup hours are the same as delivery.
282 $original_service_type = $service_type;
283 $service_type = ! $is_admin && 'pickup' === $service_type && $this->get_pickup_hours_same_as_delivery() ? 'delivery' : $service_type;
284
285 $location_id = $this->get_location_id();
286 $cache_key = "orderable_time_slots_{$location_id}";
287
288 if ( $original_service_type ) {
289 $cache_key .= "_{$original_service_type}";
290 }
291
292 if ( ! $skip_zone && false !== $zone_id ) {
293 $cache_key .= "_{$zone_id}";
294 }
295
296 $cached_service_hours = wp_cache_get( $cache_key );
297
298 if ( false !== $cached_service_hours ) {
299 // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
300 return apply_filters( 'orderable_get_service_hours', $cached_service_hours, $this, $service_type, $is_admin, $skip_zone );
301 }
302
303 global $wpdb;
304
305 $query = "SELECT DISTINCT
306 ts.*
307 FROM
308 {$wpdb->prefix}orderable_location_time_slots ts
309 LEFT JOIN
310 {$wpdb->prefix}orderable_location_delivery_zones_lookup l
311 ON ts.location_id = l.location_id AND ts.time_slot_id = l.time_slot_id
312 WHERE
313 ts.location_id = %d";
314
315 $query_params = array(
316 $location_id,
317 );
318
319 if ( $service_type ) {
320 $query .= ' AND ts.service_type = %s';
321 $query_params[] = $service_type;
322 }
323
324 // Zone doesn't matter for pickup.
325 if ( 'pickup' !== $original_service_type && ! empty( $zone_id ) && ! $skip_zone ) {
326 $query .= ' AND (l.zone_id = %d OR ts.has_zones = 0)';
327 $query_params[] = $zone_id;
328 }
329
330 $service_hours = $wpdb->get_results(
331 $wpdb->prepare(
332 $query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
333 $query_params
334 ),
335 ARRAY_A
336 );
337
338 if ( empty( $service_hours ) ) {
339 $service_hours = array();
340 } else {
341 foreach ( $service_hours as &$service_hour ) {
342 $service_hour['days'] = (array) maybe_unserialize( $service_hour['days'] );
343 $service_hour['from'] = maybe_unserialize( $service_hour['time_from'] );
344 $service_hour['to'] = maybe_unserialize( $service_hour['time_to'] );
345 }
346 }
347
348 wp_cache_set( $cache_key, $service_hours, '', ORDERABLE_CACHE_EXPIRATION_TIME );
349
350 // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
351 return apply_filters( 'orderable_get_service_hours', $service_hours, $this, $service_type, $is_admin, $skip_zone );
352 }
353
354 /**
355 * Get services on day.
356 *
357 * @param int $timestamp Timestamp of specific day at 00:00am (GMT).
358 *
359 * @return array
360 */
361 public function get_services_on_day( $timestamp ) {
362 $timestamp_adjusted = Orderable_Timings::get_timestamp_adjusted( $timestamp );
363 $services_on_day = array();
364 $services = $this->get_services();
365
366 if ( empty( $services ) ) {
367 return $services_on_day;
368 }
369
370 $day_to_check = absint( date( 'w', $timestamp_adjusted ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
371
372 foreach ( $services as $service ) {
373 $service_days = $this->get_service_days( $service );
374 $services_on_day[ $service ] = isset( $service_days[ $day_to_check ] ) && ! $this->is_holiday( $timestamp, $service );
375 }
376
377 return $services_on_day;
378 }
379
380 /**
381 * Get open days.
382 *
383 * @return array
384 */
385 public function get_open_days() {
386 static $open_days_cache;
387
388 if ( ! empty( $open_days_cache[ $this->get_location_id() ] ) ) {
389 // phpcs:ignore WooCommerce.Commenting.CommentHooks
390 return apply_filters( 'orderable_location_get_open_days', $open_days_cache[ $this->get_location_id() ], $this );
391 }
392
393 $open_days = array();
394 $open_hours_settings = $this->get_open_hours();
395 $days_of_the_week = Orderable_Timings::get_days_of_the_week();
396
397 foreach ( $open_hours_settings as $day => $open_hour ) {
398 if ( ! empty( $open_hour['enabled'] ) ) {
399 $open_days[ $day ] = $days_of_the_week[ $day ];
400 }
401 }
402
403 /**
404 * Filter location open days.
405 *
406 * @since 1.8.0
407 * @hook orderable_location_get_open_days
408 * @param array $open_days Location open days.
409 * @param Orderable_Location_Single $location Location object.
410 */
411 $open_days = apply_filters( 'orderable_location_get_open_days', $open_days, $this );
412
413 $open_days_cache[ $this->get_location_id() ] = $open_days;
414
415 return $open_days;
416 }
417
418 /**
419 * Is the location open?
420 *
421 * @param int $timestamp Timestamp (GMT).
422 *
423 * @return bool
424 */
425 public function is_open( $timestamp ) {
426 $date_time = Orderable_Timings::get_date_time_by_timestamp( $timestamp );
427 $open_days = $this->get_open_days();
428
429 return array_key_exists( $date_time->format( 'w' ), $open_days );
430 }
431
432 /**
433 * Get open hours.
434 *
435 * @return array
436 */
437 public function get_open_hours() {
438 static $open_hours_cache;
439
440 if ( ! empty( $open_hours_cache[ $this->get_location_id() ] ) ) {
441 // phpcs:ignore WooCommerce.Commenting.CommentHooks
442 return apply_filters( 'orderable_location_get_open_hours', $open_hours_cache[ $this->get_location_id() ], $this );
443 }
444
445 if ( $this->get_override_default_open_hours() ) {
446 $open_hours = maybe_unserialize( $this->location_data['open_hours'] );
447 $open_hours = ! empty( $open_hours ) ? $open_hours : array();
448 } else {
449 $open_hours = Orderable_Location::get_default_open_hours();
450 }
451
452 /**
453 * Filter location open hours.
454 *
455 * @since 1.8.0
456 * @hook orderable_location_get_open_hours
457 * @param array $open_hours Location open hours.
458 * @param Orderable_Location_Single $location Location object.
459 */
460 $open_hours = apply_filters( 'orderable_location_get_open_hours', $open_hours, $this );
461
462 $open_hours_cache[ $this->get_location_id() ] = $open_hours;
463
464 return $open_hours;
465 }
466
467 /**
468 * Get upcoming open hours.
469 *
470 * @return array
471 */
472 public function get_upcoming_open_hours() {
473 $open_hours = array();
474 $days = Orderable_Timings::get_days_of_the_week( 'full', 0 );
475 $open_hours_settings = $this->get_open_hours();
476 $current_day = absint( current_time( 'w', true ) );
477 $tense = 'last';
478
479 foreach ( $days as $index => $day ) {
480 $day_settings = isset( $open_hours_settings[ $index ] ) ? $open_hours_settings[ $index ] : null;
481
482 if ( empty( $day_settings ) ) {
483 continue;
484 }
485
486 if ( $index === $current_day ) {
487 $tense = 'this';
488 }
489
490 $day_name_en = date( 'l', strtotime( "Sunday +{$index} days" ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
491 $datetime = new DateTime( $tense . ' ' . $day_name_en, wp_timezone() );
492 $timestamp = $datetime->getTimestamp();
493 $timestamp_adjusted = Orderable_Timings::get_timestamp_adjusted( $timestamp );
494 $services_on_day = $this->get_services_on_day( $timestamp );
495 $hours = __( 'Closed', 'orderable' );
496 $is_holiday = $this->is_holiday( $timestamp );
497 $is_holiday = $is_holiday && empty( array_filter( $services_on_day ) );
498 $open = ! empty( $day_settings['enabled'] ) && $day_settings['enabled'];
499
500 if ( $open && $is_holiday ) {
501 $hours = __( 'Holiday', 'orderable' );
502 } elseif ( $open && ! $is_holiday ) {
503 $from = sprintf( '%s:%s %s', $day_settings['from']['hour'], $day_settings['from']['minute'], $day_settings['from']['period'] );
504 $to = sprintf( '%s:%s %s', $day_settings['to']['hour'], $day_settings['to']['minute'], $day_settings['to']['period'] );
505 $hours = sprintf( '%s &mdash; %s', $from, $to );
506 }
507
508 $open_hours[ $index ] = array(
509 'day' => $day,
510 'date' => date( 'd', $timestamp_adjusted ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
511 'hours' => $hours,
512 'is_closed' => ! $open,
513 'services' => $services_on_day,
514 );
515 }
516
517 /**
518 * Filter location upcoming open hours.
519 *
520 * @since 1.8.0
521 * @hook orderable_upcoming_open_hours
522 * @param array $open_hours Location upcoming open hours.
523 * @param Orderable_Location_Single $location Location object.
524 */
525 return apply_filters( 'orderable_upcoming_open_hours', $open_hours, $this );
526 }
527
528 /**
529 * Get slots for service type and day of the week.
530 *
531 * @param int $timestamp Timestamp (GMT).
532 * @param string $type Service type (delivery|pickup).
533 *
534 * @return array
535 */
536 public function get_slots( $timestamp, $type = 'delivery' ) {
537 $slots = array();
538
539 if ( empty( $timestamp ) ) {
540 return $slots;
541 }
542
543 $settings = $this->get_service_hours( $type );
544
545 if ( empty( $settings ) ) {
546 return $slots;
547 }
548
549 $date_time = Orderable_Timings::get_date_time_by_timestamp( $timestamp );
550 $current_timestamp = time();
551 $timestamp = ! $timestamp ? $current_timestamp : $timestamp;
552 $day_number = (int) $date_time->format( 'w' ); // 0 (Sunday) through 6 (Saturday).
553
554 foreach ( $settings as $setting_key => $setting_row ) {
555 $days = array_map( 'absint', $setting_row['days'] );
556
557 if ( ! in_array( $day_number, $days, true ) ) {
558 continue;
559 }
560
561 $slots = array(
562 'all-day' => array(
563 'formatted' => __( 'All Day', 'orderable' ),
564 'value' => 'all-day',
565 'timestamp' => $timestamp,
566 'setting_key' => $setting_key,
567 'setting_row' => $setting_row,
568 ),
569 );
570
571 break;
572 }
573
574 if ( has_filter( 'orderable_get_slots' ) ) {
575 _deprecated_hook( 'orderable_get_slots', '1.8.0', 'orderable_location_get_slots' );
576
577 /**
578 * Filter location slots.
579 *
580 * @since 1.0.0
581 * @hook orderable_get_slots
582 * @deprecated 1.8.0 Use orderable_location_get_slots instead.
583 *
584 * @param array $slots Location slots.
585 * @param int $timestamp Timestamp (GMT).
586 * @param string $type The service type. Either 'delivery' or 'pickup'.
587 * @param Orderable_Location_Single $location Location object.
588 */
589 $slots = apply_filters( 'orderable_get_slots', $slots, $timestamp, $type, $this );
590 }
591
592 /**
593 * Filter location slots.
594 *
595 * @since 1.8.0
596 * @hook orderable_location_get_slots
597 *
598 * @param array $slots Location slots.
599 * @param int $timestamp Timestamp (GMT).
600 * @param string $type The service type. Either 'delivery' or 'pickup'.
601 * @param Orderable_Location_Single $location Location object.
602 */
603 return apply_filters( 'orderable_location_get_slots', $slots, $timestamp, $type, $this );
604 }
605
606 /**
607 * Get holidays.
608 *
609 * @param null $type Service type (delivery/pickup).
610 * @param bool $include_defaults Include default holidays.
611 *
612 * @return array
613 */
614 public function get_holidays( $type = null, $include_defaults = true ) {
615 static $holidays_cache;
616
617 if ( ! empty( $holidays_cache[ $this->get_location_id() ] ) ) {
618 // phpcs:ignore WooCommerce.Commenting.CommentHooks
619 return apply_filters( 'orderable_location_get_holidays', $holidays_cache[ $this->get_location_id() ], $type, $include_defaults, $this );
620 }
621
622 global $wpdb;
623
624 $holidays = $include_defaults && $this->get_enable_default_holidays() ? (array) Orderable_Settings::get_setting( 'holidays' ) : array();
625
626 $sql = "SELECT
627 holiday_id,
628 date_from 'from',
629 date_to 'to',
630 services,
631 repeat_yearly 'repeat'
632 FROM
633 {$wpdb->orderable_location_holidays} holidays
634 INNER JOIN
635 {$wpdb->orderable_locations} locations
636 ON
637 locations.location_id = holidays.location_id
638 WHERE
639 locations.location_id = %d";
640
641 $holidays_query = $wpdb->get_results(
642 $wpdb->prepare(
643 $sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
644 $this->get_location_id()
645 ),
646 ARRAY_A
647 );
648
649 /**
650 * Filter location holidays query result.
651 *
652 * @since 1.14.0
653 */
654 $holidays_query = apply_filters( 'orderable_location_holidays_query_result', $holidays_query, $this );
655
656 $holidays_query = ! empty( $holidays_query ) ? $holidays_query : array();
657 $holidays = array_merge( $holidays, $holidays_query );
658
659 if ( ! empty( $holidays ) ) {
660 $today = new DateTime( 'now', wp_timezone() );
661 $today_timestamp = $today->getTimestamp();
662
663 foreach ( $holidays as $index => $holiday ) {
664 if ( empty( $holiday['from'] ) ) {
665 continue;
666 }
667
668 $holidays[ $index ]['services'] = maybe_unserialize( $holiday['services'] );
669 $holidays[ $index ]['timestamps'] = array();
670
671 $from = DateTime::createFromFormat( 'Y-m-d H:i:s', $holiday['from'] . ' 00:00:00', wp_timezone() );
672 $to = ! empty( $holiday['to'] ) ? DateTime::createFromFormat( 'Y-m-d H:i:s', $holiday['to'] . ' 00:00:00', wp_timezone() ) : clone $from;
673
674 // Add one minute so last slot is included.
675 $to->modify( '+1 minute' );
676
677 // Add years to from and to if this holiday repeats and is in the past.
678 if ( $today_timestamp > $to->getTimestamp() && ! empty( $holiday['repeat'] ) ) {
679 // Calculate how many years have past and append 1 additional.
680 $today_datetime = new DateTime();
681 $today_datetime->setTimestamp( $today_timestamp );
682
683 $interval = $today_datetime->diff( $to );
684 $year = intval( $interval->format( '%Y' ) ) + 1;
685
686 $from->modify( '+' . $year . ' year' );
687 $to->modify( '+' . $year . ' year' );
688
689 $holidays[ $index ]['from'] = $from->format( 'Y-m-d' );
690 $holidays[ $index ]['to'] = ! empty( $holidays[ $index ]['to'] ) ? $to->format( 'Y-m-d' ) : '';
691 }
692
693 $range = new DatePeriod(
694 $from,
695 new DateInterval( 'P1D' ), // Every 1 day.
696 $to
697 );
698
699 if ( empty( $range ) ) {
700 continue;
701 }
702
703 foreach ( $range as $time ) {
704 $holidays[ $index ]['timestamps'][] = $time->getTimestamp();
705 }
706 }
707 }
708
709 /**
710 * Filter location holidays.
711 *
712 * @since 1.8.0
713 * @hook orderable_location_get_holidays
714 * @param array $holidays Location holidays.
715 * @param string|null $type Service type (delivery/pickup).
716 * @param bool $include_defaults Include default holidays.
717 * @param Orderable_Location_Single $location Location object.
718 */
719 $holidays = apply_filters( 'orderable_location_get_holidays', $holidays, $type, $include_defaults, $this );
720
721 $holidays_cache[ $this->get_location_id() ] = $holidays;
722
723 return $holidays;
724 }
725
726 /**
727 * Is this timestamp a holiday?
728 *
729 * @param int $timestamp Timestamp (GMT).
730 * @param string|null $type Service type (delivery/pickup).
731 *
732 * @return bool
733 */
734 public function is_holiday( $timestamp, $type = null ) {
735 $is_holiday = false;
736 $holidays = $this->get_holidays();
737
738 // If no holidays are set, then this isn't a holiday.
739 if ( ! empty( $holidays ) ) {
740 foreach ( $holidays as $holiday ) {
741 // If no timestamps or not assigned to any service, continue.
742 if ( empty( $holiday['timestamps'] ) || empty( $holiday['services'] ) ) {
743 continue;
744 }
745
746 // If timestamp isn't a holiday, continue.
747 if ( ! in_array( $timestamp, $holiday['timestamps'], true ) ) {
748 continue;
749 }
750
751 // If we want to check a specific delivery type, check that
752 // service is assigned to this holiday. Otherwise, continue.
753 if ( $type && ! in_array( $type, $holiday['services'], true ) ) {
754 continue;
755 }
756
757 // If we got here, then this is indeed a holiday.
758 $is_holiday = true;
759 break;
760 }
761 }
762
763 /**
764 * Filter if is a holiday.
765 *
766 * @since 1.8.0
767 * @hook orderable_location_is_holiday
768 * @param bool $is_holiday Is this timestamp a holiday?
769 * @param int $timestamp Timestamp (GMT).
770 * @param string|null $type Service type (delivery/pickup).
771 * @param Orderable_Location_Single $location Location object.
772 */
773 return apply_filters( 'orderable_location_is_holiday', $is_holiday, $timestamp, $type, $this );
774 }
775
776 /**
777 * Get dates available for service type.
778 *
779 * @param string $type Type. Can be 'delivery' or 'pickup'.
780 * @param bool $ignore_needs_shipping Ignore check for WC()->cart->needs_shipping().
781 *
782 * @return array|bool Array when dates are available, "true" when no date selection required, "false" when no dates available.
783 */
784 public function get_service_dates( $type = false, $ignore_needs_shipping = false ) {
785 $cache_key = 'orderable_service_dates_' . md5( $type . (int) $ignore_needs_shipping . wp_json_encode( WC()->cart ) . $this->get_location_id() );
786 $cached_result = wp_cache_get( $cache_key );
787
788 if ( false !== $cached_result ) {
789 return $cached_result;
790 }
791
792 /**
793 * Filter whether ignoring check for WC()->cart->needs_shipping() when
794 * getting service dates.
795 *
796 * @since 1.14.0
797 * @hook orderable_location_service_dates_ignore_needs_shipping
798 * @param bool $ignore_needs_shipping Ignore check for WC()->cart->needs_shipping(). Default: false.
799 * @param string $service_type It can be 'delivery' or 'pickup'.
800 * @return bool New value
801 */
802 $ignore_needs_shipping = apply_filters( 'orderable_location_service_dates_ignore_needs_shipping', $ignore_needs_shipping, $type );
803
804 if ( ! $ignore_needs_shipping && ! WC()->cart->needs_shipping() ) {
805 // For backwards compatibility.
806 $result = apply_filters_deprecated( 'orderable-service-dates', array( true, $type ), '1.8.0', 'orderable_location_service_dates' );
807 // Return true. No service is required.
808
809 /**
810 * Filter orderable service dates.
811 *
812 * @since 1.8.0
813 * @hook orderable_location_service_dates
814 * @see Orderable_Location_Single::get_service_dates()
815 */
816 $service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
817
818 wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
819
820 return $service_dates;
821 }
822
823 $type = ! $type ? Orderable_Services::get_selected_service( false ) : $type;
824 $service_dates = array();
825
826 if ( ! $type ) {
827 // For backwards compatibility.
828 $result = apply_filters_deprecated( 'orderable-service-dates', array( false, $type ), '1.8.0', 'orderable_location_service_dates' );
829 // Return false. A service should be selected.
830 // @todo Check if this should be true when no shipping method is selected yet.
831
832 /**
833 * Filter orderable service dates.
834 *
835 * @since 1.8.0
836 * @hook orderable_location_service_dates
837 * @see Orderable_Location_Single::get_service_dates()
838 */
839 $service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
840
841 wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
842
843 return $service_dates;
844 }
845
846 $services = $this->get_services();
847
848 if ( ! in_array( $type, $services, true ) ) {
849 // For backwards compatibility.
850 $result = apply_filters_deprecated( 'orderable-service-dates', array( false, $type ), '1.8.0', 'orderable_location_service_dates' );
851
852 /**
853 * Filter orderable service dates.
854 *
855 * @since 1.8.0
856 * @hook orderable_location_service_dates
857 * @see Orderable_Location_Single::get_service_dates()
858 */
859 $service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
860
861 wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
862
863 return $service_dates;
864 }
865
866 $min_max_method = $this->get_delivery_calculation_method();
867 $lead_time_period = $this->get_lead_time_period();
868 $lead_days = 'days' === $lead_time_period ? $this->get_lead_time() : 0;
869 $preorder_days = $this->get_preorder_days();
870 $service_days = $this->get_service_days( $type );
871
872 $start_date = new DateTime( 'now', wp_timezone() );
873 $start_date->setTime( 0, 0 ); // Set time to midnight 00:00:00.
874
875 $date_range = new ArrayIterator( array( $start_date ) );
876
877 $counted_lead_days = 0;
878 $counted_preorder_days = 0;
879
880 if ( ! empty( $service_days ) ) {
881 /**
882 * Filter the max index date.
883 *
884 * Since the condition to break the loop that
885 * searchs for service dates relies on the
886 * preorder days limit, we check against $max_index_date
887 * to prevent an infinite loop case preorder days
888 * limit fail multiple times.
889 *
890 * @since 1.8.1
891 * @hook orderable_location_max_index_date
892 * @param int $max_index_date The max index date value. Default: 365.
893 * @param string $type The type. Can be 'delivery' or 'pickup'.
894 * @param Orderable_Location_Single $location The location.
895 * @return int New value
896 */
897 $max_index_date = apply_filters( 'orderable_location_max_index_date', 365, $type, $this );
898
899 foreach ( $date_range as $index => $date ) {
900 // Check to avoid an infinite loop.
901 if ( $index > $max_index_date ) {
902 break;
903 }
904
905 // If we're at the preorder day limit, break the loop.
906 if ( $counted_preorder_days > $preorder_days ) {
907 break;
908 }
909
910 $timestamp = $date->getTimestamp();
911 $week_day = $date->format( 'w' );
912 $date_range->append( clone $date->modify( '+1 day' ) );
913
914 // If calculation method is 'all', we want to increase
915 // the counters for all days.
916 if ( 'all' === $min_max_method ) {
917 $counted_lead_days ++;
918 $counted_preorder_days ++;
919
920 // We aren't ready to start serving up dates yet as we
921 // haven't counted the number of lead days required.
922 if ( $lead_days >= $counted_lead_days ) {
923 continue;
924 }
925 }
926
927 // If calculation method is 'weekdays' (Weekdays), we want to
928 // increase the counters here if the day is a weekday. This
929 // happens before checking if it's a service day.
930 if ( 'weekdays' === $min_max_method ) {
931 if ( Orderable_Timings::is_weekday( $timestamp ) ) {
932 $counted_lead_days ++;
933 $counted_preorder_days ++;
934 }
935
936 // We aren't ready to start serving up dates yet as we
937 // haven't counted the number of lead days required.
938 if ( $lead_days >= $counted_lead_days ) {
939 continue;
940 }
941 }
942
943 // If this date is a holiday, add a date to the array and continue.
944 if ( $this->is_holiday( $timestamp, $type ) ) {
945 continue;
946 }
947
948 // If calculation method is 'open' (Open Days), we want to
949 // increase the counters here if the store is open. This
950 // happens before checking if it's a service day.
951 if ( 'open' === $min_max_method ) {
952 if ( $this->is_open( $timestamp ) ) {
953 $counted_lead_days ++;
954 $counted_preorder_days ++;
955
956 // We aren't ready to start serving up dates yet as we
957 // haven't counted the number of lead days required.
958 if ( $lead_days >= $counted_lead_days ) {
959 continue;
960 }
961 }
962 }
963
964 // If this is not a service day, skip it.
965 if ( ! in_array( (int) $week_day, array_keys( $service_days ), true ) ) {
966 continue;
967 }
968
969 // If calculation method is 'service' (Service Days), we want to increase
970 // the counters here, as any date past this point is a service day.
971 if ( 'service' === $min_max_method ) {
972 $counted_lead_days ++;
973 $counted_preorder_days ++;
974
975 // We aren't ready to start serving up dates yet as we
976 // haven't counted the number of lead days required.
977 if ( $lead_days >= $counted_lead_days ) {
978 continue;
979 }
980 }
981
982 /**
983 * Filter whether a date is available for ordering.
984 *
985 * @since 1.8.0
986 * @hook orderable_date_available
987 * @see Orderable_Location_Single::get_service_dates()
988 */
989 if ( ! apply_filters( 'orderable_date_available', true, $timestamp, $type, $this ) ) {
990 continue;
991 }
992
993 $format = get_option( 'date_format' );
994 $slots = $this->get_slots( $timestamp, $type );
995
996 if ( empty( $slots ) ) {
997 continue;
998 }
999
1000 $service_dates[] = array(
1001 'timestamp' => $timestamp,
1002 'datetime' => date( $format, $timestamp ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1003 'formatted' => Orderable_Timings::get_formatted_date( $timestamp ),
1004 'slots' => $slots,
1005 );
1006 }
1007 }
1008
1009 // If empty, return false; no dates available.
1010 // Otherwise, return dates.
1011 $service_dates = empty( $service_dates ) ? false : $service_dates;
1012
1013 // For backwards compatibility.
1014 $service_dates = apply_filters_deprecated( 'orderable-service-dates', array( $service_dates, $type ), '1.8.0', 'orderable_location_service_dates' );
1015
1016 /**
1017 * Filter orderable service dates.
1018 *
1019 * @param array|bool $service_dates The service dates available. If true,
1020 * the service doesn't require date/time selection.
1021 * If false, a service should be selected.
1022 * @param string $type The type of service. E.g. delivery, pickup.
1023 * @param Orderable_Location_Single $location The location instance.
1024 *
1025 * @return array|bool New value
1026 * @since 1.8.0
1027 * @hook orderable_location_service_dates
1028 */
1029 $service_dates = apply_filters( 'orderable_location_service_dates', $service_dates, $type, $this );
1030
1031 wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
1032
1033 return $service_dates;
1034 }
1035
1036 /**
1037 * Check if location has service dates for this service type.
1038 *
1039 * @param string $service_type Service type (delivery/pickup).
1040 *
1041 * @return bool
1042 */
1043 public function has_service_dates( $service_type ) {
1044 $service_dates = $this->get_service_dates( $service_type, true );
1045
1046 return ! empty( $service_dates );
1047 }
1048
1049 /**
1050 * Get location address.
1051 *
1052 * @return array
1053 */
1054 public function get_address() {
1055 $address = array(
1056 'address_line_1' => $this->location_data['address_line_1'],
1057 'address_line_2' => $this->location_data['address_line_2'],
1058 'city' => $this->location_data['city'],
1059 'country_state' => $this->location_data['country_state'],
1060 'postcode_zip' => $this->location_data['postcode_zip'],
1061 );
1062
1063 return $address;
1064 }
1065
1066 /**
1067 * Get formatted address.
1068 *
1069 * @return string
1070 */
1071 public function get_formatted_address() {
1072 $address = $this->get_address();
1073
1074 $country_state = $address['country_state'];
1075
1076 if ( strstr( $country_state, ':' ) ) {
1077 $country_state = explode( ':', $country_state );
1078 $country = current( $country_state );
1079 $state = end( $country_state );
1080 } else {
1081 $country = $country_state;
1082 $state = '';
1083 }
1084
1085 $state = empty( WC()->countries->get_states( $country )[ $state ] ) ? $state : WC()->countries->get_states( $country )[ $state ];
1086 $country = empty( WC()->countries->get_countries()[ $country ] ) ? $country : WC()->countries->get_countries()[ $country ];
1087
1088 $data = array(
1089 'address_1' => $address['address_line_1'],
1090 'address_2' => $address['address_line_2'],
1091 'city' => $address['city'],
1092 'state' => $state,
1093 'country' => $country,
1094 'postcode' => $address['postcode_zip'],
1095 );
1096
1097 return WC()->countries->get_formatted_address( $data, ', ' );
1098 }
1099
1100 /**
1101 * Check if location has a specific zone set.
1102 *
1103 * @param int $zone_id Zone ID.
1104 * @param bool $allow_empty Allow slots with no zone set.
1105 *
1106 * @return bool
1107 */
1108 public function has_zone( $zone_id, $allow_empty = false ) {
1109 global $wpdb;
1110
1111 $location_id = $this->get_location_id();
1112 $cache_key = "has_zone_{$location_id}_{$zone_id}_{$allow_empty}";
1113 $cache_result = wp_cache_get( $cache_key );
1114
1115 if ( false !== $cache_result ) {
1116 return (bool) $cache_result;
1117 }
1118
1119 $allow_empty_clause = $allow_empty ? 'OR (l.zone_id IS NULL AND ts.has_zones = 0)' : '';
1120
1121 $query = $wpdb->prepare(
1122 "SELECT
1123 COUNT(*)
1124 FROM
1125 {$wpdb->prefix}orderable_location_delivery_zones_lookup l
1126 LEFT JOIN
1127 {$wpdb->prefix}orderable_location_time_slots ts
1128 ON l.time_slot_id = ts.time_slot_id
1129 WHERE
1130 l.location_id = %d
1131 AND
1132 ( l.zone_id = %d {$allow_empty_clause} )",
1133 $location_id,
1134 $zone_id
1135 );
1136
1137 $result = $wpdb->get_var( $query );
1138
1139 $has_zone = $result > 0;
1140
1141 wp_cache_set( $cache_key, (int) $has_zone );
1142
1143 return $has_zone;
1144 }
1145
1146 /**
1147 * Update location title.
1148 *
1149 * @param string $title New title.
1150 */
1151 public function update_title( $title ) {
1152 global $wpdb;
1153
1154 $this->location_data['title'] = $title;
1155
1156 wp_update_post(
1157 array(
1158 'ID' => $this->location_data['post_id'],
1159 'post_title' => $title,
1160 )
1161 );
1162
1163 $wpdb->update(
1164 $wpdb->prefix . 'orderable_locations',
1165 array(
1166 'title' => $title,
1167 ),
1168 array(
1169 'location_id' => $this->location_data['location_id'],
1170 )
1171 );
1172
1173 }
1174 }
1175