"object", "default" => [], "capability" => "manage_options", // Merge incoming payloads into the stored value instead of replacing // it wholesale. This lets pages save just the keys they own (e.g. the // dashboard sends only dashboard_card_order, the shell only dark_mode) // without wiping settings they never received — which matters because // most pages get a sanitized copy (see get_safe()) and must never // write that stripped copy back. "sanitize_callback" => [self::class, "merge_on_save"], "show_in_rest" => [ "schema" => [ "type" => "object", "properties" => [ // Two producers write this key with different types: the // toolbar's tri-state switcher sends 'light'/'dark'/'system' // (string), the settings page's Dark Mode toggle sends a plain // boolean. Leaving it untyped (falls through to // additionalProperties) lets both save instead of one // rejecting the other with a 400. "dashboard_card_order" => ["type" => "object"], ], "additionalProperties" => true, ], ], ]); } public static function merge_on_save($value) { $existing = get_option(self::OPTION_NAME, []); if (!is_array($existing)) { $existing = []; } if (!is_array($value)) { return $existing; } return array_merge($existing, $value); } public static function get($key = null, $default = null) { $settings = get_option(self::OPTION_NAME, []); if (!is_array($settings)) { $settings = []; } if ($key === null) { return $settings; } return isset($settings[$key]) ? $settings[$key] : $default; } /** * Settings safe to expose on every admin page (the shell serializes this * for all logged-in users). Strips backup_s3_credentials/ * backup_gdrive_credentials (contain a secret access key / OAuth client * secret + refresh token) — the exact "future field that shouldn't be * broadcast" case this method was originally kept separate from get() * to handle. */ public static function get_safe() { $settings = self::get(); unset($settings["backup_s3_credentials"], $settings["backup_gdrive_credentials"]); return $settings; } }