PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / page-setup-wizard / class-wpbc-setup-wizard-customer-journey-policy.php

class-wpbc-setup-wizard-customer-journey-policy.php in Booking Calendar 11.9, at includes/page-setup-wizard/class-wpbc-setup-wizard-customer-journey-policy.php

367 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared Setup Wizard Customer Journey policy.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Own the stable journey groups used by route and domain validation.
14 *
15 * Keeping these allow-lists in one policy prevents the rail, validation, save
16 * handlers, and Review page from accepting different setup routes.
17 */
18 final class WPBC_Setup_Wizard_Customer_Journey_Policy {
19
20 /**
21 * Return journeys that require Business Small range-booking features.
22 *
23 * Fixed-length ranges depend on one-click range rules, while changeover
24 * journeys depend on the Business Small check-in/check-out boundary options.
25 * Keeping this list beside the route policy gives rendering and validation
26 * one edition contract instead of duplicating feature checks in templates.
27 *
28 * @return string[] Stable Customer Journey identifiers.
29 */
30 public static function get_business_small_journey_ids() {
31 return array( 'fixed_length_date_range', 'check_in_out_changeover' );
32 }
33
34 /**
35 * Determine whether Business Small range and changeover rules are available.
36 *
37 * The established Business Small bootstrap class is the same capability
38 * boundary used by the canonical Date Selection module. This deliberately
39 * keeps Free and Personal aligned without parsing display version strings.
40 *
41 * @return bool True for Business Small or a higher edition.
42 */
43 public static function supports_advanced_range_rules() {
44 return class_exists( 'wpdev_bk_biz_s' );
45 }
46
47 /**
48 * Determine whether one registered journey is available in this edition.
49 *
50 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
51 *
52 * @return bool True when the current edition owns the journey's features.
53 */
54 public static function is_journey_available( $journey_id ) {
55 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
56
57 return ! in_array( $journey_id, self::get_business_small_journey_ids(), true )
58 || self::supports_advanced_range_rules();
59 }
60
61 /**
62 * Determine whether an identifier belongs to the registered journey catalog.
63 *
64 * This apply-time allow-list is intentionally assembled from the same route
65 * groups used by the wizard. Save handlers can therefore reject a stale or
66 * forged identifier without depending on presentation data.
67 *
68 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
69 *
70 * @return bool True when the identifier belongs to a registered journey.
71 */
72 public static function is_registered_journey( $journey_id ) {
73 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
74
75 return in_array(
76 $journey_id,
77 array_merge( self::get_scheduled_booking_journey_ids(), self::get_booking_resources_journey_ids() ),
78 true
79 );
80 }
81
82 /**
83 * Return presentation-safe edition metadata for one registered journey.
84 *
85 * The DTO contains no capability or mutation details. Templates use it only
86 * to explain the server-owned policy that is independently enforced by the
87 * Setup Wizard field allow-list.
88 *
89 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
90 *
91 * @return array<string,mixed> Availability, badge, and disabled explanation.
92 */
93 public static function get_journey_edition_requirement( $journey_id ) {
94 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
95 $requires_business_small = in_array( $journey_id, self::get_business_small_journey_ids(), true );
96 $is_available = self::is_journey_available( $journey_id );
97 $show_upgrade_badge = $requires_business_small && ! $is_available;
98 $business_small_requirement = __( 'Requires Booking Calendar Business Small or higher.', 'booking' );
99
100 return array(
101 'is_available' => $is_available,
102 'minimum_edition' => $requires_business_small ? 'business_small' : 'free',
103 'badge_label' => $show_upgrade_badge ? __( 'BS+', 'booking' ) : '',
104 'disabled_reason' => $requires_business_small && ! $is_available ? $business_small_requirement : '',
105 );
106 }
107
108 /**
109 * Return journeys that configure bookable full-day Resources.
110 *
111 * These journeys share the same resource-first setup route even though their
112 * Date Selection policies differ. Keeping the complete allow-list here makes
113 * route construction, progressive saves, and Review use one domain rule.
114 *
115 * @return string[] Stable Customer Journey identifiers.
116 */
117 public static function get_booking_resources_journey_ids() {
118 return array(
119 'single_full_day',
120 'multiple_independent_days',
121 'flexible_date_range',
122 'fixed_length_date_range',
123 'check_in_out_changeover',
124 );
125 }
126
127 /**
128 * Determine whether a journey configures bookable full-day Resources.
129 *
130 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
131 *
132 * @return bool True for a supported full-day or date-range journey.
133 */
134 public static function uses_booking_resources_configuration( $journey_id ) {
135 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
136
137 return in_array( $journey_id, self::get_booking_resources_journey_ids(), true );
138 }
139
140 /**
141 * Return journeys that configure explicit time choices.
142 *
143 * @return string[] Stable Customer Journey identifiers.
144 */
145 public static function get_time_configuration_journey_ids() {
146 return array(
147 'start_end_time',
148 'start_time_duration',
149 'fixed_time_slots',
150 'repeated_time_multiple_dates',
151 'first_start_last_end',
152 );
153 }
154
155 /**
156 * Determine whether a journey uses the shared Start and End Times step.
157 *
158 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
159 *
160 * @return bool True when the journey configures explicit time choices.
161 */
162 public static function uses_time_configuration( $journey_id ) {
163 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
164
165 return in_array( $journey_id, self::get_time_configuration_journey_ids(), true );
166 }
167
168 /**
169 * Return journeys that start Date Selection at Single day after transition.
170 *
171 * This allow-list is intentionally independent of route composition so a
172 * future scheduled journey cannot inherit the recommendation accidentally.
173 *
174 * @return string[] Stable Customer Journey identifiers.
175 */
176 public static function get_single_day_transition_journey_ids() {
177 return array(
178 'guided_appointment_flow',
179 'fixed_time_slots',
180 'start_end_time',
181 'start_time_duration',
182 );
183 }
184
185 /**
186 * Determine whether the direct Date Selection transition starts at Single day.
187 *
188 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
189 *
190 * @return bool True only for the four appointment and time-based journeys.
191 */
192 public static function uses_single_day_transition_default( $journey_id ) {
193 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
194
195 return in_array( $journey_id, self::get_single_day_transition_journey_ids(), true );
196 }
197
198 /**
199 * Determine whether a journey configures independent start and end choices.
200 *
201 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
202 *
203 * @return bool True when the Start and End Times step belongs to the route.
204 */
205 public static function uses_start_end_configuration( $journey_id ) {
206 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
207
208 return in_array(
209 $journey_id,
210 array( 'start_end_time', 'repeated_time_multiple_dates', 'first_start_last_end' ),
211 true
212 );
213 }
214
215 /**
216 * Determine whether Working Hours starts disabled for one journey.
217 *
218 * The Repeat one time on multiple dates journey must not silently restrict
219 * configured times to the site's existing weekly schedule. Its Working Hours
220 * step remains available so an administrator can explicitly enable it.
221 *
222 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
223 *
224 * @return bool True when the first Working Hours proposal must be disabled.
225 */
226 public static function uses_disabled_working_hours_default( $journey_id ) {
227 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
228
229 return 'repeated_time_multiple_dates' === $journey_id;
230 }
231
232 /**
233 * Determine whether a journey configures start-time and duration choices.
234 *
235 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
236 *
237 * @return bool True only for the Start Time + Duration journey.
238 */
239 public static function uses_start_duration_configuration( $journey_id ) {
240 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
241
242 return 'start_time_duration' === $journey_id;
243 }
244
245 /**
246 * Determine whether a journey configures explicit fixed time-slot ranges.
247 *
248 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
249 *
250 * @return bool True only for the Fixed Time Slots journey.
251 */
252 public static function uses_fixed_time_slots_configuration( $journey_id ) {
253 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
254
255 return 'fixed_time_slots' === $journey_id;
256 }
257
258 /**
259 * Return journeys that require schedule, form, and appearance setup.
260 *
261 * @return string[] Stable Customer Journey identifiers.
262 */
263 public static function get_scheduled_booking_journey_ids() {
264 return array_merge( array( 'guided_appointment_flow' ), self::get_time_configuration_journey_ids() );
265 }
266
267 /**
268 * Determine whether a journey uses scheduled Booking Form and Appearance setup.
269 *
270 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
271 *
272 * @return bool True when scheduled Booking Form and Appearance steps apply.
273 */
274 public static function uses_scheduled_booking_route( $journey_id ) {
275 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
276
277 return in_array( $journey_id, self::get_scheduled_booking_journey_ids(), true );
278 }
279
280 /**
281 * Determine whether Working Hours belongs to one journey's active route.
282 *
283 * First-date start and last-date end bookings use their explicit boundary
284 * times across a multi-day range. Saving that journey disables the global
285 * Working Hours restriction, so exposing a later page that can turn the
286 * restriction back on would contradict the selected flow.
287 *
288 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
289 *
290 * @return bool True when the route includes Working Hours.
291 */
292 public static function uses_working_hours_configuration( $journey_id ) {
293 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
294
295 return self::uses_scheduled_booking_route( $journey_id ) && 'first_start_last_end' !== $journey_id;
296 }
297
298 /**
299 * Return every journey whose active route contains Booking Form.
300 *
301 * This list is the common contract for route construction, journey-specific
302 * recommendations, contextual validation, and transition tests.
303 *
304 * @return string[] Stable Customer Journey identifiers.
305 */
306 public static function get_booking_form_journey_ids() {
307 return array_merge(
308 self::get_scheduled_booking_journey_ids(),
309 self::get_booking_resources_journey_ids()
310 );
311 }
312
313 /**
314 * Determine whether a journey configures a Booking Form and Appearance.
315 *
316 * Most scheduled journeys also configure Working Hours. The First-date start
317 * and last-date end journey, full-day journeys, and date-range journeys skip
318 * Working Hours while still requiring Booking Form and Appearance.
319 *
320 * @param mixed $journey_id Untrusted or stored Customer Journey identifier.
321 *
322 * @return bool True when Booking Form and Appearance belong to the route.
323 */
324 public static function uses_booking_form_and_appearance_route( $journey_id ) {
325 $journey_id = sanitize_key( is_scalar( $journey_id ) ? (string) $journey_id : '' );
326
327 return in_array( $journey_id, self::get_booking_form_journey_ids(), true );
328 }
329
330 /**
331 * Determine whether Customer Journey was saved after a downstream step.
332 *
333 * Progressive saves record the server-observed checkpoint revision. Comparing
334 * those revisions gives downstream modules a durable transition signal that
335 * survives refreshes and cannot be forged through a query argument. The signal
336 * is consumed when the downstream step is saved at a newer revision.
337 *
338 * @param array<string,mixed> $step_results Normalized progressive-save results.
339 * @param mixed $target_step_id Downstream step whose last save is compared.
340 *
341 * @return bool True when Customer Journey has a newer verified save result.
342 */
343 public static function has_pending_transition_for_step( array $step_results, $target_step_id ) {
344 $target_step_id = sanitize_key( is_scalar( $target_step_id ) ? (string) $target_step_id : '' );
345 if ( '' === $target_step_id ) {
346 return false;
347 }
348
349 $journey_result = isset( $step_results['customer_journey'] ) && is_array( $step_results['customer_journey'] )
350 ? $step_results['customer_journey']
351 : array();
352 if ( 'saved' !== ( isset( $journey_result['status'] ) ? $journey_result['status'] : '' ) ) {
353 return false;
354 }
355
356 $target_result = isset( $step_results[ $target_step_id ] ) && is_array( $step_results[ $target_step_id ] )
357 ? $step_results[ $target_step_id ]
358 : array();
359 $journey_revision = max( 0, (int) ( isset( $journey_result['source_revision'] ) ? $journey_result['source_revision'] : 0 ) );
360 $target_revision = 'saved' === ( isset( $target_result['status'] ) ? $target_result['status'] : '' )
361 ? max( 0, (int) ( isset( $target_result['source_revision'] ) ? $target_result['source_revision'] : 0 ) )
362 : 0;
363
364 return $journey_revision > $target_revision;
365 }
366 }
367