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 / step-services / class-wpbc-setup-wizard-services-module.php

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

266 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Reusable module adapter for the Services setup editor.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Expose the Services editor through the common setup-module contract.
14 *
15 * Canonical Service reads and proposal validation remain in the domain service.
16 * This adapter contains consumer-neutral metadata, template, and asset wiring.
17 */
18 final class WPBC_Setup_Wizard_Services_Module implements WPBC_Setup_Wizard_Step_Module, WPBC_Setup_Wizard_Current_Values_Module {
19
20 /** @var WPBC_Setup_Wizard_Services */
21 private $services;
22
23 /**
24 * Build the module around the Services read and validation service.
25 *
26 * @param WPBC_Setup_Wizard_Services|null $services Optional domain service for testing or composition.
27 */
28 public function __construct( $services = null ) {
29 $this->services = $services instanceof WPBC_Setup_Wizard_Services
30 ? $services
31 : new WPBC_Setup_Wizard_Services();
32 }
33
34 /**
35 * Return the stable module and step identifier.
36 *
37 * @return string Stable identifier.
38 */
39 public function get_step_id() {
40 return 'services';
41 }
42
43 /**
44 * Return the rail and template metadata contributed by this module.
45 *
46 * @return array{id:string,label:string,template:string,footer_note:string} Step definition.
47 */
48 public function get_step_definition() {
49 return array(
50 'id' => $this->get_step_id(),
51 'label' => __( 'Services', 'booking' ),
52 'template' => 'step-services',
53 'footer_note' => __( 'Create or update these Appointment Services when you continue. Repeated saves reuse the same Service records.', 'booking' ),
54 );
55 }
56
57 /**
58 * Return the absolute server-owned template path.
59 *
60 * @return string Absolute template path.
61 */
62 public function get_template_path() {
63 return __DIR__ . '/template.php';
64 }
65
66 /**
67 * Return the field names accepted by the Services editor.
68 *
69 * @return string[] Field allow-list.
70 */
71 public function get_field_names() {
72 return array( 'services', 'inactive_service_ids' );
73 }
74
75 /**
76 * Return the fields required when advancing from the Services editor.
77 *
78 * @return string[] Required field identifiers.
79 */
80 public function get_required_fields() {
81 return array( 'services' );
82 }
83
84 /**
85 * Return owner-authorized Services or safe activation suggestions.
86 *
87 * @return array<string,mixed> Initial field values.
88 */
89 public function get_initial_values() {
90 return array(
91 'services' => $this->services->get_initial_services(),
92 'inactive_service_ids' => array(),
93 );
94 }
95
96 /**
97 * Refresh authorized Services and clear consumed Draft-transition intents.
98 *
99 * The inactive ID field is a one-save mutation intent, not durable wizard
100 * state. Reopening the step must rebuild Services from the repository and
101 * start with no pending status transitions.
102 *
103 * @return string[] Current-value field identifiers.
104 */
105 public function get_current_value_field_names() {
106 return $this->get_field_names();
107 }
108
109 /**
110 * Return other step values required by the Services presentation.
111 *
112 * @return string[] No dependencies are required.
113 */
114 public function get_context_dependencies() {
115 return array();
116 }
117
118 /**
119 * Build the Services editor template context.
120 *
121 * @param array<string,mixed> $field_values Validated Services field values.
122 * @param array<string,mixed> $consumer_context Read-only step results used to reconcile canonical identities.
123 *
124 * @return array<string,mixed> Authorized presentation context.
125 */
126 public function get_context( array $field_values, array $consumer_context = array() ) {
127 $field_values = $this->reconcile_saved_service_state( $field_values, $consumer_context );
128
129 return $this->services->get_context( $field_values );
130 }
131
132 /**
133 * Reconcile proposal identities and consume completed lifecycle intents.
134 *
135 * Newly created Services retain their wizard draft IDs in checkpoint values.
136 * Their canonical IDs live in the saved step result, so those mappings must be
137 * restored before the browser decides whether Trash removes an unsaved proposal
138 * or stages a canonical Service for Draft. Explicit Draft-transition IDs are
139 * one-shot intents and are removed only after the save result is confirmed.
140 *
141 * @param array<string,mixed> $field_values Validated Services field values.
142 * @param array<string,mixed> $consumer_context Read-only route context with saved step results.
143 *
144 * @return array<string,mixed> Reconciled presentation values.
145 */
146 private function reconcile_saved_service_state( array $field_values, array $consumer_context ) {
147 $step_result = isset( $consumer_context['_step_results']['services'] ) && is_array( $consumer_context['_step_results']['services'] )
148 ? $consumer_context['_step_results']['services']
149 : array();
150
151 if ( 'saved' !== sanitize_key( isset( $step_result['status'] ) ? (string) $step_result['status'] : '' ) ) {
152 return $field_values;
153 }
154
155 $service_mappings = $this->get_saved_service_mappings( $step_result );
156 $services = isset( $field_values['services'] ) && is_array( $field_values['services'] )
157 ? $field_values['services']
158 : array();
159
160 foreach ( $services as $service_index => $service ) {
161 if ( ! is_array( $service ) || ! empty( $service['source_service_id'] ) ) {
162 continue;
163 }
164
165 $draft_id = isset( $service['draft_id'] ) ? sanitize_key( (string) $service['draft_id'] ) : '';
166 if ( '' !== $draft_id && ! empty( $service_mappings[ $draft_id ] ) ) {
167 $services[ $service_index ]['source_service_id'] = $service_mappings[ $draft_id ];
168 }
169 }
170
171 $field_values['services'] = $services;
172 $field_values['inactive_service_ids'] = array();
173
174 return $field_values;
175 }
176
177 /**
178 * Extract canonical Service IDs from one verified Services save result.
179 *
180 * @param array<string,mixed> $step_result Normalized saved step result.
181 *
182 * @return array<string,int> Canonical Service IDs keyed by wizard draft ID.
183 */
184 private function get_saved_service_mappings( array $step_result ) {
185 $stored_mappings = array_merge(
186 isset( $step_result['created_ids'] ) ? (array) $step_result['created_ids'] : array(),
187 isset( $step_result['updated_ids'] ) ? (array) $step_result['updated_ids'] : array()
188 );
189 $service_mappings = array();
190
191 foreach ( $stored_mappings as $stored_mapping ) {
192 if ( is_scalar( $stored_mapping ) && preg_match( '/^service:([a-z0-9_-]{1,64}):([0-9]+)$/', (string) $stored_mapping, $matches ) ) {
193 $service_mappings[ $matches[1] ] = absint( $matches[2] );
194 }
195 }
196
197 return $service_mappings;
198 }
199
200 /**
201 * Validate the Services proposal transport field.
202 *
203 * @param string $field_id Stable field identifier.
204 * @param mixed $raw_value Untrusted Service proposals.
205 * @param bool $is_required Whether at least one complete Service is required.
206 *
207 * @return array<int,array<string,mixed>>|int[]|WP_Error Normalized field value or an error.
208 */
209 public function validate_field( $field_id, $raw_value, $is_required ) {
210 if ( 'services' === $field_id ) {
211 return $this->services->validate_services( $raw_value, $is_required );
212 }
213
214 if ( 'inactive_service_ids' === $field_id ) {
215 return $this->services->validate_inactive_service_ids( $raw_value );
216 }
217
218 return new WP_Error( 'wpbc_setup_wizard_services_field_unknown', __( 'The Services editor received an unsupported field.', 'booking' ) );
219 }
220
221 /**
222 * Enqueue compiled Services assets and presentation translations.
223 *
224 * @param string $module_url Absolute URL to the Setup Wizard module root.
225 * @param string|false $asset_version Plugin version used for cache busting.
226 * @param string $shared_style_handle Consumer shell style handle.
227 * @param string $shared_script_handle Consumer shell script handle.
228 *
229 * @return void
230 */
231 public function enqueue_assets( $module_url, $asset_version, $shared_style_handle, $shared_script_handle ) {
232 $module_url = trailingslashit( $module_url );
233
234 if ( function_exists( 'wpbc_load_js__required_for_media_upload' ) ) {
235 wpbc_load_js__required_for_media_upload();
236 }
237
238 wp_enqueue_style( 'wpbc-setup-wizard-services', $module_url . 'step-services/_out/step-services.css', array( $shared_style_handle ), $asset_version );
239 wp_enqueue_script( 'wpbc-setup-wizard-services', $module_url . 'step-services/_out/step-services.js', array( 'jquery', $shared_script_handle ), $asset_version, true );
240 wp_localize_script(
241 'wpbc-setup-wizard-services',
242 'wpbc_setup_wizard_services',
243 array(
244 'i18n' => array(
245 'service_title_required' => __( 'Enter a title for every Service.', 'booking' ),
246 'service_number_invalid' => __( 'Enter a valid number within the available range.', 'booking' ),
247 'service_limit' => __( 'The maximum number of Service drafts has been reached.', 'booking' ),
248 /* translators: %s: Service title. */
249 'remove_service' => __( 'Remove %s', 'booking' ),
250 /* translators: %s: Service title. */
251 'move_service_to_draft' => __( 'Move %s to Draft', 'booking' ),
252 /* translators: %s: Service title. */
253 'service_draft_pending' => __( '%s will move to Draft when you save.', 'booking' ),
254 'service_removed' => __( 'Unsaved Service removed from this setup.', 'booking' ),
255 'new_service' => __( 'New Service', 'booking' ),
256 'hour_singular' => __( 'hour', 'booking' ),
257 'hour_plural' => __( 'hours', 'booking' ),
258 'minute_singular' => __( 'minute', 'booking' ),
259 'minute_plural' => __( 'minutes', 'booking' ),
260 'duration_joiner' => _x( 'and', 'Duration parts separator', 'booking' ),
261 ),
262 )
263 );
264 }
265 }
266