array( 'since' => '2.2.1', 'title' => __("What's new in AllAccessible 2.2", 'allaccessible'), 'items' => array( array( 'heading' => __('Build your accessibility statement page', 'allaccessible'), 'body' => __('We draft it from your AllAccessible statement, you publish it as a page, and the accessibility menu links visitors to it. If you already have one, we ask before touching anything. When the wording changes, one click refreshes the page.', 'allaccessible'), 'cta_label' => __('Create my statement page', 'allaccessible'), 'cta_anchor' => 'aacb-statement-card', ), array( 'heading' => __('A status-first dashboard', 'allaccessible'), 'body' => __('One health card answers "is my site OK?" in plain words, says when the score only covers part of your site, and carries one clear next action.', 'allaccessible'), ), array( 'heading' => __('Scan from the overview', 'allaccessible'), 'body' => __('When a scan is due it runs right from the overview with live progress, and if it cannot start the plugin tells you why.', 'allaccessible'), ), ), ), ); } /** "2.2.1" → "2.2". */ public static function line_for($version) { return preg_match('/^(\d+)\.(\d+)/', (string) $version, $m) ? $m[1] . '.' . $m[2] : (string) $version; } public static function entry_for($version) { $notes = self::notes(); $line = self::line_for($version); return isset($notes[$line]) ? $notes[$line] + array('line' => $line) : null; } /** * Called by VersionManager when the stored version changes. * * @return bool true when the dialog was queued. */ public static function on_upgrade($from, $to) { if ((string) $from === '0' || (string) $from === '') return false; // fresh install $entry = self::entry_for($to); if (!$entry) return false; if (version_compare((string) $from, $entry['since'], '>=')) return false; // already on a build that had it update_option(self::OPTION_PENDING, $entry['line'], false); return true; } /** Pure rule, testable: should the dialog open by itself on this screen? */ public static function evaluate_open(array $ctx) { if (empty($ctx['can_manage'])) return false; if (strpos((string) ($ctx['screen_id'] ?? ''), 'allaccessible') === false) return false; $pending = (string) ($ctx['pending'] ?? ''); return $pending !== '' && $pending === (string) ($ctx['line'] ?? ''); } private static function on_plugin_screen() { $screen = function_exists('get_current_screen') ? get_current_screen() : null; return $screen && strpos((string) $screen->id, 'allaccessible') !== false; } public static function should_open() { if (!current_user_can('manage_options') || !self::on_plugin_screen()) return false; $entry = self::entry_for(AACB_VERSION); if (!$entry) return false; return self::evaluate_open(array( 'can_manage' => true, 'screen_id' => get_current_screen()->id, 'pending' => get_option(self::OPTION_PENDING, ''), 'line' => $entry['line'], )); } /** Header button. Prints nothing when there are no notes for this line. */ public static function render_link() { if (!self::entry_for(AACB_VERSION)) return; ?> 'forbidden'), 403); } check_ajax_referer(self::AJAX_ACTION); $act = isset($_POST['wn_act']) ? sanitize_key($_POST['wn_act']) : ''; if ($act !== 'seen') { wp_send_json_error(array('message' => 'unknown action'), 400); } $line = isset($_POST['line']) ? preg_replace('/[^0-9.]/', '', (string) $_POST['line']) : ''; if ($line === '') $line = self::line_for(AACB_VERSION); update_option(self::OPTION_SEEN, $line, false); delete_option(self::OPTION_PENDING); wp_send_json_success(array('seen' => $line)); } }