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
← All changes | includes/class-metasync-per-page-helper.php +60 -6 2.6.26 → 2.7.2 View file →
@@ -12,9 +12,10 @@
12 12 * - Resolve the active page size for a given admin list, preferring an
13 13 * explicit submission on that list's own request key, then the user's
14 14 * persisted preference (stored in user_meta), and finally the
15 15 * page-specific default. A submitted valid value is persisted back to
16 - * user_meta so it survives across page loads and sessions.
16 + * user_meta so it survives across page loads and sessions — gated on a
17 + * per-page nonce so a crafted link cannot change the stored preference.
17 18 * - Render an inline `<select>` with the allowed options, wired to refresh
18 19 * the current page with the new page size.
19 20 *
20 21 * Meta key scheme: `metasync_per_page_<page_key>` (one preference per page per
@@ -120,9 +121,9 @@
120 121 * @return array<string,string>
121 122 */
122 123 public static function request_state($page_key, $request = null)
123 124 {
124 - $request = is_array($request) ? $request : $_REQUEST;
125 + $request = is_array($request) ? $request : $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only, allow-listed list state for links
125 126 $keys = array_merge(self::state_params($page_key), [self::request_key($page_key)]);
126 127 $state = [];
127 128
128 129 foreach ($keys as $key) {
@@ -168,14 +169,57 @@
168 169 return $state;
169 170 }
170 171
171 172 /**
173 + * Nonce action protecting persistence of a page-size preference.
174 + *
175 + * The selector's navigation is a plain GET (no form submit), so the nonce
176 + * travels as a `_wpnonce` query parameter added by metasync-per-page.js
177 + * and is verified in resolve() before anything is written to user_meta.
178 + *
179 + * @param string $page_key Unique list identifier.
180 + * @return string
181 + */
182 + public static function nonce_action($page_key)
183 + {
184 + return 'metasync_per_page_' . $page_key;
185 + }
186 +
187 + /**
188 + * Page keys that have a results-per-page selector. Kept beside the
189 + * resolution logic so the localized nonce map (page_nonces()) and the
190 + * persistence gate in resolve() cannot drift apart.
191 + *
192 + * @return string[]
193 + */
194 + public static function page_keys()
195 + {
196 + return ['redirections', '404_monitor', 'media_library', 'sync_log'];
197 + }
198 +
199 + /**
200 + * Nonce for each selector page key, for wp_localize_script().
201 + *
202 + * @return array<string,string>
203 + */
204 + public static function page_nonces()
205 + {
206 + $nonces = [];
207 + foreach (self::page_keys() as $page_key) {
208 + $nonces[$page_key] = wp_create_nonce(self::nonce_action($page_key));
209 + }
210 + return $nonces;
211 + }
212 +
213 + /**
172 214 * Resolve the active per-page value for a given admin list.
173 215 *
174 216 * Resolution order:
175 217 * 1. `$_REQUEST[$request_key]` when present AND a valid allow-list
176 - * integer — also persisted to user_meta so the choice survives
177 - * future sessions.
218 + * integer — applied to this request, and persisted to user_meta
219 + * when the request also carries a valid `metasync_per_page_<page_key>`
220 + * nonce (added by the selector's own navigation) so only the
221 + * selector itself can change the stored preference.
178 222 * 2. The user's persisted preference (`metasync_per_page_<page_key>`),
179 223 * when it is a valid allow-list integer.
180 224 * 3. The supplied `$default`.
181 225 *
@@ -194,13 +238,23 @@
194 238 $request_key = $request_key === null ? self::request_key($page_key) : $request_key;
195 239 $user_id = function_exists('get_current_user_id') ? get_current_user_id() : 0;
196 240 $meta_key = 'metasync_per_page_' . $page_key;
197 241
198 - // 1) A freshly submitted value wins and is persisted.
242 + // 1) A freshly submitted value wins and is persisted — but only when
243 + // the request carries the per-page nonce. A nonce-less URL (shared,
244 + // bookmarked, forged) still applies the value for this one request
245 + // without writing the user's stored preference.
199 246 if (isset($_REQUEST[$request_key])) {
200 247 $submitted = (int) $_REQUEST[$request_key];
201 248 if (in_array($submitted, self::ALLOWED_VALUES, true)) {
202 - if ($user_id > 0 && function_exists('update_user_meta')) {
249 + $nonce_ok = false;
250 + if (isset($_REQUEST['_wpnonce']) && is_scalar($_REQUEST['_wpnonce'])) {
251 + $nonce = sanitize_key(wp_unslash($_REQUEST['_wpnonce']));
252 + if ($nonce !== '' && function_exists('wp_verify_nonce')) {
253 + $nonce_ok = (bool) wp_verify_nonce($nonce, self::nonce_action($page_key));
254 + }
255 + }
256 + if ($nonce_ok && $user_id > 0 && function_exists('update_user_meta')) {
203 257 update_user_meta($user_id, $meta_key, $submitted);
204 258 }
205 259 return $submitted;
206 260 }