| 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 |
|