PluginProbe
Booking Calendar / 11.8.4
Booking Calendar v11.8.4
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.8.4, at includes/_functions/booking-resources-catalog-compatibility.php

537 lines 19.3 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 }
124
125 /**
126 * Return the explicitly stored renderer preference.
127 *
128 * Missing or malformed historical values intentionally resolve to the new
129 * renderer without writing a default during activation or update.
130 *
131 * @return string Either `new` or `legacy`.
132 */
133 function wpbc_booking_resources_catalog_get_stored_renderer() {
134 $stored_renderer = sanitize_key( (string) get_bk_option( 'booking_resources_catalog_renderer' ) );
135
136 return in_array( $stored_renderer, array( 'new', 'legacy' ), true ) ? $stored_renderer : 'new';
137 }
138
139 /**
140 * Return the temporary support override for the Resources renderer.
141 *
142 * The constant is intentionally read at runtime and never copied into the
143 * database. Removing it therefore returns control to the customer's saved
144 * compatibility setting. Invalid and non-scalar values are ignored safely.
145 *
146 * @return string Either `new`, `legacy`, or an empty string when no valid
147 * support override is defined.
148 */
149 function wpbc_booking_resources_catalog_get_constant_renderer() {
150 if ( ! defined( 'WPBC_BOOKING_RESOURCES_CATALOG_MODE' ) ) {
151 return '';
152 }
153
154 $constant_renderer = constant( 'WPBC_BOOKING_RESOURCES_CATALOG_MODE' );
155 if ( ! is_scalar( $constant_renderer ) ) {
156 return '';
157 }
158
159 $constant_renderer = sanitize_key( trim( (string) $constant_renderer ) );
160
161 return in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ? $constant_renderer : '';
162 }
163
164 /**
165 * Return the temporary support override shared by all 11.6 catalogs.
166 *
167 * `WPBC_CATALOG_MODE` is the domain-neutral override. The released
168 * `WPBC_BOOKING_RESOURCES_CATALOG_MODE` constant remains a compatibility alias
169 * and is used when the generic override is absent.
170 *
171 * @return string Either `new`, `legacy`, or an empty string when no valid
172 * support override is defined.
173 */
174 function wpbc_catalogs_get_constant_renderer() {
175 if ( defined( 'WPBC_CATALOG_MODE' ) ) {
176 $constant_renderer = constant( 'WPBC_CATALOG_MODE' );
177 if ( is_scalar( $constant_renderer ) ) {
178 $constant_renderer = sanitize_key( trim( (string) $constant_renderer ) );
179 if ( in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ) {
180 return $constant_renderer;
181 }
182 }
183 }
184
185 return wpbc_booking_resources_catalog_get_constant_renderer();
186 }
187
188 /**
189 * Resolve the renderer for one complete compatibility-policy input.
190 *
191 * The stored preference is presentation-only. Runtime availability and paid
192 * edition compatibility always win. A valid support constant then overrides
193 * the saved presentation preference without changing it. Invalid values safely
194 * use the saved renderer, which itself defaults to `new` without being written.
195 *
196 * @param bool $catalog_available Whether both catalog feature gates are enabled.
197 * @param bool $is_pro_active Whether any paid edition is active.
198 * @param string $pro_version Detected paid-edition version, or an empty value.
199 * @param string $stored_renderer Stored renderer preference.
200 * @param string $constant_renderer Temporary support override, or an empty value.
201 *
202 * @return string Either `new` or `legacy`.
203 */
204 function wpbc_booking_resources_catalog_resolve_renderer(
205 $catalog_available,
206 $is_pro_active,
207 $pro_version,
208 $stored_renderer,
209 $constant_renderer = ''
210 ) {
211 $stored_renderer = is_scalar( $stored_renderer ) ? sanitize_key( (string) $stored_renderer ) : '';
212 $stored_renderer = in_array( $stored_renderer, array( 'new', 'legacy' ), true ) ? $stored_renderer : 'new';
213 $constant_renderer = is_scalar( $constant_renderer ) ? sanitize_key( (string) $constant_renderer ) : '';
214 $constant_renderer = in_array( $constant_renderer, array( 'new', 'legacy' ), true ) ? $constant_renderer : '';
215
216 if ( ! $catalog_available || ! wpbc_booking_resources_catalog_resolve_pro_compatibility( $is_pro_active, $pro_version ) ) {
217 return 'legacy';
218 }
219
220 return '' !== $constant_renderer ? $constant_renderer : $stored_renderer;
221 }
222
223 /**
224 * Return the renderer that may be used by the current installation.
225 *
226 * Compatibility is derived rather than saved. Consequently, temporarily
227 * activating an old paid edition never overwrites an explicit preference.
228 * The temporary support constant also remains request-local and is never
229 * persisted.
230 *
231 * @return string Either `new` or `legacy`.
232 */
233 function wpbc_booking_resources_catalog_get_effective_renderer() {
234 return wpbc_catalogs_get_effective_renderer();
235 }
236
237 /**
238 * Return the renderer shared by catalog pages upgraded in Booking Calendar 11.6.
239 *
240 * The existing `booking_resources_catalog_renderer` option remains the
241 * canonical stored preference to avoid a data migration. Runtime gates, paid
242 * version compatibility, and support constants are derived without replacing
243 * the customer's stored selection.
244 *
245 * @return string Either `new` or `legacy`.
246 */
247 function wpbc_catalogs_get_effective_renderer() {
248 return wpbc_booking_resources_catalog_resolve_renderer(
249 wpbc_booking_resources_catalog_is_available(),
250 wpbc_booking_resources_catalog_is_pro_active(),
251 wpbc_booking_resources_catalog_get_pro_version(),
252 wpbc_booking_resources_catalog_get_stored_renderer(),
253 wpbc_catalogs_get_constant_renderer()
254 );
255 }
256
257 /**
258 * Determine whether upgraded 11.6 catalog pages should use their new renderer.
259 *
260 * @return bool True when new catalog renderers are selected.
261 */
262 function wpbc_catalogs_should_use_new_renderer() {
263 return 'new' === wpbc_catalogs_get_effective_renderer();
264 }
265
266 /**
267 * Determine whether the canonical Resources tab should use the new catalog.
268 *
269 * @return bool True when the independent catalog is the effective renderer.
270 */
271 function wpbc_booking_resources_catalog_should_use_new_renderer() {
272 return 'new' === wpbc_booking_resources_catalog_get_effective_renderer();
273 }
274
275 /**
276 * Determine whether a route targets the default Booking Resources surface.
277 *
278 * The compatibility renderer applies only to the canonical Resources page
279 * when its tab is omitted or explicitly set to `resources`. Sibling tabs keep
280 * their established controllers and are never interpreted as a request for
281 * either the new or legacy default renderer.
282 *
283 * @param string $page_slug Administration page slug.
284 * @param string $tab_slug Administration tab slug, or an empty string when omitted.
285 *
286 * @return bool True for the canonical default Resources route.
287 */
288 function wpbc_booking_resources_catalog_is_default_route( $page_slug, $tab_slug = '' ) {
289 if ( ! is_scalar( $page_slug ) || ! is_scalar( $tab_slug ) ) {
290 return false;
291 }
292
293 $page_slug = sanitize_key( (string) $page_slug );
294 $tab_slug = sanitize_key( (string) $tab_slug );
295
296 return 'wpbc-resources' === $page_slug && in_array( $tab_slug, array( '', 'resources' ), true );
297 }
298
299 /**
300 * Resolve the renderer selected for the current administration request.
301 *
302 * An empty result is intentional for Capacity Rules, Searchable Resources,
303 * MultiUser Users, and every non-Resources request. The default page classes
304 * may still register their tab metadata so sibling-page navigation remains
305 * complete, but only this resolver authorizes their content renderer.
306 *
307 * @return string Either `new`, `legacy`, or an empty string for a sibling route.
308 */
309 function wpbc_booking_resources_catalog_get_request_renderer() {
310 if ( ! is_admin() || wp_doing_ajax() ) {
311 return '';
312 }
313
314 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
315 $page_slug = isset( $_GET['page'] ) && is_scalar( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : '';
316 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
317 if ( isset( $_GET['tab'] ) && ! is_scalar( $_GET['tab'] ) ) {
318 return '';
319 }
320
321 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route detection.
322 $tab_slug = isset( $_GET['tab'] ) ? wp_unslash( $_GET['tab'] ) : '';
323
324 if ( ! wpbc_booking_resources_catalog_is_default_route( $page_slug, $tab_slug ) ) {
325 return '';
326 }
327
328 return wpbc_booking_resources_catalog_get_effective_renderer();
329 }
330
331 /**
332 * Redirect temporary parallel catalog aliases to the canonical Resources tab.
333 *
334 * These aliases were used only while the new catalog was developed in
335 * parallel. Redirecting before WordPress resolves plugin pages keeps saved
336 * bookmarks working without registering duplicate visible admin pages.
337 *
338 * @return void
339 */
340 function wpbc_booking_resources_catalog_redirect_parallel_aliases() {
341 if ( ! is_admin() || wp_doing_ajax() ) {
342 return;
343 }
344
345 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only administration route normalization.
346 $page_slug = isset( $_GET['page'] ) && is_scalar( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
347 if ( ! wpbc_booking_resources_catalog_is_parallel_alias( $page_slug ) ) {
348 return;
349 }
350
351 $canonical_url = add_query_arg(
352 array(
353 'page' => 'wpbc-resources',
354 'tab' => 'resources',
355 ),
356 admin_url( 'admin.php' )
357 );
358
359 wp_safe_redirect( $canonical_url, 302, 'Booking Calendar' );
360 exit;
361 }
362
363 /**
364 * Canonicalize the temporary aliases before administration output can begin.
365 *
366 * The admin_init registration is an intentional fallback for non-standard
367 * loaders that include Booking Calendar after plugins_loaded has fired, or
368 * for integrations that temporarily reject an earlier redirect.
369 */
370 add_action( 'plugins_loaded', 'wpbc_booking_resources_catalog_redirect_parallel_aliases', 1 );
371 add_action( 'admin_init', 'wpbc_booking_resources_catalog_redirect_parallel_aliases', 1 );
372
373 /**
374 * Determine whether an administration slug is a temporary catalog alias.
375 *
376 * @param string $page_slug Administration page slug.
377 *
378 * @return bool True for an allow-listed 11.6 parallel-route alias.
379 */
380 function wpbc_booking_resources_catalog_is_parallel_alias( $page_slug ) {
381 if ( ! is_scalar( $page_slug ) ) {
382 return false;
383 }
384
385 return in_array(
386 sanitize_key( (string) $page_slug ),
387 array( 'wpbc-booking-resources', 'wpbc-catalog-booking-resources' ),
388 true
389 );
390 }
391
392 /**
393 * Map the configured Booking Calendar settings role to a capability.
394 *
395 * @return string WordPress capability required to manage plugin settings.
396 */
397 function wpbc_booking_resources_catalog_get_settings_capability() {
398 if ( function_exists( 'wpbc_booking_resources_get_role_capability' ) ) {
399 return wpbc_booking_resources_get_role_capability( 'booking_user_role_settings' );
400 }
401
402 $minimum_role = get_bk_option( 'booking_user_role_settings' );
403 $capabilities = array(
404 'administrator' => 'activate_plugins',
405 'editor' => 'publish_pages',
406 'author' => 'publish_posts',
407 'contributor' => 'edit_posts',
408 'subscriber' => 'read',
409 );
410
411 return isset( $capabilities[ $minimum_role ] ) ? $capabilities[ $minimum_role ] : 'manage_options';
412 }
413
414 /**
415 * Determine whether the current user may manage Booking Calendar settings.
416 *
417 * MultiUser settings remain restricted to the same super-administrator
418 * context used by the General Settings page.
419 *
420 * @return bool True when the current user may view and save the setting.
421 */
422 function wpbc_booking_resources_catalog_can_manage_settings() {
423 $has_settings_capability = current_user_can( wpbc_booking_resources_catalog_get_settings_capability() );
424 $is_multiuser_active = function_exists( 'wpbc_is_mu_user_can_be_here' );
425 $is_multiuser_admin = ! $is_multiuser_active || wpbc_is_mu_user_can_be_here( 'only_super_admin' );
426
427 return wpbc_booking_resources_catalog_resolve_settings_access(
428 $has_settings_capability,
429 $is_multiuser_active,
430 $is_multiuser_admin
431 );
432 }
433
434 /**
435 * Resolve settings access for one capability and MultiUser context.
436 *
437 * Keeping this policy independent of the current user permits deterministic
438 * regular-user and super-administrator release tests while the runtime wrapper
439 * continues to use the established Booking Calendar permission APIs.
440 *
441 * @param bool $has_settings_capability Whether the user has the configured settings capability.
442 * @param bool $is_multiuser_active Whether Booking Calendar MultiUser is active.
443 * @param bool $is_multiuser_admin Whether the user is in its super-administrator context.
444 *
445 * @return bool True when settings access is allowed.
446 */
447 function wpbc_booking_resources_catalog_resolve_settings_access( $has_settings_capability, $is_multiuser_active, $is_multiuser_admin ) {
448 if ( ! $has_settings_capability ) {
449 return false;
450 }
451
452 return ! $is_multiuser_active || (bool) $is_multiuser_admin;
453 }
454
455 /**
456 * Determine whether the current user may act on plugin updates.
457 *
458 * @return bool True for users who can update or otherwise manage plugins.
459 */
460 function wpbc_booking_resources_catalog_can_manage_plugins() {
461 return current_user_can( 'update_plugins' ) || current_user_can( 'activate_plugins' );
462 }
463
464 /**
465 * Return the administration screen used to update the paid plugin.
466 *
467 * Booking Calendar Pro exposes its custom updater on the Plugins screen. On a
468 * multisite installation that screen belongs to Network Admin.
469 *
470 * @return string Absolute Plugins screen URL.
471 */
472 function wpbc_booking_resources_catalog_get_plugin_update_url() {
473 return is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' );
474 }
475
476 /**
477 * Show the persistent mixed-version warning on the legacy Resources page.
478 *
479 * The warning is intentionally not dismissible while the incompatible paid
480 * edition remains active. It informs administrators without blocking the
481 * temporary legacy workflow in 11.6.
482 *
483 * @param string $page_name Booking Calendar settings page identifier.
484 *
485 * @return void
486 */
487 function wpbc_booking_resources_catalog_show_pro_compatibility_notice( $page_name ) {
488 if ( 'resources' !== $page_name || ! wpbc_is_11_6_features_enabled() ) {
489 return;
490 }
491
492 if ( wpbc_booking_resources_catalog_is_pro_compatible() || ! wpbc_booking_resources_catalog_can_manage_plugins() ) {
493 return;
494 }
495
496 $pro_version = wpbc_booking_resources_catalog_get_pro_version();
497 if ( '' === $pro_version ) {
498 $pro_version = __( 'Unknown', 'booking' );
499 }
500
501 $guide_html_id = 'wpbc_resources_booking_resources_warning_116';
502 ?>
503 <div id="<?php echo esc_attr( $guide_html_id ); ?>" class="notice notice-warning wpbc_booking_resources_catalog__compatibility_notice">
504 <?php
505 if ( function_exists( 'wpbc_is_dismissed' ) ) {
506 wpbc_is_dismissed(
507 $guide_html_id,
508 array(
509 'title' => '<span class="wpbc-bi-x-lg" aria-hidden="true"></span><span class="screen-reader-text">' . esc_html__( 'Dismiss', 'booking' ) . '</span>',
510 'hint' => __( 'Dismiss', 'booking' ),
511 'class' => 'wpbc_booking_resources_catalog__compatibility_notice',
512 'is_apply_in_demo' => true,
513 'css' => 'border-radius: 7px;padding: 7px 0 7px 10px;',
514 )
515 );
516 }
517 ?>
518 <p>
519 <?php
520 /* translators: %s: Detected Booking Calendar Pro version. */
521 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.' ),
522 '<a href="https://wpbookingcalendar.com/wn/whats-new-in-booking-calendar-update-11-6/" target="_blank">',
523 '</a>'
524 );
525 ?>
526 <br />
527 <?php
528 /* translators: %s: Detected Booking Calendar Pro version. */
529 printf( esc_html__( 'Detected Pro version: %s.', 'booking' ), esc_html( $pro_version ) );
530 ?>
531 <a href="<?php echo esc_url( wpbc_booking_resources_catalog_get_plugin_update_url() ); ?>"><?php esc_html_e( 'Update Booking Calendar Pro', 'booking' ); ?></a>
532 </p>
533 </div>
534 <?php
535 }
536 add_action( 'wpbc_hook_settings_page_header', 'wpbc_booking_resources_catalog_show_pro_compatibility_notice', 20, 1 );
537