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-step-registry.php

class-wpbc-setup-wizard-step-registry.php in Booking Calendar 11.9, at includes/page-setup-wizard/class-wpbc-setup-wizard-step-registry.php

310 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ordered step registry for the Setup Wizard module.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Own the allow-listed Setup Wizard step order and open-ended rail presentation.
14 */
15 final class WPBC_Setup_Wizard_Step_Registry {
16
17 /** @var WPBC_Setup_Wizard_Step_Module_Registry */
18 private $module_registry;
19
20 /**
21 * Build the route registry around the reusable module registry.
22 *
23 * @param WPBC_Setup_Wizard_Step_Module_Registry|null $module_registry Optional shared module registry.
24 */
25 public function __construct( $module_registry = null ) {
26 $this->module_registry = $module_registry instanceof WPBC_Setup_Wizard_Step_Module_Registry
27 ? $module_registry
28 : new WPBC_Setup_Wizard_Step_Module_Registry();
29 }
30
31 /**
32 * Return the ordered implemented step definitions.
33 *
34 * The complete allow-list includes branch-specific pages. Active-route
35 * methods expose only the pages selected by the validated draft, so the rail
36 * remains open ended and never advertises an unrelated branch.
37 *
38 * @return array<string,array<string,string>> Steps keyed by stable identifier.
39 */
40 public function get_steps() {
41 $core_steps = array(
42 'welcome' => array(
43 'id' => 'welcome',
44 'label' => __( 'Welcome', 'booking' ),
45 'template' => 'step-welcome',
46 'footer_note' => __( 'Your changes are saved as you continue. Back only navigates and does not roll back completed steps.', 'booking' ),
47 ),
48 'business_details' => array(
49 'id' => 'business_details',
50 'label' => __( 'Business details', 'booking' ),
51 'template' => 'step-business-details',
52 'footer_note' => __( 'Business details are saved for Setup Wizard guidance and do not change Booking Calendar settings.', 'booking' ),
53 ),
54 'date_time_formats' => array(
55 'id' => 'date_time_formats',
56 'label' => __( 'Dates & times', 'booking' ),
57 'template' => 'step-date-time-formats',
58 'footer_note' => __( 'Save & continue updates these Date and time settings immediately.', 'booking' ),
59 ),
60 'booking_experience' => array(
61 'id' => 'booking_experience',
62 'label' => __( 'Terminology', 'booking' ),
63 'template' => 'step-booking-experience',
64 'header_template' => 'step-booking-experience-toolbar',
65 'footer_note' => __( 'Save & continue updates Booking Mode immediately.', 'booking' ),
66 ),
67 'customer_journey' => array(
68 'id' => 'customer_journey',
69 'label' => __( 'Customer journey', 'booking' ),
70 'template' => 'step-customer-journey',
71 'footer_note' => __( 'Customer Journey and its required setup defaults are saved; changing it marks dependent steps for review.', 'booking' ),
72 ),
73 );
74
75 $module_steps = array_diff_key( $this->module_registry->get_step_definitions(), $core_steps );
76
77 return array_merge( $core_steps, $module_steps );
78 }
79
80 /**
81 * Return the ordered route selected by the current validated draft.
82 *
83 * Date Selection follows Customer Journey for every route so customer-facing
84 * calendar behavior is explicit before journey-specific configuration. Days
85 * Off is also common to every supported journey. In Guided appointment flow,
86 * Services and Working Hours are configured between Date Selection and Days
87 * Off for Guided appointments. Time-based journeys configure their reusable
88 * start/end, start/duration, or fixed-slot choices immediately after Date
89 * Selection.
90 * Guided and time-based routes then share Days Off, Booking Form, and
91 * Appearance; only Guided appointment flow includes Services. Working Hours
92 * is also shared except by First-date start and last-date end, whose boundary
93 * times must not be restricted by the weekly schedule.
94 * Full-day and date-range routes share Booking Resources, Days Off, Booking
95 * Form, and Appearance without adding the time-oriented Working Hours step.
96 * Business details is omitted on live-demo sites and WordPress Playground so
97 * shared or temporary visitors are not asked to enter site or contact
98 * information. Publish & integrate is appended to every non-demo route and
99 * records only a future integration plan.
100 * Review Setup closes every route and is the explicit revalidation boundary
101 * after earlier progressive saves and before later complex step handlers are
102 * introduced.
103 *
104 * @param array<string,mixed> $draft_values Normalized values keyed by step.
105 *
106 * @return string[] Ordered active step identifiers.
107 */
108 public function get_active_step_ids( array $draft_values = array() ) {
109 $step_ids = array( 'welcome' );
110 if ( WPBC_Setup_Wizard_Environment_Policy::allows_business_details() ) {
111 $step_ids[] = 'business_details';
112 }
113 $step_ids = array_merge( $step_ids, array( 'date_time_formats', 'booking_experience', 'customer_journey' ) );
114 $journey = isset( $draft_values['customer_journey']['customer_journey'] )
115 ? sanitize_key( (string) $draft_values['customer_journey']['customer_journey'] )
116 : '';
117 $step_ids[] = 'date_selection';
118 if ( 'guided_appointment_flow' === $journey ) {
119 $step_ids[] = 'services';
120 } elseif ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_booking_resources_configuration( $journey ) ) {
121 $step_ids[] = 'booking_resources';
122 } elseif ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_start_duration_configuration( $journey ) ) {
123 $step_ids[] = 'start_duration_times';
124 } elseif ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_fixed_time_slots_configuration( $journey ) ) {
125 $step_ids[] = 'fixed_time_slots';
126 } elseif ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_start_end_configuration( $journey ) ) {
127 $step_ids[] = 'start_end_times';
128 }
129 if ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_working_hours_configuration( $journey ) ) {
130 $step_ids[] = 'working_hours';
131 }
132 $step_ids[] = 'days_off';
133 if ( WPBC_Setup_Wizard_Customer_Journey_Policy::uses_booking_form_and_appearance_route( $journey ) ) {
134 $step_ids[] = 'booking_form_template';
135 $step_ids[] = 'appearance';
136 }
137 if ( $this->module_registry->is_module_available( 'publish_integration', $draft_values ) ) {
138 $step_ids[] = 'publish_integration';
139 }
140 $step_ids[] = 'review_setup';
141
142 return $step_ids;
143 }
144
145 /**
146 * Return one registered step.
147 *
148 * @param string $step_id Proposed step identifier.
149 *
150 * @return array<string,string>|null Registered step or null.
151 */
152 public function get_step( $step_id ) {
153 $steps = $this->get_steps();
154
155 return isset( $steps[ $step_id ] ) ? $steps[ $step_id ] : null;
156 }
157
158 /**
159 * Return the first registered step identifier.
160 *
161 * @return string First stable step identifier.
162 */
163 public function get_first_step_id() {
164 $step_ids = array_keys( $this->get_steps() );
165
166 return (string) reset( $step_ids );
167 }
168
169 /**
170 * Normalize a stored or requested step to an allow-listed identifier.
171 *
172 * @param mixed $step_id Untrusted or stored value.
173 * @param array<string,mixed> $draft_values Normalized values keyed by step.
174 *
175 * @return string Valid registered step identifier.
176 */
177 public function normalize_step_id( $step_id, array $draft_values = array() ) {
178 $normalized_step_id = sanitize_key( is_scalar( $step_id ) ? (string) $step_id : '' );
179 $active_step_ids = $this->get_active_step_ids( $draft_values );
180
181 if ( in_array( $normalized_step_id, $active_step_ids, true ) ) {
182 return $normalized_step_id;
183 }
184
185 /*
186 * A draft may have been saved on Business details before its site became a
187 * live demo. Continue at the next stable core step instead of moving that
188 * draft to the final journey-specific page.
189 */
190 if ( 'business_details' === $normalized_step_id && in_array( 'date_time_formats', $active_step_ids, true ) ) {
191 return 'date_time_formats';
192 }
193
194 return null !== $this->get_step( $normalized_step_id ) ? (string) end( $active_step_ids ) : $this->get_first_step_id();
195 }
196
197 /**
198 * Resolve an adjacent step without accepting a client-selected target.
199 *
200 * @param string $current_step_id Current allow-listed step identifier.
201 * @param string $direction Either `next` or `back`.
202 * @param array<string,mixed> $draft_values Normalized values keyed by step.
203 *
204 * @return string|null Adjacent step identifier, or null at a boundary.
205 */
206 public function get_adjacent_step_id( $current_step_id, $direction, array $draft_values = array() ) {
207 $step_ids = $this->get_active_step_ids( $draft_values );
208 $current_step_id = $this->normalize_step_id( $current_step_id, $draft_values );
209 $current_step_index = array_search( $current_step_id, $step_ids, true );
210 $offset = 'next' === $direction ? 1 : -1;
211 $target_index = $current_step_index + $offset;
212
213 return isset( $step_ids[ $target_index ] ) ? $step_ids[ $target_index ] : null;
214 }
215
216 /**
217 * Return all ordered step identifiers before the supplied current step.
218 *
219 * @param string $current_step_id Current allow-listed step identifier.
220 * @param array<string,mixed> $draft_values Normalized values keyed by step.
221 *
222 * @return string[] Ordered completed step identifiers.
223 */
224 public function get_completed_step_ids( $current_step_id, array $draft_values = array() ) {
225 $step_ids = $this->get_active_step_ids( $draft_values );
226 $current_step_id = $this->normalize_step_id( $current_step_id, $draft_values );
227 $current_step_index = array_search( $current_step_id, $step_ids, true );
228
229 return array_slice( $step_ids, 0, $current_step_index );
230 }
231
232 /**
233 * Build visible rail records without revealing unchosen future branches.
234 *
235 * @param string $current_step_id Current allow-listed step identifier.
236 * @param array<string,mixed> $draft_values Normalized values keyed by step.
237 *
238 * @return array<int,array{id:string,label:string,state:string}> Visible rail items.
239 */
240 public function get_rail_items( $current_step_id, array $draft_values = array() ) {
241 $current_step_id = $this->normalize_step_id( $current_step_id, $draft_values );
242 $completed_ids = $this->get_completed_step_ids( $current_step_id, $draft_values );
243 $visible_ids = array_merge( $completed_ids, array( $current_step_id ) );
244 $steps = $this->get_steps();
245 $rail_items = array();
246
247 foreach ( $visible_ids as $step_id ) {
248 $rail_items[] = array(
249 'id' => $step_id,
250 'label' => $steps[ $step_id ]['label'],
251 'state' => $step_id === $current_step_id ? 'current' : 'complete',
252 );
253 }
254
255 return $rail_items;
256 }
257
258 /**
259 * Determine whether at least one registered step follows the current step.
260 *
261 * The shell renders an open-ended neutral marker on the last implemented step
262 * because later branch steps are not yet registered.
263 *
264 * @param string $current_step_id Current allow-listed step identifier.
265 * @param array<string,mixed> $draft_values Normalized values keyed by step.
266 *
267 * @return bool True when a registered adjacent step exists.
268 */
269 public function has_next_step( $current_step_id, array $draft_values = array() ) {
270 return null !== $this->get_adjacent_step_id( $current_step_id, 'next', $draft_values );
271 }
272
273 /**
274 * Determine whether a previous registered step exists.
275 *
276 * @param string $current_step_id Current allow-listed step identifier.
277 * @param array<string,mixed> $draft_values Normalized values keyed by step.
278 *
279 * @return bool True when Back navigation is available.
280 */
281 public function has_previous_step( $current_step_id, array $draft_values = array() ) {
282 return null !== $this->get_adjacent_step_id( $current_step_id, 'back', $draft_values );
283 }
284
285 /**
286 * Return context-sensitive copy below the open-ended journey marker.
287 *
288 * @param string $current_step_id Current allow-listed step identifier.
289 * @param array<string,mixed> $draft_values Normalized values keyed by step.
290 *
291 * @return string Translated rail note.
292 */
293 public function get_rail_note( $current_step_id, array $draft_values = array() ) {
294 $current_step_id = $this->normalize_step_id( $current_step_id, $draft_values );
295 if ( 'publish_integration' === $current_step_id ) {
296 return __( 'Your setup is ready to review after you choose how customers will access booking.', 'booking' );
297 }
298
299 if ( 'review_setup' === $current_step_id ) {
300 return __( 'Review every selection before approving your setup plan.', 'booking' );
301 }
302
303 if ( in_array( $current_step_id, array( 'customer_journey', 'date_selection', 'days_off', 'services', 'booking_resources', 'start_end_times', 'start_duration_times', 'fixed_time_slots', 'working_hours', 'booking_form_template', 'appearance' ), true ) ) {
304 return __( 'Your next steps will follow the customer journey you choose.', 'booking' );
305 }
306
307 return __( 'Your next steps will appear as you continue.', 'booking' );
308 }
309 }
310