← All changes
|
includes/page-appointment-services/appointment_services__functions.php
+33
-276
11.5
→
11.9
View file →
| @@ -103,172 +103,8 @@ | ||
| 103 | 103 | return current_user_can( $availability_role ); |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | /** |
| 107 | - * Render the compact Monday-to-Sunday labels in the Service listing header. | |
| 108 | - * | |
| 109 | - * @param array<string,mixed> $column Normalized shared listing column settings. | |
| 110 | - * @param WPBC_UI_Listing $listing Shared listing instance. | |
| 111 | - * | |
| 112 | - * @return void | |
| 113 | - */ | |
| 114 | -function wpbc_appointment_services_render_weekday_listing_header( $column, $listing ) { | |
| 115 | - unset( $column, $listing ); | |
| 116 | - ?> | |
| 117 | - <span><?php esc_html_e( 'Weekly Availability', 'booking' ); ?></span> | |
| 118 | - <span class="wpbc_appointment_services__weekday_labels" aria-hidden="true"> | |
| 119 | - <i><?php esc_html_e( 'Mon', 'booking' ); ?></i> | |
| 120 | - <i><?php esc_html_e( 'Tue', 'booking' ); ?></i> | |
| 121 | - <i><?php esc_html_e( 'Wed', 'booking' ); ?></i> | |
| 122 | - <i><?php esc_html_e( 'Thu', 'booking' ); ?></i> | |
| 123 | - <i><?php esc_html_e( 'Fri', 'booking' ); ?></i> | |
| 124 | - <i><?php esc_html_e( 'Sat', 'booking' ); ?></i> | |
| 125 | - <i><?php esc_html_e( 'Sun', 'booking' ); ?></i> | |
| 126 | - </span> | |
| 127 | - <?php | |
| 128 | -} | |
| 129 | - | |
| 130 | -/** | |
| 131 | - * Render an accessible-only title for the Service row-actions column. | |
| 132 | - * | |
| 133 | - * @param array<string,mixed> $column Normalized shared listing column settings. | |
| 134 | - * @param WPBC_UI_Listing $listing Shared listing instance. | |
| 135 | - * | |
| 136 | - * @return void | |
| 137 | - */ | |
| 138 | -function wpbc_appointment_services_render_actions_listing_header( $column, $listing ) { | |
| 139 | - unset( $column, $listing ); | |
| 140 | - ?><span class="screen-reader-text"><?php esc_html_e( 'Actions', 'booking' ); ?></span><?php | |
| 141 | -} | |
| 142 | - | |
| 143 | -/** | |
| 144 | - * Render Status and Service ID sorting controls in one listing column. | |
| 145 | - * | |
| 146 | - * Both sort keys use the shared listing request and AJAX controller. Combining | |
| 147 | - * their controls keeps Service ID visible without dedicating another column. | |
| 148 | - * | |
| 149 | - * @param array<string,mixed> $column Normalized shared listing column settings. | |
| 150 | - * @param WPBC_UI_Listing $listing Shared listing instance. | |
| 151 | - * | |
| 152 | - * @return void | |
| 153 | - */ | |
| 154 | -function wpbc_appointment_services_render_status_listing_header( $column, $listing ) { | |
| 155 | - unset( $column ); | |
| 156 | - $current_sorting = $listing->get_sorting_request(); | |
| 157 | - $sort_controls = array( | |
| 158 | - 'status' => array( | |
| 159 | - 'label' => __( 'Status', 'booking' ), | |
| 160 | - 'class' => 'wpbc_appointment_services__status_sort', | |
| 161 | - ), | |
| 162 | - 'service_id' => array( | |
| 163 | - 'label' => __( 'ID', 'booking' ), | |
| 164 | - 'class' => 'wpbc_appointment_services__id_sort', | |
| 165 | - ), | |
| 166 | - ); | |
| 167 | - ?> | |
| 168 | - <div class="wpbc_appointment_services__status_header"> | |
| 169 | - <?php foreach ( $sort_controls as $sort_key => $sort_control ) : ?> | |
| 170 | - <?php | |
| 171 | - $is_active = $sort_key === $current_sorting['sort_by']; | |
| 172 | - $link_class = 'wpbc_ui_listing__sort_link ' . $sort_control['class'] . ( $is_active ? ' is-active' : '' ); | |
| 173 | - $icon_class = $is_active | |
| 174 | - ? ( 'desc' === $current_sorting['sort_order'] ? 'wpbc-bi-arrow-down' : 'wpbc-bi-arrow-up' ) | |
| 175 | - : 'wpbc_icn_import_export'; | |
| 176 | - ?> | |
| 177 | - <a href="#" | |
| 178 | - class="<?php echo esc_attr( $link_class ); ?>" | |
| 179 | - data-wpbc-listing-sort="<?php echo esc_attr( $listing->get_listing_id() ); ?>" | |
| 180 | - data-wpbc-listing-sort-key="<?php echo esc_attr( $sort_key ); ?>"> | |
| 181 | - <span><?php echo esc_html( $sort_control['label'] ); ?></span> | |
| 182 | - <i class="wpbc_ui_listing__sort_icon <?php echo esc_attr( $icon_class ); ?>" aria-hidden="true"></i> | |
| 183 | - </a> | |
| 184 | - <?php endforeach; ?> | |
| 185 | - </div> | |
| 186 | - <?php | |
| 187 | -} | |
| 188 | - | |
| 189 | -/** | |
| 190 | - * Return the shared listing configured for the Services catalog. | |
| 191 | - * | |
| 192 | - * The same instance is used by the page renderer and AJAX endpoint so the | |
| 193 | - * displayed and persisted page sizes always follow one allow-list. | |
| 194 | - * | |
| 195 | - * @return WPBC_UI_Listing Services catalog listing component. | |
| 196 | - */ | |
| 197 | -function wpbc_appointment_services_get_catalog_listing() { | |
| 198 | - static $service_listing = null; | |
| 199 | - | |
| 200 | - if ( null === $service_listing ) { | |
| 201 | - $columns = array( | |
| 202 | - 'service' => array( | |
| 203 | - 'label' => __( 'Service', 'booking' ), | |
| 204 | - 'class' => 'column-service', | |
| 205 | - 'sortable' => 'title', | |
| 206 | - ), | |
| 207 | - 'duration' => array( | |
| 208 | - 'label' => __( 'Duration', 'booking' ), | |
| 209 | - 'class' => 'column-duration', | |
| 210 | - 'sortable' => 'duration', | |
| 211 | - ), | |
| 212 | - ); | |
| 213 | - if ( wpbc_appointment_services_is_pricing_available() ) { | |
| 214 | - $columns['price'] = array( | |
| 215 | - 'label' => __( 'Price', 'booking' ), | |
| 216 | - 'class' => 'column-price', | |
| 217 | - 'sortable' => 'price', | |
| 218 | - ); | |
| 219 | - } | |
| 220 | - $columns += array( | |
| 221 | - 'providers' => array( | |
| 222 | - 'label' => __( 'Providers', 'booking' ), | |
| 223 | - 'class' => 'column-providers', | |
| 224 | - ), | |
| 225 | - 'weekdays' => array( | |
| 226 | - 'class' => 'column-weekdays', | |
| 227 | - 'header_callback' => 'wpbc_appointment_services_render_weekday_listing_header', | |
| 228 | - ), | |
| 229 | - 'status' => array( | |
| 230 | - 'label' => __( 'Status', 'booking' ), | |
| 231 | - 'class' => 'column-status', | |
| 232 | - 'sortable' => 'status', | |
| 233 | - 'header_callback' => 'wpbc_appointment_services_render_status_listing_header', | |
| 234 | - ), | |
| 235 | - 'actions' => array( | |
| 236 | - 'class' => 'column-actions', | |
| 237 | - 'header_callback' => 'wpbc_appointment_services_render_actions_listing_header', | |
| 238 | - ), | |
| 239 | - ); | |
| 240 | - | |
| 241 | - $service_listing = new WPBC_UI_Listing( | |
| 242 | - 'appointment_services_catalog', | |
| 243 | - array( | |
| 244 | - 'aria_label' => __( 'Services catalog', 'booking' ), | |
| 245 | - 'items_per_page_default' => 10, | |
| 246 | - 'items_per_page_options' => array( 5, 10, 50, 100 ), | |
| 247 | - 'sort_by' => 'service_id', | |
| 248 | - 'sort_order' => 'desc', | |
| 249 | - 'sort_keys' => array( 'service_id' ), | |
| 250 | - 'columns' => $columns, | |
| 251 | - 'classes' => array( | |
| 252 | - 'container' => 'wpbc_appointment_services__list', | |
| 253 | - 'table_wrap' => 'wpbc_appointment_services__table_wrap', | |
| 254 | - 'table' => 'wpbc_appointment_services__table', | |
| 255 | - 'footer' => 'wpbc_appointment_services__list_footer', | |
| 256 | - 'result_count' => 'wpbc_appointment_services__result_count', | |
| 257 | - 'items_per_page' => 'wpbc_appointment_services__items_per_page', | |
| 258 | - 'pagination' => 'wpbc_appointment_services__pagination', | |
| 259 | - 'previous' => 'wpbc_appointment_services__page_prev', | |
| 260 | - 'page_label' => 'wpbc_appointment_services__page_label', | |
| 261 | - 'next' => 'wpbc_appointment_services__page_next', | |
| 262 | - ), | |
| 263 | - ) | |
| 264 | - ); | |
| 265 | - } | |
| 266 | - | |
| 267 | - return $service_listing; | |
| 268 | -} | |
| 269 | - | |
| 270 | -/** | |
| 271 | 107 | * Resolve the MultiUser-aware owner for Service queries and writes. |
| 272 | 108 | * |
| 273 | 109 | * @return int Owner user ID, or zero for site-wide ownership. |
| 274 | 110 | */ |
| @@ -405,12 +241,16 @@ | ||
| 405 | 241 | |
| 406 | 242 | /** |
| 407 | 243 | * Resolve the public image configured for one Provider Booking Resource. |
| 408 | 244 | * |
| 409 | - * Provider images are stored by the Business Large Searchable Resources | |
| 410 | - * module. Callers pass that module's option collection so it is loaded only | |
| 411 | - * once when several Providers are rendered. Other editions pass an empty | |
| 412 | - * collection and retain their existing avatar or initials fallback. | |
| 245 | + * Existing Business Large Searchable Resources values remain the fast path | |
| 246 | + * because callers already load that option collection once for a Provider | |
| 247 | + * list. When that legacy field is not configured, the resolver uses the | |
| 248 | + * cross-edition Booking Resource content repository so Free's bundled default | |
| 249 | + * image and pictures saved by the Resource catalog reach every Provider UI. | |
| 250 | + * The guarded fallback keeps this presentation helper safe during partial | |
| 251 | + * bootstrap and compatibility requests where the content repository has not | |
| 252 | + * been loaded yet. | |
| 413 | 253 | * |
| 414 | 254 | * @param int $provider_id Provider Booking Resource ID. |
| 415 | 255 | * @param array<int,array<string,mixed>> $search_options Searchable Resource options keyed by resource ID. |
| 416 | 256 | * |
| @@ -417,21 +257,39 @@ | ||
| 417 | 257 | * @return string Sanitized Provider image URL or an empty string. |
| 418 | 258 | */ |
| 419 | 259 | function wpbc_appointment_services_get_provider_image_url( $provider_id, $search_options ) { |
| 420 | 260 | $provider_id = absint( $provider_id ); |
| 421 | - if ( ! $provider_id || empty( $search_options[ $provider_id ]['picture'] ) ) { | |
| 261 | + if ( ! $provider_id ) { | |
| 422 | 262 | return ''; |
| 423 | 263 | } |
| 424 | 264 | |
| 425 | - $image_value = $search_options[ $provider_id ]['picture']; | |
| 426 | - if ( is_array( $image_value ) ) { | |
| 427 | - $image_value = reset( $image_value ); | |
| 265 | + if ( ! empty( $search_options[ $provider_id ]['picture'] ) ) { | |
| 266 | + $image_value = $search_options[ $provider_id ]['picture']; | |
| 267 | + if ( is_array( $image_value ) ) { | |
| 268 | + $image_value = reset( $image_value ); | |
| 269 | + } | |
| 270 | + if ( ! is_scalar( $image_value ) ) { | |
| 271 | + return ''; | |
| 272 | + } | |
| 273 | + | |
| 274 | + return esc_url_raw( wpbc_lang( (string) $image_value ) ); | |
| 428 | 275 | } |
| 429 | - if ( ! is_scalar( $image_value ) ) { | |
| 276 | + | |
| 277 | + if ( ! function_exists( 'wpbc_booking_resource_content_repository' ) ) { | |
| 430 | 278 | return ''; |
| 431 | 279 | } |
| 432 | 280 | |
| 433 | - return esc_url_raw( wpbc_lang( (string) $image_value ) ); | |
| 281 | + // Booking Calendar Free has exactly one implicit Booking Resource (ID 1). | |
| 282 | + if ( ! class_exists( 'wpdev_bk_personal' ) && 1 !== $provider_id ) { | |
| 283 | + return ''; | |
| 284 | + } | |
| 285 | + | |
| 286 | + $resource_content = wpbc_booking_resource_content_repository()->get( $provider_id ); | |
| 287 | + if ( empty( $resource_content['picture_url'] ) || ! is_scalar( $resource_content['picture_url'] ) ) { | |
| 288 | + return ''; | |
| 289 | + } | |
| 290 | + | |
| 291 | + return esc_url_raw( wpbc_lang( (string) $resource_content['picture_url'] ) ); | |
| 434 | 292 | } |
| 435 | 293 | |
| 436 | 294 | /** |
| 437 | 295 | * Provider presentation data for the Services management table. |
| @@ -437,9 +295,9 @@ | ||
| 437 | 295 | * Provider presentation data for the Services management table. |
| 438 | 296 | * |
| 439 | 297 | * Booking resources remain the availability authority. The default weekday |
| 440 | 298 | * map combines General Availability with each resource's effective Working |
| 441 | - * Time rule. Business Large Searchable Resource pictures are reused before a | |
| 299 | + * Time rule. Cross-edition Booking Resource pictures are reused before a | |
| 442 | 300 | * WordPress user avatar, keeping the administration catalog consistent with |
| 443 | 301 | * the public Appointment flow. Extensions may replace the presentation or |
| 444 | 302 | * recurring-weekday summary through the filters below without changing the |
| 445 | 303 | * Services page contract. |
| @@ -676,109 +534,8 @@ | ||
| 676 | 534 | ); |
| 677 | 535 | $counts['all'] = $counts['active'] + $counts['inactive'] + $counts['archived']; |
| 678 | 536 | |
| 679 | 537 | return $counts; |
| 680 | -} | |
| 681 | - | |
| 682 | -/** | |
| 683 | - * Load one exact page of Services through the shared listing contract. | |
| 684 | - * | |
| 685 | - * Providers implementing count_items() receive the efficient contract: one | |
| 686 | - * grouped count followed by one LIMIT/OFFSET page query. Existing replacement | |
| 687 | - * providers remain compatible through a bounded in-memory pagination fallback, | |
| 688 | - * but should implement count_items() and honor list_items() limit/offset values | |
| 689 | - * before serving large catalogs. | |
| 690 | - * | |
| 691 | - * @param object $provider Service data provider. | |
| 692 | - * @param array $query Search, status, Provider, page, and page-size values. | |
| 693 | - * @param WPBC_UI_Listing $listing Shared listing instance. | |
| 694 | - * | |
| 695 | - * @return array<string,mixed>|WP_Error Normalized Services, counts, and pagination metadata. | |
| 696 | - */ | |
| 697 | -function wpbc_appointment_services_get_catalog_page( $provider, $query, $listing ) { | |
| 698 | - $query = wp_parse_args( | |
| 699 | - is_array( $query ) ? $query : array(), | |
| 700 | - array( | |
| 701 | - 'search' => '', | |
| 702 | - 'status' => 'all', | |
| 703 | - 'resource_id' => 0, | |
| 704 | - 'page_number' => 1, | |
| 705 | - 'items_per_page' => $listing->get_items_per_page(), | |
| 706 | - 'sort_by' => null, | |
| 707 | - 'sort_order' => null, | |
| 708 | - ) | |
| 709 | - ); | |
| 710 | - $status = in_array( $query['status'], array( 'all', 'active', 'inactive', 'archived' ), true ) ? $query['status'] : 'all'; | |
| 711 | - $sorting = $listing->get_sorting_request( $query['sort_by'], $query['sort_order'] ); | |
| 712 | - $query['sort_by'] = $sorting['sort_by']; | |
| 713 | - $query['sort_order'] = $sorting['sort_order']; | |
| 714 | - | |
| 715 | - if ( method_exists( $provider, 'count_items' ) ) { | |
| 716 | - $raw_counts = $provider->count_items( $query ); | |
| 717 | - if ( is_wp_error( $raw_counts ) ) { | |
| 718 | - return $raw_counts; | |
| 719 | - } | |
| 720 | - | |
| 721 | - $counts = wpbc_appointment_services_normalize_status_counts( $raw_counts ); | |
| 722 | - $total_items = 'all' === $status ? $counts['all'] : $counts[ $status ]; | |
| 723 | - $pagination = $listing->get_pagination_data( $total_items, $query['page_number'], $query['items_per_page'] ); | |
| 724 | - $page_query = array_merge( | |
| 725 | - $query, | |
| 726 | - array( | |
| 727 | - 'status' => $status, | |
| 728 | - 'limit' => $pagination['limit'], | |
| 729 | - 'offset' => $pagination['offset'], | |
| 730 | - ) | |
| 731 | - ); | |
| 732 | - $services = $provider->list_items( $page_query ); | |
| 733 | - if ( is_wp_error( $services ) ) { | |
| 734 | - return $services; | |
| 735 | - } | |
| 736 | - | |
| 737 | - $services = array_slice( (array) $services, 0, $pagination['limit'] ); | |
| 738 | - } else { | |
| 739 | - $services = $provider->list_items( | |
| 740 | - array_merge( | |
| 741 | - $query, | |
| 742 | - array( 'status' => 'all' ) | |
| 743 | - ) | |
| 744 | - ); | |
| 745 | - if ( is_wp_error( $services ) ) { | |
| 746 | - return $services; | |
| 747 | - } | |
| 748 | - | |
| 749 | - $all_services = array_map( 'wpbc_appointment_services_normalize_item', (array) $services ); | |
| 750 | - $counts = array( | |
| 751 | - 'all' => count( $all_services ), | |
| 752 | - 'active' => 0, | |
| 753 | - 'inactive' => 0, | |
| 754 | - 'archived' => 0, | |
| 755 | - ); | |
| 756 | - foreach ( $all_services as $service ) { | |
| 757 | - if ( isset( $counts[ $service['status'] ] ) ) { | |
| 758 | - ++$counts[ $service['status'] ]; | |
| 759 | - } | |
| 760 | - } | |
| 761 | - $services = 'all' === $status | |
| 762 | - ? $all_services | |
| 763 | - : array_values( | |
| 764 | - array_filter( | |
| 765 | - $all_services, | |
| 766 | - static function ( $service ) use ( $status ) { | |
| 767 | - return $status === $service['status']; | |
| 768 | - } | |
| 769 | - ) | |
| 770 | - ); | |
| 771 | - $pagination = $listing->get_pagination_data( count( $services ), $query['page_number'], $query['items_per_page'] ); | |
| 772 | - $services = array_slice( $services, $pagination['offset'], $pagination['limit'] ); | |
| 773 | - } | |
| 774 | - | |
| 775 | - return array( | |
| 776 | - 'services' => array_map( 'wpbc_appointment_services_normalize_item', (array) $services ), | |
| 777 | - 'counts' => $counts, | |
| 778 | - 'pagination' => $pagination, | |
| 779 | - 'sorting' => $sorting, | |
| 780 | - ); | |
| 781 | 538 | } |
| 782 | 539 | |
| 783 | 540 | /** |
| 784 | 541 | * Authorize an Appointment Services AJAX request. |