PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / services / SettingsModeDeepLink.php

SettingsModeDeepLink.php in 404 Solution trunk, at includes/services/SettingsModeDeepLink.php

113 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * A link that opens one specific Advanced Mode setting, from anywhere.
9 *
10 * Every new install starts in Simple Mode, and the mode is a per-user
11 * preference held in user_meta (see SettingsModePreference), not a URL
12 * parameter. So a plain `...&subpage=abj404_options#auto_score` link lands a
13 * simple-mode admin on a page where the field they were promised does not
14 * exist. This class closes that gap from both ends:
15 *
16 * - {@see urlForAdvancedSetting()} builds a nonce-protected link that
17 * carries a request to switch the current user to Advanced Mode, plus the
18 * `#fragment` of the field to land on.
19 * - {@see applyIfRequested()} runs at admin_init, verifies the nonce and
20 * that the caller really is a plugin admin, and flips that user's own
21 * stored preference to Advanced before the page renders.
22 *
23 * Only the requesting user's own display preference is changed, only ever
24 * towards Advanced, and the mode toggle sitting at the top of the page they
25 * land on shows the new state and puts it back in one click. Nothing about
26 * the plugin's behaviour changes; Advanced Mode only reveals more fields.
27 */
28 class ABJ_404_Solution_SettingsModeDeepLink {
29
30 /** Query arg carrying the request. Also referenced literally by AdminInitHandlers. */
31 const QUERY_ARG = 'abj404_show_advanced';
32
33 /** Nonce action guarding the mode switch. */
34 const NONCE_ACTION = 'abj404_show_advanced_setting';
35
36 /**
37 * Absolute, unescaped URL to the options page that puts the current user
38 * into Advanced Mode and lands on the given field.
39 *
40 * @param string $anchorId The `id` attribute of the field to land on,
41 * e.g. 'auto_score'.
42 * @param array<string, mixed> $options The plugin options.
43 * @return string
44 */
45 public static function urlForAdvancedSetting(string $anchorId, array $options): string {
46 $args = array(self::QUERY_ARG => '1');
47 if (function_exists('wp_create_nonce')) {
48 $args['_wpnonce'] = (string)wp_create_nonce(self::NONCE_ACTION);
49 }
50 $url = ABJ_404_Solution_AdminPageUrlBuilder::subpageUrl('abj404_options', $args, $options);
51
52 if ($anchorId !== '') {
53 $url .= '#' . rawurlencode($anchorId);
54 }
55 return $url;
56 }
57
58 /**
59 * Honour an inbound advanced-setting link: switch the current user to
60 * Advanced Mode so the field the link points at is actually rendered.
61 *
62 * No-op unless the request carries the query arg, a valid nonce for this
63 * user's session, and the caller is a plugin admin. Never switches a user
64 * back to Simple Mode; that stays a deliberate act on the mode toggle.
65 *
66 * @return bool True when the preference was switched.
67 */
68 public static function applyIfRequested(): bool {
69 if (!isset($_GET[self::QUERY_ARG])) {
70 return false;
71 }
72 if (!function_exists('wp_verify_nonce')) {
73 return false;
74 }
75
76 $policy = abj_service('admin_access_policy');
77 if (!is_object($policy) || !method_exists($policy, 'isPluginAdmin') || !$policy->isPluginAdmin()) {
78 return false;
79 }
80
81 $nonce = ABJ_404_Solution_RequestInputNormalizer::readText(
82 $_GET, array('name' => '_wpnonce'));
83 if ($nonce === '' || !wp_verify_nonce($nonce, self::NONCE_ACTION)) {
84 return false;
85 }
86
87 $preference = abj_service('settings_mode_preference');
88 if (!is_object($preference) || !method_exists($preference, 'setMode')) {
89 return false;
90 }
91 $writeResult = $preference->setMode(
92 ABJ_404_Solution_SettingsModePreference::MODE_ADVANCED
93 );
94 if ($writeResult === false) {
95 self::reportModeWriteFailure();
96 return false;
97 }
98 return true;
99 }
100
101 private static function reportModeWriteFailure(): void {
102 $message = 'Advanced-settings deep link could not persist the current user mode.';
103 $logger = function_exists('abj_service_optional')
104 ? abj_service_optional('logging')
105 : null;
106 if (is_object($logger) && method_exists($logger, 'warn')) {
107 $logger->warn($message);
108 return;
109 }
110 abj404_logPhpFallback('settings-mode-deep-link', $message);
111 }
112 }
113