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 / _functions / booking-resources-catalog-compatibility.php

booking-resources-catalog-compatibility.php in Booking Calendar 11.6.1, at includes/_functions/booking-resources-catalog-compatibility.php

539 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Temporary shared catalog compatibility controls for 11.6.
4 *
5 * @package Booking Calendar
6 * @since 11.6.0
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Return the minimum paid-edition version that supports the new catalog.
15 *
16 * @return string Required Booking Calendar Pro version.
17 */
18 function wpbc_booking_resources_catalog_get_minimum_pro_version() {
19 return '11.6';
20 }
21
22 /**
23 * Determine whether a paid Booking Calendar edition is active.
24 *
25 * Constants are preferred because they are available for every paid edition.
26 * The class fallback keeps mixed-version installations detectable when an old
27 * paid bootstrap does not define the current constants.
28 *
29 * @return bool True when a paid edition is active.
30 */
31 function wpbc_booking_resources_catalog_is_pro_active() {
32 return defined( 'WPBC_PRO_VERSION_NUM' ) || defined( 'WPBC_PRO_FILE' ) || class_exists( 'wpdev_bk_personal' );
33 }
34
35 /**
36 * Return the detected paid-edition version.
37 *
38 * @return string Sanitized Pro version, or an empty string when unavailable.
39 */
40 function wpbc_booking_resources_catalog_get_pro_version() {
41 if ( ! defined( 'WPBC_PRO_VERSION_NUM' ) ) {
42 return '';
43 }
44
45 return sanitize_text_field( (string) WPBC_PRO_VERSION_NUM );
46 }
47
48 /**
49 * Determine whether a detected paid-edition version is safe to compare.
50 *
51 * Mixed-version installations can expose an empty or malformed constant while
52 * the paid bootstrap is only partly loaded. Treating that value as compatible
53 * would allow the new renderer to call APIs that the active paid edition does
54 * not provide, so only conventional dotted WordPress plugin versions are
55 * accepted.
56 *
57 * @param string $pro_version Detected paid-edition version.
58 *
59 * @return bool True when the version is a dotted numeric version with an
60 * optional prerelease or build suffix.
61 */
62 function wpbc_booking_resources_catalog_is_valid_pro_version( $pro_version ) {
63 if ( ! is_scalar( $pro_version ) ) {
64 return false;
65 }
66
67 $pro_version = trim( (string) $pro_version );
68
69 return 1 === preg_match( '/^\d+(?:\.\d+)+(?:[-+][0-9A-Za-z.-]+)?$/', $pro_version );
70 }
71
72 /**
73 * Resolve whether one edition/version combination supports the new catalog.
74 *
75 * This pure policy boundary exists so release tests can exercise Free, current
76 * Pro, old Pro, and incomplete mixed-version installations without redefining
77 * immutable PHP constants or changing the active plugin set.
78 *
79 * @param bool $is_pro_active Whether any paid edition is active.
80 * @param string $pro_version Detected paid-edition version, or an empty value.
81 *
82 * @return bool True when the installation is compatible with the new catalog.
83 */
84 function wpbc_booking_resources_catalog_resolve_pro_compatibility( $is_pro_active, $pro_version ) {
85 if ( ! $is_pro_active ) {
86 return true;
87 }
88
89 if ( ! wpbc_booking_resources_catalog_is_valid_pro_version( $pro_version ) ) {
90 return false;
91 }
92
93 return version_compare( (string) $pro_version, wpbc_booking_resources_catalog_get_minimum_pro_version(), '>=' );
94 }
95
96 /**
97 * Determine whether the active paid edition supports the new catalog.
98 *
99 * Free-only installations are compatible. An active paid edition without a
100 * reliable version is treated conservatively as incompatible.
101 *
102 * @return bool True when the installation may use the new catalog.
103 */
104 function wpbc_booking_resources_catalog_is_pro_compatible() {
105 return wpbc_booking_resources_catalog_resolve_pro_compatibility(
106 wpbc_booking_resources_catalog_is_pro_active(),
107 wpbc_booking_resources_catalog_get_pro_version()
108 );
109 }
110
111 /**
112 * Determine whether the independent catalog runtime is available.
113 *
114 * The saved renderer preference is not changed when a development or rollback
115 * gate is disabled. This allows the installation to return to the preferred
116 * renderer when the catalog runtime becomes available again.
117 *
118 * @return bool True when both 11.6 catalog feature gates are enabled.
119 */
120 function wpbc_booking_resources_catalog_is_available() {
121 return function_exists( 'wpbc_is_11_6_features_enabled' )
122 && wpbc_is_11_6_features_enabled()
123 && function_exists( 'wpbc_is_11_6_catalog_v2_enabled' )
124 && wpbc_is_11_6_catalog_v2_enabled();
125 }
126
127 /**
128 * Return the explicitly stored renderer preference.
129 *
130 * Missing or malformed historical values intentionally resolve to the new
131 * renderer without writing a default during activation or update.
132 *
133 * @return string Either `new` or `legacy`.
134 */
135 function wpbc_booking_resources_catalog_get_stored_renderer() {
136 $stored_renderer = sanitize_key( (string) get_bk_option( 'booking_resources_catalog_renderer' ) );
137
138 return in_array( $stored_renderer, array( 'new', 'legacy' ), true ) ? $stored_renderer : 'new';
139 }
140
141 /**
142 * Return the temporary support override for the Resources renderer.
143 *
144 * The constant is intentionally read at runtime and never copied into the
145 * database. Removing it therefore returns control to the customer's saved
146 * compatibility setting. Invalid and non-scalar values are ignored safely.
147 *
148 * @return string Either `new`, `legacy`, or an empty string when no valid
149 * support override is defined.
150 */
151 function wpbc_booking_resources_catalog_get_constant_renderer() {
152 if ( ! defined( 'WPBC_BOOKING_RESOURCES_CATALOG_MODE' ) ) {
153 return '';
154 }
155
156 $constant_renderer = constant( 'WPBC_BOOKING_RESOURCES_CATALOG_MODE' );
157 if ( ! is_scalar( $constant_renderer ) ) {
158 return '';
159 }
160
161 $constant_renderer = sanitize_key( trim( (string) $constant_renderer ) );
162
163 return in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ? $constant_renderer : '';
164 }
165
166 /**
167 * Return the temporary support override shared by all 11.6 catalogs.
168 *
169 * `WPBC_CATALOG_MODE` is the domain-neutral override. The released
170 * `WPBC_BOOKING_RESOURCES_CATALOG_MODE` constant remains a compatibility alias
171 * and is used when the generic override is absent.
172 *
173 * @return string Either `new`, `legacy`, or an empty string when no valid
174 * support override is defined.
175 */
176 function wpbc_catalogs_get_constant_renderer() {
177 if ( defined( 'WPBC_CATALOG_MODE' ) ) {
178 $constant_renderer = constant( 'WPBC_CATALOG_MODE' );
179 if ( is_scalar( $constant_renderer ) ) {
180 $constant_renderer = sanitize_key( trim( (string) $constant_renderer ) );
181 if ( in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ) {
182 return $constant_renderer;
183 }
184 }
185 }
186
187 return wpbc_booking_resources_catalog_get_constant_renderer();
188 }
189
190 /**
191 * Resolve the renderer for one complete compatibility-policy input.
192 *
193 * The stored preference is presentation-only. Runtime availability and paid
194 * edition compatibility always win. A valid support constant then overrides
195 * the saved presentation preference without changing it. Invalid values safely
196 * use the saved renderer, which itself defaults to `new` without being written.
197 *
198 * @param bool $catalog_available Whether both catalog feature gates are enabled.
199 * @param bool $is_pro_active Whether any paid edition is active.
200 * @param string $pro_version Detected paid-edition version, or an empty value.
201 * @param string $stored_renderer Stored renderer preference.
202 * @param string $constant_renderer Temporary support override, or an empty value.
203 *
204 * @return string Either `new` or `legacy`.
205 */
206 function wpbc_booking_resources_catalog_resolve_renderer(
207 $catalog_available,
208 $is_pro_active,
209 $pro_version,
210 $stored_renderer,
211 $constant_renderer = ''
212 ) {
213 $stored_renderer = is_scalar( $stored_renderer ) ? sanitize_key( (string) $stored_renderer ) : '';
214 $stored_renderer = in_array( $stored_renderer, array( 'new', 'legacy' ), true ) ? $stored_renderer : 'new';
215 $constant_renderer = is_scalar( $constant_renderer ) ? sanitize_key( (string) $constant_renderer ) : '';
216 $constant_renderer = in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ? $constant_renderer : '';
217
218 if ( ! $catalog_available || ! wpbc_booking_resources_catalog_resolve_pro_compatibility( $is_pro_active, $pro_version ) ) {
219 return 'legacy';
220 }
221
222 return '' !== $constant_renderer ? $constant_renderer : $stored_renderer;
223 }
224
225 /**
226 * Return the renderer that may be used by the current installation.
227 *
228 * Compatibility is derived rather than saved. Consequently, temporarily
229 * activating an old paid edition never overwrites an explicit preference.
230 * The temporary support constant also remains request-local and is never
231 * persisted.
232 *
233 * @return string Either `new` or `legacy`.
234 */
235 function wpbc_booking_resources_catalog_get_effective_renderer() {
236 return wpbc_catalogs_get_effective_renderer();
237 }
238
239 /**
240 * Return the renderer shared by catalog pages upgraded in Booking Calendar 11.6.
241 *
242 * The existing `booking_resources_catalog_renderer` option remains the
243 * canonical stored preference to avoid a data migration. Runtime gates, paid
244 * version compatibility, and support constants are derived without replacing
245 * the customer's stored selection.
246 *
247 * @return string Either `new` or `legacy`.
248 */
249 function wpbc_catalogs_get_effective_renderer() {
250 return wpbc_booking_resources_catalog_resolve_renderer(
251 wpbc_booking_resources_catalog_is_available(),
252 wpbc_booking_resources_catalog_is_pro_active(),
253 wpbc_booking_resources_catalog_get_pro_version(),
254 wpbc_booking_resources_catalog_get_stored_renderer(),
255 wpbc_catalogs_get_constant_renderer()
256 );
257 }
258
259 /**
260 * Determine whether upgraded 11.6 catalog pages should use their new renderer.
261 *
262 * @return bool True when new catalog renderers are selected.
263 */
264 function wpbc_catalogs_should_use_new_renderer() {
265 return 'new' === wpbc_catalogs_get_effective_renderer();
266 }
267
268 /**
269 * Determine whether the canonical Resources tab should use the new catalog.
270 *
271 * @return bool True when the independent catalog is the effective renderer.
272 */
273 function wpbc_booking_resources_catalog_should_use_new_renderer() {
274 return 'new' === wpbc_booking_resources_catalog_get_effective_renderer();
275 }
276
277 /**
278 * Determine whether a route targets the default Booking Resources surface.
279 *
280 * The compatibility renderer applies only to the canonical Resources page
281 * when its tab is omitted or explicitly set to `resources`. Sibling tabs keep
282 * their established controllers and are never interpreted as a request for
283 * either the new or legacy default renderer.
284 *
285 * @param string $page_slug Administration page slug.
286 * @param string $tab_slug Administration tab slug, or an empty string when omitted.
287 *
288 * @return bool True for the canonical default Resources route.
289 */
290 function wpbc_booking_resources_catalog_is_default_route( $page_slug, $tab_slug = '' ) {
291 if ( ! is_scalar( $page_slug ) || ! is_scalar( $tab_slug ) ) {
292 return false;
293 }
294
295 $page_slug = sanitize_key( (string) $page_slug );
296 $tab_slug = sanitize_key( (string) $tab_slug );
297
298 return 'wpbc-resources' === $page_slug && in_array( $tab_slug, array( '', 'resources' ), true );
299 }
300
301 /**
302 * Resolve the renderer selected for the current administration request.
303 *
304 * An empty result is intentional for Capacity Rules, Searchable Resources,
305 * MultiUser Users, and every non-Resources request. The default page classes
306 * may still register their tab metadata so sibling-page navigation remains
307 * complete, but only this resolver authorizes their content renderer.
308 *
309 * @return string Either `new`, `legacy`, or an empty string for a sibling route.
310 */
311 function wpbc_booking_resources_catalog_get_request_renderer() {
312 if ( ! is_admin() || wp_doing_ajax() ) {
313 return '';
314 }
315
316 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
317 $page_slug = isset( $_GET['page'] ) && is_scalar( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : '';
318 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
319 if ( isset( $_GET['tab'] ) && ! is_scalar( $_GET['tab'] ) ) {
320 return '';
321 }
322
323 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
324 $tab_slug = isset( $_GET['tab'] ) ? wp_unslash( $_GET['tab'] ) : '';
325
326 if ( ! wpbc_booking_resources_catalog_is_default_route( $page_slug, $tab_slug ) ) {
327 return '';
328 }
329
330 return wpbc_booking_resources_catalog_get_effective_renderer();
331 }
332
333 /**
334 * Redirect temporary parallel catalog aliases to the canonical Resources tab.
335 *
336 * These aliases were used only while the new catalog was developed in
337 * parallel. Redirecting before WordPress resolves plugin pages keeps saved
338 * bookmarks working without registering duplicate visible admin pages.
339 *
340 * @return void
341 */
342 function wpbc_booking_resources_catalog_redirect_parallel_aliases() {
343 if ( ! is_admin() || wp_doing_ajax() ) {
344 return;
345 }
346
347 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route normalization.
348 $page_slug = isset( $_GET['page'] ) && is_scalar( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
349 if ( ! wpbc_booking_resources_catalog_is_parallel_alias( $page_slug ) ) {
350 return;
351 }
352
353 $canonical_url = add_query_arg(
354 array(
355 'page' => 'wpbc-resources',
356 'tab' => 'resources',
357 ),
358 admin_url( 'admin.php' )
359 );
360
361 wp_safe_redirect( $canonical_url, 302, 'Booking Calendar' );
362 exit;
363 }
364
365 /**
366 * Canonicalize the temporary aliases before administration output can begin.
367 *
368 * The admin_init registration is an intentional fallback for non-standard
369 * loaders that include Booking Calendar after plugins_loaded has fired, or
370 * for integrations that temporarily reject an earlier redirect.
371 */
372 add_action( 'plugins_loaded', 'wpbc_booking_resources_catalog_redirect_parallel_aliases', 1 );
373 add_action( 'admin_init', 'wpbc_booking_resources_catalog_redirect_parallel_aliases', 1 );
374
375 /**
376 * Determine whether an administration slug is a temporary catalog alias.
377 *
378 * @param string $page_slug Administration page slug.
379 *
380 * @return bool True for an allow-listed 11.6 parallel-route alias.
381 */
382 function wpbc_booking_resources_catalog_is_parallel_alias( $page_slug ) {
383 if ( ! is_scalar( $page_slug ) ) {
384 return false;
385 }
386
387 return in_array(
388 sanitize_key( (string) $page_slug ),
389 array( 'wpbc-booking-resources', 'wpbc-catalog-booking-resources' ),
390 true
391 );
392 }
393
394 /**
395 * Map the configured Booking Calendar settings role to a capability.
396 *
397 * @return string WordPress capability required to manage plugin settings.
398 */
399 function wpbc_booking_resources_catalog_get_settings_capability() {
400 if ( function_exists( 'wpbc_booking_resources_get_role_capability' ) ) {
401 return wpbc_booking_resources_get_role_capability( 'booking_user_role_settings' );
402 }
403
404 $minimum_role = get_bk_option( 'booking_user_role_settings' );
405 $capabilities = array(
406 'administrator' => 'activate_plugins',
407 'editor' => 'publish_pages',
408 'author' => 'publish_posts',
409 'contributor' => 'edit_posts',
410 'subscriber' => 'read',
411 );
412
413 return isset( $capabilities[ $minimum_role ] ) ? $capabilities[ $minimum_role ] : 'manage_options';
414 }
415
416 /**
417 * Determine whether the current user may manage Booking Calendar settings.
418 *
419 * MultiUser settings remain restricted to the same super-administrator
420 * context used by the General Settings page.
421 *
422 * @return bool True when the current user may view and save the setting.
423 */
424 function wpbc_booking_resources_catalog_can_manage_settings() {
425 $has_settings_capability = current_user_can( wpbc_booking_resources_catalog_get_settings_capability() );
426 $is_multiuser_active = function_exists( 'wpbc_is_mu_user_can_be_here' );
427 $is_multiuser_admin = ! $is_multiuser_active || wpbc_is_mu_user_can_be_here( 'only_super_admin' );
428
429 return wpbc_booking_resources_catalog_resolve_settings_access(
430 $has_settings_capability,
431 $is_multiuser_active,
432 $is_multiuser_admin
433 );
434 }
435
436 /**
437 * Resolve settings access for one capability and MultiUser context.
438 *
439 * Keeping this policy independent of the current user permits deterministic
440 * regular-user and super-administrator release tests while the runtime wrapper
441 * continues to use the established Booking Calendar permission APIs.
442 *
443 * @param bool $has_settings_capability Whether the user has the configured settings capability.
444 * @param bool $is_multiuser_active Whether Booking Calendar MultiUser is active.
445 * @param bool $is_multiuser_admin Whether the user is in its super-administrator context.
446 *
447 * @return bool True when settings access is allowed.
448 */
449 function wpbc_booking_resources_catalog_resolve_settings_access( $has_settings_capability, $is_multiuser_active, $is_multiuser_admin ) {
450 if ( ! $has_settings_capability ) {
451 return false;
452 }
453
454 return ! $is_multiuser_active || (bool) $is_multiuser_admin;
455 }
456
457 /**
458 * Determine whether the current user may act on plugin updates.
459 *
460 * @return bool True for users who can update or otherwise manage plugins.
461 */
462 function wpbc_booking_resources_catalog_can_manage_plugins() {
463 return current_user_can( 'update_plugins' ) || current_user_can( 'activate_plugins' );
464 }
465
466 /**
467 * Return the administration screen used to update the paid plugin.
468 *
469 * Booking Calendar Pro exposes its custom updater on the Plugins screen. On a
470 * multisite installation that screen belongs to Network Admin.
471 *
472 * @return string Absolute Plugins screen URL.
473 */
474 function wpbc_booking_resources_catalog_get_plugin_update_url() {
475 return is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' );
476 }
477
478 /**
479 * Show the persistent mixed-version warning on the legacy Resources page.
480 *
481 * The warning is intentionally not dismissible while the incompatible paid
482 * edition remains active. It informs administrators without blocking the
483 * temporary legacy workflow in 11.6.
484 *
485 * @param string $page_name Booking Calendar settings page identifier.
486 *
487 * @return void
488 */
489 function wpbc_booking_resources_catalog_show_pro_compatibility_notice( $page_name ) {
490 if ( 'resources' !== $page_name || ! wpbc_is_11_6_features_enabled() ) {
491 return;
492 }
493
494 if ( wpbc_booking_resources_catalog_is_pro_compatible() || ! wpbc_booking_resources_catalog_can_manage_plugins() ) {
495 return;
496 }
497
498 $pro_version = wpbc_booking_resources_catalog_get_pro_version();
499 if ( '' === $pro_version ) {
500 $pro_version = __( 'Unknown', 'booking' );
501 }
502
503 $guide_html_id = 'wpbc_resources_booking_resources_warning_116';
504 ?>
505 <div id="<?php echo esc_attr( $guide_html_id ); ?>" class="notice notice-warning wpbc_booking_resources_catalog__compatibility_notice">
506 <?php
507 if ( function_exists( 'wpbc_is_dismissed' ) ) {
508 wpbc_is_dismissed(
509 $guide_html_id,
510 array(
511 'title' => '<span class="wpbc-bi-x-lg" aria-hidden="true"></span><span class="screen-reader-text">' . esc_html__( 'Dismiss', 'booking' ) . '</span>',
512 'hint' => __( 'Dismiss', 'booking' ),
513 'class' => 'wpbc_booking_resources_catalog__compatibility_notice',
514 'is_apply_in_demo' => true,
515 'css' => 'border-radius: 7px;padding: 7px 0 7px 10px;',
516 )
517 );
518 }
519 ?>
520 <p>
521 <?php
522 /* translators: %s: Detected Booking Calendar Pro version. */
523 printf( esc_html( 'Your Booking Calendar Pro version must be updated to 11.6 or newer to use the %s new Booking Resources catalog %s. The legacy Resources page remains available temporarily and is scheduled for removal in Booking Calendar 11.7 - 11.8.' ),
524 '<a href="https://wpbookingcalendar.com/wn/whats-new-in-booking-calendar-update-11-6/" target="_blank">',
525 '</a>'
526 );
527 ?>
528 <br />
529 <?php
530 /* translators: %s: Detected Booking Calendar Pro version. */
531 printf( esc_html__( 'Detected Pro version: %s.', 'booking' ), esc_html( $pro_version ) );
532 ?>
533 <a href="<?php echo esc_url( wpbc_booking_resources_catalog_get_plugin_update_url() ); ?>"><?php esc_html_e( 'Update Booking Calendar Pro', 'booking' ); ?></a>
534 </p>
535 </div>
536 <?php
537 }
538 add_action( 'wpbc_hook_settings_page_header', 'wpbc_booking_resources_catalog_show_pro_compatibility_notice', 20, 1 );
539