PluginProbe
Booking Calendar / 11.6.1
Booking Calendar v11.6.1
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 10.11 All 204 releases
booking / includes / page-appointment-services / appointment_services__page.php

appointment_services__page.php in Booking Calendar 11.6.1, at includes/page-appointment-services/appointment_services__page.php

634 lines 28.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /** AJAX Appointment Services admin page. @package Booking Calendar */
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * Enqueue Appointment Services management-page styles.
9 *
10 * @param string $where_to_load Booking Calendar asset context.
11 *
12 * @return void
13 */
14 function wpbc_appointment_services_enqueue_css( $where_to_load ) {
15 if ( ! in_array( $where_to_load, array( 'admin', 'both' ), true ) ) {
16 return;
17 }
18
19 $is_resources_page_with_mode_guide = function_exists( 'wpbc_is_resources_page' )
20 && wpbc_is_resources_page();
21
22 if ( $is_resources_page_with_mode_guide ) {
23 wp_enqueue_style(
24 'wpbc-appointment-provider-tools',
25 trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_provider_tools.css',
26 array(),
27 WP_BK_VERSION_NUM
28 );
29 }
30
31 if ( ! wpbc_appointment_services__is_page() ) {
32 return;
33 }
34
35 WPBC_UI_Catalog::enqueue_styles();
36 wp_enqueue_style( 'wpbc-appointment-services-page', trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_services_page.css', array( 'wpbc-ui-catalog' ), WP_BK_VERSION_NUM );
37 }
38 add_action( 'wpbc_enqueue_css_files', 'wpbc_appointment_services_enqueue_css', 66 );
39
40
41 /**
42 * Resolve the Service that should be selected when the catalog first opens.
43 *
44 * An explicit `service_id` request always wins. The first owner-visible active
45 * Service is selected automatically only on the Service and Provider Setup
46 * Wizard step, leaving ordinary Services-page visits unchanged.
47 *
48 * @return int Initial Service ID, or zero when the inspector should stay closed.
49 */
50 function wpbc_appointment_services_get_initial_service_id() {
51
52 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only selection state.
53 $requested_service_id = isset( $_GET['service_id'] ) && is_scalar( $_GET['service_id'] )
54 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only selection state.
55 ? absint( wp_unslash( $_GET['service_id'] ) )
56 : 0;
57
58 if ( $requested_service_id ) {
59 return $requested_service_id;
60 }
61
62 if (
63 ! function_exists( 'wpbc_setup_wizard_page__is_active_step' )
64 || ! wpbc_setup_wizard_page__is_active_step( 'service_provider' )
65 || ! function_exists( 'wpbc_appointment_services_repository' )
66 ) {
67 return 0;
68 }
69
70 $services = wpbc_appointment_services_repository()->list_items(
71 array(
72 'status' => 'active',
73 'sort_by' => 'service_id',
74 'sort_order' => 'asc',
75 'limit' => 1,
76 )
77 );
78
79 if ( is_wp_error( $services ) || empty( $services[0]['service_id'] ) ) {
80 return 0;
81 }
82
83 return absint( $services[0]['service_id'] );
84 }
85
86 /**
87 * Migrate released Service listing preferences into the shared catalog namespace.
88 *
89 * The migration runs only when the new site-local preference payload has never
90 * been created. Existing shared preferences, including an explicit reset, are
91 * never replaced by older listing values.
92 *
93 * @param array<string,mixed> $configuration Registered Service catalog configuration.
94 *
95 * @return void
96 */
97 function wpbc_appointment_services_migrate_catalog_preferences( $configuration ) {
98 $user_id = get_current_user_id();
99 $namespace = WPBC_UI_Catalog_Preferences::get_namespace( 'appointment_services_catalog' );
100 if ( ! $user_id || '' === $namespace || false !== get_user_option( $namespace, $user_id ) ) {
101 return;
102 }
103
104 $legacy_preferences = get_user_option( 'wpbc_admin_listing_preferences', $user_id );
105 $legacy_preferences = is_array( $legacy_preferences ) && isset( $legacy_preferences['appointment_services_catalog'] ) && is_array( $legacy_preferences['appointment_services_catalog'] )
106 ? $legacy_preferences['appointment_services_catalog']
107 : array();
108 if ( empty( $legacy_preferences ) ) {
109 return;
110 }
111
112 $request_values = array_intersect_key(
113 $legacy_preferences,
114 array(
115 'items_per_page' => true,
116 'sort_by' => true,
117 'sort_order' => true,
118 'column_order' => true,
119 )
120 );
121 if ( isset( $legacy_preferences['visible_fields'] ) && is_array( $legacy_preferences['visible_fields'] ) ) {
122 $request_values['visible_columns'] = $legacy_preferences['visible_fields'];
123 }
124 $legacy_filters = isset( $legacy_preferences['filters'] ) && is_array( $legacy_preferences['filters'] )
125 ? $legacy_preferences['filters']
126 : array();
127 $service_request = WPBC_Appointment_Services_Catalog_Request::create(
128 array(
129 'status' => isset( $legacy_filters['status'] ) ? $legacy_filters['status'] : 'all',
130 'resource_id' => isset( $legacy_filters['resource_id'] ) ? $legacy_filters['resource_id'] : 0,
131 )
132 );
133
134 $request = WPBC_UI_Catalog_Request::create( $configuration, $request_values );
135 if ( ! is_wp_error( $request ) && ! is_wp_error( $service_request ) ) {
136 WPBC_UI_Catalog_Preferences::save(
137 'appointment_services_catalog',
138 $request,
139 array(
140 'status' => $service_request->get( 'status', 'all' ),
141 'resource_id' => $service_request->get( 'resource_id', 0 ),
142 ),
143 $user_id,
144 1
145 );
146 }
147 }
148
149 /**
150 * Return validated initial URL overrides for the Services catalog.
151 *
152 * @return array<string,mixed> Shared catalog URL overrides.
153 */
154 function wpbc_appointment_services_get_initial_catalog_overrides() {
155 $parameter_map = array(
156 'page_number' => 'catalog_page',
157 'items_per_page' => 'catalog_per_page',
158 'sort_by' => 'catalog_sort',
159 'sort_order' => 'catalog_order',
160 'search' => 'catalog_search',
161 'visible_columns' => 'catalog_columns',
162 'column_order' => 'catalog_column_order',
163 'template_pack' => 'template_pack',
164 );
165 $url_overrides = array();
166 foreach ( $parameter_map as $request_key => $parameter_name ) {
167 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial catalog state.
168 if ( ! isset( $_GET[ $parameter_name ] ) ) {
169 continue;
170 }
171 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Validated by WPBC_UI_Catalog_Request.
172 $request_value = wp_unslash( $_GET[ $parameter_name ] );
173 if ( in_array( $request_key, array( 'visible_columns', 'column_order' ), true ) && is_scalar( $request_value ) ) {
174 $request_value = '' === trim( (string) $request_value ) ? array() : explode( ',', (string) $request_value );
175 }
176 $url_overrides[ $request_key ] = $request_value;
177 }
178
179 return $url_overrides;
180 }
181
182 /**
183 * Build the shared Services catalog browser configuration.
184 *
185 * @return array<string,mixed> Browser-safe Service and catalog configuration.
186 */
187 function wpbc_appointment_services_get_catalog_client_config() {
188 $catalog_id = 'appointment_services_catalog';
189 $domain_configuration = wpbc_appointment_services_get_catalog_config();
190 $configuration = WPBC_UI_Catalog_Registry::get_instance()->get_configuration( $catalog_id );
191 if ( empty( $configuration ) ) {
192 return array();
193 }
194
195 wpbc_appointment_services_migrate_catalog_preferences( $configuration );
196 $stored_preferences = WPBC_UI_Catalog_Preferences::load( $catalog_id );
197 $shared_request = WPBC_UI_Catalog_Request::create(
198 $configuration,
199 array( 'request_id' => 1 ),
200 $stored_preferences,
201 wpbc_appointment_services_get_initial_catalog_overrides(),
202 true
203 );
204 $service_values = array(
205 'status' => isset( $stored_preferences['status'] ) ? $stored_preferences['status'] : 'all',
206 'resource_id' => isset( $stored_preferences['resource_id'] ) ? $stored_preferences['resource_id'] : 0,
207 );
208 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only initial Provider filter.
209 if ( isset( $_GET['provider_id'] ) ) {
210 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Validated by the Service request object.
211 $service_values['resource_id'] = wp_unslash( $_GET['provider_id'] );
212 }
213 $service_request = WPBC_Appointment_Services_Catalog_Request::create( $service_values );
214 $auto_load = ! is_wp_error( $shared_request ) && ! is_wp_error( $service_request );
215 if ( ! $auto_load ) {
216 $shared_request = WPBC_UI_Catalog_Request::create( $configuration, array( 'request_id' => 1 ) );
217 if ( is_wp_error( $shared_request ) ) {
218 return array();
219 }
220 $initial_error = is_wp_error( $service_request ) ? $service_request : new WP_Error( 'wpbc_appointment_services_invalid_request', __( 'The Services request is invalid.', 'booking' ) );
221 $initial_response = WPBC_UI_Catalog_Response::from_wp_error( $catalog_id, 1, $initial_error );
222 $service_request = WPBC_Appointment_Services_Catalog_Request::create();
223 } else {
224 $empty_response = WPBC_UI_Catalog_Response::create_empty( $catalog_id, $shared_request );
225 $initial_response = is_wp_error( $empty_response )
226 ? WPBC_UI_Catalog_Response::from_wp_error( $catalog_id, 1, $empty_response )
227 : $empty_response->to_array();
228 }
229
230 $initial_request = $shared_request->to_array();
231 $initial_request['status'] = $service_request->get( 'status', 'all' );
232 $initial_request['resource_id'] = $service_request->get( 'resource_id', 0 );
233 $client_configuration = WPBC_UI_Catalog::get_client_configuration( $catalog_id, 'wpbc_appointment_services_catalog', $initial_request, $initial_response );
234 $client_configuration['ajax_url'] = admin_url( 'admin-ajax.php' );
235 $client_configuration['auto_load'] = $auto_load;
236 $client_configuration['nonce'] = wp_create_nonce( $configuration['nonce_name'] );
237 $default_request = WPBC_UI_Catalog_Request::create( $configuration );
238 if ( is_wp_error( $default_request ) ) {
239 return array();
240 }
241 $client_configuration['default_request'] = $default_request->to_array();
242 $client_configuration['default_request']['status'] = 'all';
243 $client_configuration['default_request']['resource_id'] = 0;
244 $client_configuration['url_parameters'] = array(
245 'page_number' => 'catalog_page',
246 'items_per_page' => 'catalog_per_page',
247 'sort_by' => 'catalog_sort',
248 'sort_order' => 'catalog_order',
249 'search' => 'catalog_search',
250 'visible_columns' => 'catalog_columns',
251 'column_order' => 'catalog_column_order',
252 'template_pack' => 'template_pack',
253 'resource_id' => 'provider_id',
254 );
255 $client_configuration['filters'] = $domain_configuration['filters'];
256 $client_configuration['actions'] = $domain_configuration['actions'];
257
258 return $client_configuration;
259 }
260
261
262 /**
263 * Enqueue and configure the AJAX Appointment Services management client.
264 *
265 * @param string $where_to_load Booking Calendar asset context.
266 *
267 * @return void
268 */
269 function wpbc_appointment_services_enqueue_js( $where_to_load ) {
270 if ( ! in_array( $where_to_load, array( 'admin', 'both' ), true ) || ! wpbc_appointment_services__is_page() ) {
271 return;
272 }
273 WPBC_UI_Catalog::enqueue_scripts();
274 wpbc_load_js__required_for_media_upload();
275 $selected_service_id = wpbc_appointment_services_get_initial_service_id();
276 $default_provider_ids = array();
277
278 if ( ! class_exists( 'wpdev_bk_personal' ) ) {
279 $default_provider_id = function_exists( 'wpbc_get_default_resource' ) ? absint( wpbc_get_default_resource() ) : 1;
280 if ( ! $default_provider_id ) {
281 $default_provider_id = 1;
282 }
283
284 $default_provider_ids[] = $default_provider_id;
285 }
286
287 wp_enqueue_script( 'wpbc-appointment-services-page', trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/appointment_services_page.js', array(
288 'jquery',
289 'wpbc_all',
290 'wpbc-ui-catalog',
291 'wpbc-ui-catalog-actions',
292 ), WP_BK_VERSION_NUM, array( 'in_footer' => WPBC_JS_IN_FOOTER ) );
293
294 wp_localize_script( 'wpbc-appointment-services-page', 'wpbc_appointment_services_config', array(
295 'ajax_url' => admin_url( 'admin-ajax.php' ),
296 'nonce' => wp_create_nonce( 'wpbc_appointment_services_ajax_nonce' ),
297 'actions' => array(
298 'list' => 'WPBC_AJX_APPOINTMENT_SERVICES_LIST',
299 'load' => 'WPBC_AJX_APPOINTMENT_SERVICE_LOAD',
300 'save' => 'WPBC_AJX_APPOINTMENT_SERVICE_SAVE',
301 'duplicate' => 'WPBC_AJX_APPOINTMENT_SERVICE_DUPLICATE',
302 'archive' => 'WPBC_AJX_APPOINTMENT_SERVICE_ARCHIVE',
303 'inline_schema' => 'WPBC_AJX_APPOINTMENT_SERVICES_INLINE_SCHEMA',
304 'bulk_contract' => 'WPBC_AJX_APPOINTMENT_SERVICES_BULK_CONTRACT',
305 'preview' => 'WPBC_AJX_APPOINTMENT_SERVICES_CATALOG_PREVIEW',
306 'apply' => 'WPBC_AJX_APPOINTMENT_SERVICES_CATALOG_APPLY',
307 'delete_preview' => 'WPBC_AJX_APPOINTMENT_SERVICES_DELETE_PREVIEW',
308 'delete_apply' => 'WPBC_AJX_APPOINTMENT_SERVICES_DELETE_APPLY',
309 ),
310 'selected_id' => $selected_service_id,
311 'focus_section' => wpbc_appointment_services_get_requested_focus_section(),
312 'default_provider_ids' => $default_provider_ids,
313 'pricing_available' => wpbc_appointment_services_is_pricing_available(),
314 'catalog' => wpbc_appointment_services_get_catalog_client_config(),
315 'i18n' => array(
316 'loading' => __( 'Loading Services', 'booking' ) . '...',
317 'empty' => __( 'No Services yet', 'booking' ),
318 'empty_help' => __( 'Create the first Service to define what customers can book.', 'booking' ),
319 'no_providers' => __( 'No Providers available', 'booking' ),
320 'no_providers_help'=> __( 'Create a Provider before assigning Services and accepting Appointments.', 'booking' ),
321 'not_connected' => __( 'The Services database is not ready. Reload this page as an administrator or reactivate Booking Calendar to finish the database upgrade.', 'booking' ),
322 'load_failed' => __( 'Services could not be loaded.', 'booking' ),
323 'operation_failed' => __( 'The Service editor could not be opened. Please reload the page and try again.', 'booking' ),
324 'save_failed' => __( 'The Service could not be saved.', 'booking' ),
325 'duplicate_failed' => __( 'The Service could not be duplicated.', 'booking' ),
326 'archive_failed' => __( 'The Service could not be archived.', 'booking' ),
327 'preview_failed' => __( 'The Service changes could not be reviewed.', 'booking' ),
328 'apply_failed' => __( 'The Service changes could not be applied.', 'booking' ),
329 'delete_preview_failed' => __( 'The Service deletion could not be reviewed.', 'booking' ),
330 'delete_apply_failed' => __( 'The selected Services could not be permanently deleted.', 'booking' ),
331 'confirm_archive' => __( 'Archive this Service?', 'booking' ),
332 'confirm_discard' => __( 'Discard unsaved Service changes?', 'booking' ),
333 'untitled' => __( 'Untitled Service', 'booking' ),
334 'new_service' => __( 'New Service', 'booking' ),
335 'create_service_title' => __( 'Add Service', 'booking' ),
336 'create_service_description' => __( 'Create a Service without leaving the catalog.', 'booking' ),
337 'edit_service_title' => __( 'Edit Service', 'booking' ),
338 'edit_service_description' => __( 'Update this Service without leaving the catalog.', 'booking' ),
339 'inspector_context_new' => __( 'New', 'booking' ),
340 /* translators: %d: Service ID. */
341 'inspector_context_id' => __( 'ID: %d', 'booking' ),
342 'active' => __( 'Active', 'booking' ),
343 'draft' => __( 'Draft', 'booking' ),
344 'archived' => __( 'Archived', 'booking' ),
345 'no_provider' => __( 'No Providers assigned', 'booking' ),
346 'no_availability' => __( 'No weekly availability', 'booking' ),
347 'more_providers' => __( 'more Providers', 'booking' ),
348 'available' => __( 'Available', 'booking' ),
349 'unavailable' => __( 'Unavailable', 'booking' ),
350 'available_providers' => __( 'Available Providers: %s', 'booking' ),
351 'no_available_providers' => __( 'No assigned Providers are available', 'booking' ),
352 'edit_availability' => __( 'Edit availability for %s', 'booking' ),
353 'edit_provider_availability' => __( 'Edit Provider availability', 'booking' ),
354 'column_id' => __( 'ID', 'booking' ),
355 'column_service' => __( 'Service', 'booking' ),
356 'column_duration' => __( 'Duration', 'booking' ),
357 'column_price' => __( 'Price', 'booking' ),
358 'column_providers' => __( 'Providers', 'booking' ),
359 'column_weekly_availability' => __( 'Weekly Availability', 'booking' ),
360 'column_status' => __( 'Status', 'booking' ),
361 'column_actions' => __( 'Actions', 'booking' ),
362 /* translators: %s: Duration in minutes. */
363 'duration_minutes' => __( '%s min', 'booking' ),
364 /* translators: 1: Buffer before in minutes, 2: Buffer after in minutes. */
365 'buffers_summary' => __( 'Buffers: %1$s / %2$s min', 'booking' ),
366 /* translators: 1: Buffer before in minutes, 2: Buffer after in minutes. */
367 'buffers_tooltip' => __( 'Buffer before: %1$s min; Buffer after: %2$s min', 'booking' ),
368 /* translators: 1: Service title, 2: Service description. */
369 'service_thumbnail_tooltip' => __( 'Title: %1$s — Description: %2$s', 'booking' ),
370 'no_description' => __( 'No description', 'booking' ),
371 'edit' => __( 'Edit Service', 'booking' ),
372 'archive' => __( 'Archive Service', 'booking' ),
373 /* translators: %s: Service title. */
374 'select_service' => __( 'Select %s', 'booking' ),
375 'select_all' => __( 'Select all visible Services', 'booking' ),
376 'editing_rows' => __( 'Editing rows', 'booking' ),
377 /* translators: %s: Number of changed Service rows. */
378 'changed_rows' => __( '%s changed rows', 'booking' ),
379 'changed' => __( 'Changed', 'booking' ),
380 'inline_help' => __( 'Edit safe fields on this page, then review all changes before applying them.', 'booking' ),
381 'inline_schema_failed' => __( 'Safe inline Service fields could not be loaded.', 'booking' ),
382 'bulk_contract_failed' => __( 'Safe fields could not be determined for this Service selection.', 'booking' ),
383 'cancel' => __( 'Cancel', 'booking' ),
384 'review_changes' => __( 'Review changes', 'booking' ),
385 'apply_changes' => __( 'Apply changes', 'booking' ),
386 'bulk_edit_title' => __( 'Edit selected Services', 'booking' ),
387 'bulk_edit_description' => __( 'Enable only the fields that should change for every selected Service.', 'booking' ),
388 'inline_review_title' => __( 'Review inline changes', 'booking' ),
389 'bulk_review_title' => __( 'Review bulk changes', 'booking' ),
390 'review_confirmation' => __( 'Confirm the row-specific changes before applying them.', 'booking' ),
391 'review_description' => __( 'No Service will change until you choose Apply changes.', 'booking' ),
392 'showing' => __( 'Showing %1$s–%2$s of %3$s Services', 'booking' ),
393 ),
394 'currency_symbol' => wpbc_appointment_services_is_pricing_available() && function_exists( 'wpbc_get_currency_symbol' ) ? wpbc_get_currency_symbol() : '',
395 'weekdays' => array(
396 __( 'Monday', 'booking' ),
397 __( 'Tuesday', 'booking' ),
398 __( 'Wednesday', 'booking' ),
399 __( 'Thursday', 'booking' ),
400 __( 'Friday', 'booking' ),
401 __( 'Saturday', 'booking' ),
402 __( 'Sunday', 'booking' ),
403 ),
404 ) );
405 }
406 add_action( 'wpbc_enqueue_js_files', 'wpbc_appointment_services_enqueue_js', 66 );
407
408 /**
409 * Print Service-owned catalog templates on the Services page only.
410 *
411 * @return void
412 */
413 function wpbc_appointment_services_print_catalog_templates() {
414 static $templates_printed = false;
415 if ( $templates_printed || ! wpbc_appointment_services__is_page() ) {
416 return;
417 }
418 $templates_printed = true;
419 WPBC_UI_Catalog::print_templates( 'appointment_services_catalog' );
420 }
421 add_action( 'admin_footer', 'wpbc_appointment_services_print_catalog_templates', 40 );
422
423 /**
424 * Render the global Add Service action in the Booking Calendar top toolbar.
425 *
426 * Creating a Service is a page-level action, while saving remains contextual
427 * to the Service editor in the right inspector.
428 *
429 * @param string $page_tag Current Booking Calendar page tag.
430 * @param string $active_page_tab Current active page tab.
431 * @param string $active_page_subtab Current active page subtab.
432 *
433 * @return void
434 */
435 function wpbc_appointment_services_render_top_toolbar( $page_tag, $active_page_tab, $active_page_subtab ) {
436 if (
437 ! wpbc_appointment_services__is_page()
438 || ! current_user_can( wpbc_appointment_services_get_manage_capability() )
439 ) {
440 return;
441 }
442 ?>
443 <div class="wpbc_ui_el__buttons_group wpbc_appointment_services__top_toolbar">
444 <button type="button" class="button button-primary wpbc_appointment_services__add" disabled>
445 <span class="wpbc-bi-plus-lg" aria-hidden="true"></span>
446 <?php esc_html_e( 'Add Service', 'booking' ); ?>
447 </button>
448 </div>
449 <?php
450 }
451 add_action( 'wpbc_ui_el__top_nav__content_end', 'wpbc_appointment_services_render_top_toolbar', 20, 3 );
452
453
454 /** Appointment Services page definition for the shared Booking Calendar shell. */
455 class WPBC_Page_Appointment_Services extends WPBC_Page_Structure {
456
457 /**
458 * Return the WordPress admin page slug when the user may access Services.
459 *
460 * @return string Page slug, or a non-matching random value when unauthorized.
461 */
462 public function in_page() {
463 if ( ! current_user_can( wpbc_appointment_services_get_manage_capability() ) ) {
464 return (string) wp_rand( 100000, 1000000 );
465 }
466
467 return 'wpbc-services';
468 }
469
470 /**
471 * Define the Appointment Services tab and shared page-shell behavior.
472 *
473 * @return array<string,array<string,mixed>> Page tab definition.
474 */
475 public function tabs() {
476 $service_hint = wpbc_appointment_services_is_pricing_available()
477 ? __( 'Create Services and configure duration, buffers, price, Providers, and Booking Form.', 'booking' )
478 : __( 'Create Services and configure duration, buffers, Providers, and Booking Form.', 'booking' );
479
480 return array(
481 'appointment_services' => array(
482 'is_show_top_path' => true,
483 'is_show_top_navigation' => false,
484 'left_navigation__default_view_mode' => 'compact',
485 'right_vertical_sidebar__is_show' => true,
486 'right_vertical_sidebar__default_view_mode' => 'none',
487 'right_vertical_sidebar__content_click_collapse_mode' => 'none',
488 'right_vertical_sidebar_compact__is_show' => true,
489 'top_path' => array( 'root_title' => false ),
490 'top_path_title' => __( 'Services', 'booking' ),
491 'title' => __( 'Services', 'booking' ),
492 'page_title' => __( 'Services', 'booking' ),
493 'hint' => $service_hint,
494 'font_icon' => 'wpbc-bi-grid',
495 'default' => true,
496 'disabled' => false,
497 'hided' => false,
498 'subtabs' => array(),
499 ),
500 );
501 }
502
503 /**
504 * Render the compact Settings and Help controls for the right sidebar.
505 *
506 * @return void
507 */
508 public function right_sidebar_compact_content() {
509 WPBC_UI_Sidebar_Panels::render_rightbar_tabs( array(
510 array(
511 'id' => 'wpbc_tab_service_settings',
512 'panel_id' => 'wpbc_service__inspector_settings',
513 'title' => __( 'Settings', 'booking' ),
514 'icon' => 'wpbc_icn_tune',
515 'selected' => true,
516 ),
517 array(
518 'id' => 'wpbc_tab_service_help',
519 'panel_id' => 'wpbc_service__inspector_help',
520 'title' => __( 'Help', 'booking' ),
521 'icon' => 'wpbc-bi-info-circle',
522 ),
523 ), array(
524 'aria_label' => __( 'Service Panels', 'booking' ),
525 'context' => 'appointment_services',
526 'class' => 'wpbc_appointment_services__rightbar_tabs',
527 ) );
528 }
529
530 /**
531 * Render the right-sidebar Settings and Help inspector panels.
532 *
533 * @return void
534 */
535 public function right_sidebar_content() {
536 ?>
537 <div class="wpbc_bfb__panel--library wpbc_rightbar_palette wpbc_appointment_services__rightbar"><?php
538 WPBC_UI_Sidebar_Panels::render_panel( array(
539 'id' => 'wpbc_service__inspector_settings',
540 'labelledby' => 'wpbc_tab_service_settings',
541 'class' => 'wpbc_appointment_services__inspector_settings',
542 ), 'wpbc_appointment_services_render_settings_panel' );
543 WPBC_UI_Sidebar_Panels::render_panel( array(
544 'id' => 'wpbc_service__inspector_help',
545 'labelledby' => 'wpbc_tab_service_help',
546 'class' => 'wpbc_appointment_services__inspector_help',
547 'hidden' => true,
548 ), 'wpbc_appointment_services_render_help_panel' );
549 ?></div><?php
550 }
551
552 /**
553 * Render the AJAX-driven Service filters and management table shell.
554 *
555 * @return void|false False when the current user is unauthorized.
556 */
557 public function content() {
558 if ( ! current_user_can( wpbc_appointment_services_get_manage_capability() ) ) {
559 return false;
560 }
561 $providers = wpbc_appointment_services_get_provider_options();
562 $resource_capability = class_exists( 'wpdev_bk_personal' )
563 ? get_bk_option( 'booking_user_role_resources' )
564 : get_bk_option( 'booking_user_role_settings' );
565 $can_manage_providers = $resource_capability && current_user_can( $resource_capability );
566 $selected_provider_id = isset( $_GET['provider_id'] ) ? absint( $_GET['provider_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
567 if ( ! isset( $providers[ $selected_provider_id ] ) ) {
568 $selected_provider_id = 0;
569 }
570 ?>
571 <div class="wpbc_appointment_services_page" data-wpbc-appointment-services-page="1">
572 <div class="wpbc_appointment_services__filters">
573 <div class="wpbc_appointment_services__status_filters" role="group" aria-label="<?php
574 esc_attr_e( 'Filter Services by status', 'booking' ); ?>">
575 <button type="button" class="wpbc_appointment_services__status_filter is-active"
576 data-service-status="all" aria-pressed="true"><span><?php
577 esc_html_e( 'All', 'booking' ); ?></span><strong data-service-count="all">0</strong>
578 </button>
579 <button type="button" class="wpbc_appointment_services__status_filter" data-service-status="active"
580 aria-pressed="false"><span><?php
581 esc_html_e( 'Active', 'booking' ); ?></span><strong data-service-count="active">0</strong>
582 </button>
583 <button type="button" class="wpbc_appointment_services__status_filter"
584 data-service-status="inactive" aria-pressed="false"><span><?php
585 esc_html_e( 'Draft', 'booking' ); ?></span><strong data-service-count="inactive">0</strong>
586 </button>
587 <button type="button" class="wpbc_appointment_services__status_filter"
588 data-service-status="archived" aria-pressed="false"><span><?php
589 esc_html_e( 'Archived', 'booking' ); ?></span><strong
590 data-service-count="archived">0</strong></button>
591 <span class="wpbc_appointment_services__filter_divider" aria-hidden="true"></span>
592 <span class="wpbc_appointment_services__provider_total"><span><?php
593 esc_html_e( 'Providers', 'booking' ); ?></span><strong data-provider-count>0</strong></span>
594 </div>
595 <div class="wpbc_appointment_services__filter_controls">
596 <div class="wpbc_appointment_services__search">
597 <span class="wpbc-bi-search" aria-hidden="true"></span>
598 <label class="screen-reader-text" for="wpbc_service_search"><?php esc_html_e( 'Search Services', 'booking' ); ?></label>
599 <input type="search" id="wpbc_service_search" placeholder="<?php esc_attr_e( 'Search Services', 'booking' ); ?>" autocomplete="off" data-wpbc-ui-catalog-search />
600 <button type="button" class="wpbc_appointment_services__search_clear" aria-label="<?php esc_attr_e( 'Clear Service search', 'booking' ); ?>" data-wpbc-ui-catalog-search-clear data-wpbc-appointment-services-search-clear hidden>
601 <span class="wpbc-bi-x-lg" aria-hidden="true"></span>
602 </button>
603 </div>
604 <label class="screen-reader-text" for="wpbc_service_provider_filter"><?php
605 esc_html_e( 'Filter by Provider', 'booking' ); ?></label>
606 <select id="wpbc_service_provider_filter">
607 <option value="0"><?php
608 esc_html_e( 'All Providers', 'booking' ); ?></option><?php
609 foreach ( $providers as $provider_id => $provider_title ) : ?>
610 <option value="<?php
611 echo esc_attr( $provider_id ); ?>" <?php selected( $selected_provider_id, absint( $provider_id ) ); ?>><?php
612 echo esc_html( $provider_title ); ?></option><?php
613 endforeach; ?></select>
614 </div>
615 </div>
616 <div class="wpbc_appointment_services__content" aria-live="polite" aria-busy="true">
617 <div class="wpbc_appointment_services__provider_notice" hidden>
618 <span class="wpbc-bi-exclamation-circle" aria-hidden="true"></span>
619 <div><strong><?php esc_html_e( 'No Providers available', 'booking' ); ?></strong>
620 <span><?php esc_html_e( 'Create a Provider before assigning Services and accepting Appointments.', 'booking' ); ?></span></div>
621 <?php if ( $can_manage_providers ) : ?>
622 <a class="button button-secondary" href="<?php echo esc_url( admin_url( 'admin.php?page=wpbc-resources' ) ); ?>"><?php esc_html_e( 'Manage Providers', 'booking' ); ?></a>
623 <?php endif; ?>
624 </div>
625 <div id="wpbc_appointment_services_catalog"></div>
626 <noscript><p><?php esc_html_e( 'JavaScript is required to manage Services.', 'booking' ); ?></p></noscript>
627 </div>
628 </div>
629 <?php
630 }
631 }
632
633 add_action( 'wpbc_menu_created', array( new WPBC_Page_Appointment_Services(), '__construct' ) );
634