PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / includes / class-metasync-bookingpress-compat.php

class-metasync-bookingpress-compat.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at includes/class-metasync-bookingpress-compat.php

191 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * BookingPress front-end session compatibility.
4 *
5 * BookingPress hooks `init` at priority 1 and calls session_start() on every
6 * non-admin request. The PHPSESSID cookie this sets on every response makes
7 * SiteGround (and most hosts) skip their page cache for the whole site, so
8 * no public page is ever served from cache — everything renders in full PHP
9 * on every visit.
10 *
11 * Nothing in BookingPress depends on that global session:
12 *
13 * - The spam-protection captcha it was built for writes its session value
14 * from an admin-ajax endpoint, where BookingPress's own is_admin() guard
15 * prevents the session from starting, so the write is discarded; and the
16 * validation filter that would read it is registered nowhere (its only
17 * add_filter line ships commented out in their plugin). Current versions
18 * exchange the captcha over transients and nonces instead.
19 * - The remaining session uses are version-gated legacy support for old
20 * releases, living inside AJAX handlers that call session_start()
21 * themselves — except Pro's cart handshake, which does ride on the
22 * front-end session, but only for visitors holding a
23 * bookingpress_cart_id cookie (set by the separate cart module). Those
24 * visitors keep the session; see the cookie guard below.
25 *
26 * This shim removes the global session start on public HTML requests so host
27 * page caching can engage again. Admin, AJAX, cron and CLI requests are
28 * untouched. REST requests are also affected: the shim runs on `init` at
29 * priority 0, before WordPress defines the REST_REQUEST constant (that happens
30 * during `parse_request`), so is_public_html_request() cannot distinguish a
31 * REST request at that point and treats it as a public HTML request. This is
32 * harmless in practice — REST responses are not host page-cached anyway, and
33 * BookingPress manages its own sessions inside its AJAX/REST handlers. Any
34 * code that genuinely needs a session still starts its own.
35 *
36 * @package MetaSync
37 * @subpackage MetaSync/includes
38 * @since 2.6.22
39 */
40
41 if (!defined('ABSPATH')) {
42 exit; # Exit if accessed directly
43 }
44
45 class Metasync_BookingPress_Compat
46 {
47 /**
48 * Option value that disables the shim ("Auto" keeps it on).
49 */
50 const DISABLED_VALUE = 'off';
51
52 /**
53 * Runs on `init` at priority 0, ahead of BookingPress's priority-1
54 * session start, and unhooks it for this request.
55 *
56 * @return void
57 */
58 public static function neutralize_bookingpress_session()
59 {
60 if (self::is_disabled_by_setting() || !self::is_public_html_request()) {
61 return;
62 }
63 # Pro's cart handshake (cart-cookie expiry flag and cart prefill) rides
64 # on the front-end session, but only for visitors the separate cart
65 # module has given a bookingpress_cart_id cookie. Leave the session in
66 # place for them so the handshake keeps working; once their cookie is
67 # expired the shim engages again on its own.
68 if (!empty($_COOKIE['bookingpress_cart_id'])) {
69 return;
70 }
71 self::remove_spam_protection_session_hook();
72 }
73
74 /**
75 * The shim is on by default; only an explicit "off" disables it.
76 *
77 * @return bool True when the shim must not touch anything.
78 */
79 private static function is_disabled_by_setting()
80 {
81 $options = get_option('metasync_options', array());
82 $mode = $options['general']['bookingpress_session_compat'] ?? 'auto';
83 return $mode === self::DISABLED_VALUE;
84 }
85
86 /**
87 * Only public page renders matter for host caching; every other request
88 * context is left exactly as WordPress finds it.
89 *
90 * Note: REST_REQUEST is not defined until `parse_request`, so at the
91 * `init` priority 0 where the shim runs it is always absent and a REST
92 * request reads as a public HTML request here. See the class docblock.
93 *
94 * @return bool True for front-end HTML requests.
95 */
96 private static function is_public_html_request()
97 {
98 if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
99 return false;
100 }
101 # WP_CLI is only ever defined (as true) by wp-cli itself, so the bare
102 # defined() check carries the meaning; referencing the constant value
103 # trips static analysis, which resolves the stubbed value to always-true.
104 if (defined('WP_CLI')) {
105 return false;
106 }
107 if (defined('REST_REQUEST') && REST_REQUEST) {
108 return false;
109 }
110 return true;
111 }
112
113 /**
114 * Remove bookingpress_spam_protection::bookingpress_start_session from
115 * the init hook.
116 *
117 * The preferred path uses the global instance BookingPress exposes; a
118 * registry scan follows as fallback to catch renamed globals, subclasses
119 * or a changed registration priority.
120 *
121 * @return bool True when the callback was found and removed.
122 */
123 private static function remove_spam_protection_session_hook()
124 {
125 $removed = false;
126
127 # BookingPress 1.6.2 registers the callback once at priority 1, but a
128 # future version could register it more than once (or at another
129 # priority). Loop until remove_action() reports no match left so every
130 # instance is cleared regardless of how many were registered.
131 if (!empty($GLOBALS['bookingpress_spam_protection']) && is_object($GLOBALS['bookingpress_spam_protection'])) {
132 do {
133 $did = remove_action(
134 'init',
135 array($GLOBALS['bookingpress_spam_protection'], 'bookingpress_start_session'),
136 1
137 );
138 if ($did) {
139 $removed = true;
140 }
141 } while ($did);
142 }
143
144 if ($removed) {
145 return true;
146 }
147
148 if (!isset($GLOBALS['wp_filter']['init'])) {
149 return false;
150 }
151
152 $init_hook = &$GLOBALS['wp_filter']['init'];
153
154 # WP_Hook objects (WP 4.7+) keep callbacks in a public property; the
155 # plain-array shape covers legacy WP and unit-test registries alike.
156 if ($init_hook instanceof WP_Hook) {
157 $callbacks = &$init_hook->callbacks;
158 } elseif (is_array($init_hook)) {
159 $callbacks = &$init_hook;
160 } else {
161 return false;
162 }
163
164 if (empty($callbacks)) {
165 return false;
166 }
167
168 # Registry-scan fallback: sweep every priority and unset every matching
169 # callback rather than stopping after the first. Catches multiple
170 # registrations (future-proofing) and renamed globals/subclasses alike.
171 foreach ($callbacks as $priority => $entries) {
172 if (!is_array($entries)) {
173 continue;
174 }
175 foreach ($entries as $callback_id => $entry) {
176 $fn = is_array($entry) && isset($entry['function']) ? $entry['function'] : $entry;
177 if (
178 is_array($fn) && count($fn) === 2 && is_object($fn[0])
179 && is_a($fn[0], 'bookingpress_spam_protection')
180 && $fn[1] === 'bookingpress_start_session'
181 ) {
182 unset($callbacks[$priority][$callback_id]);
183 $removed = true;
184 }
185 }
186 }
187
188 return $removed;
189 }
190 }
191