opts = get_option( 'accessiq_settings', AccessIQ::default_settings() ); add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_filter( 'manage_posts_columns', array( $this, 'add_accessibility_column' ) ); add_filter( 'manage_pages_columns', array( $this, 'add_accessibility_column' ) ); add_action( 'manage_posts_custom_column', array( $this, 'render_accessibility_column' ), 10, 2 ); add_action( 'manage_pages_custom_column', array( $this, 'render_accessibility_column' ), 10, 2 ); add_action( 'admin_init', array( $this, 'maybe_redirect_to_welcome' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'wp_ajax_accessiq_save', array( $this, 'ajax_save' ) ); } public function maybe_redirect_to_welcome() { if ( ! get_transient( 'accessiq_show_welcome' ) ) { return; } // WordPress multisite activation sets 'activate-multi' with no nonce. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['activate-multi'] ) ) { // No nonce available from WP core multisite activation flow. delete_transient( 'accessiq_show_welcome' ); return; } delete_transient( 'accessiq_show_welcome' ); wp_safe_redirect( admin_url( 'admin.php?page=accessiq-about' ) ); exit; } public function add_menu() { $icon = ACCESSIQ_URL . 'admin/images/menu-icon.png'; add_menu_page( 'AccessIQ', 'AccessIQ', 'manage_options', 'accessiq', array( $this, 'render_page' ), $icon, 30 ); add_submenu_page( 'accessiq', 'Overview', '🏠 Overview', 'manage_options', 'accessiq', array( $this, 'render_page' ) ); add_submenu_page( 'accessiq', 'All Issues', '⚠ All Issues', 'manage_options', 'accessiq-issues', array( $this, 'render_issues_page' ) ); } /** * Chart data for Overview (matches dashboard.php logic). * * @return array{score:int,gradeColor:string,devices:array} */ private function get_dashboard_chart_payload() { global $wpdb; $last = get_posts( array( 'post_type' => ACCESSIQ_SCAN_CPT, 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC', ) ); $score = 0; if ( ! empty( $last ) ) { $score = (int) get_post_meta( $last[0]->ID, '_accessiq_score', true ); } if ( $score >= 70 ) { $grade_color = '#57f1db'; } elseif ( $score >= 50 ) { $grade_color = '#fbbf24'; } else { $grade_color = '#f87171'; } $sessions_tbl = $wpdb->prefix . 'accessiq_sessions'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared $rows = $wpdb->get_results( 'SELECT COALESCE(device_type, \'Desktop\') AS device, COUNT(*) AS total FROM `' . esc_sql( $sessions_tbl ) . '` GROUP BY device_type ORDER BY total DESC' ); $devices = array( 'Desktop' => 0, 'Mobile' => 0, 'Tablet' => 0 ); foreach ( (array) $rows as $row ) { $key = ucfirst( strtolower( trim( $row->device ) ) ); if ( isset( $devices[ $key ] ) ) { $devices[ $key ] = (int) $row->total; } } return array( 'score' => $score, 'gradeColor' => $grade_color, 'devices' => $devices, ); } public function enqueue( $hook ) { wp_enqueue_style( 'accessiq-admin-sidebar', ACCESSIQ_URL . 'admin/css/admin.css', array(), ACCESSIQ_VERSION ); wp_enqueue_script( 'accessiq-sidebar', ACCESSIQ_URL . 'admin/js/sidebar.js', array( 'jquery' ), ACCESSIQ_VERSION, true ); if ( strpos( $hook, 'accessiq' ) === false ) { return; } wp_enqueue_style( 'accessiq-google-fonts', 'https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;1,9..40,400&family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap', array(), ACCESSIQ_VERSION ); $admin_deps = array( 'jquery' ); if ( 'toplevel_page_accessiq' === $hook || 'accessiq_page_accessiq-scanner' === $hook ) { wp_enqueue_script( 'accessiq-chartjs', ACCESSIQ_URL . 'admin/js/chart.min.js', array(), '4.4.8', true ); $admin_deps[] = 'accessiq-chartjs'; } wp_enqueue_script( 'accessiq-admin', ACCESSIQ_URL . 'admin/js/admin.js', $admin_deps, ACCESSIQ_VERSION, true ); $d = ( 'toplevel_page_accessiq' === $hook ) ? $this->get_dashboard_chart_payload() : array( 'score' => 0, 'gradeColor' => '#10d98e', 'devices' => array( 'Desktop' => 0, 'Mobile' => 0, 'Tablet' => 0 ), ); $chart_data = array( 'score' => (int) $d['score'], 'gradeColor' => sanitize_hex_color( $d['gradeColor'] ) ?: '#10d98e', 'desk' => (int) ( $d['devices']['Desktop'] ?? 0 ), 'mob' => (int) ( $d['devices']['Mobile'] ?? 0 ), 'tab' => (int) ( $d['devices']['Tablet'] ?? 0 ), ); wp_localize_script( 'accessiq-admin', 'accessiqAdmin', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'accessiq_nonce' ), 'save_scan_nonce' => wp_create_nonce( 'accessiq_save_scan' ), 'settings_nonce' => wp_create_nonce( 'accessiq_settings_nonce' ), 'settings' => $this->opts, 'scan_url' => admin_url( 'admin.php?page=accessiq-scanner' ), 'chart_data' => $chart_data, ) ); } public function ajax_save() { check_ajax_referer( 'accessiq_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_die(); } $s = $this->opts; $s['widget_enabled'] = ( isset( $_POST['widget_enabled'] ) && sanitize_text_field( wp_unslash( $_POST['widget_enabled'] ) ) === 'true' ); $s['widget_position'] = sanitize_text_field( wp_unslash( $_POST['widget_position'] ?? 'bottom-right' ) ) ?: 'bottom-right'; $s['widget_color'] = sanitize_hex_color( wp_unslash( $_POST['widget_color'] ?? '#1d4ed8' ) ) ?: '#1d4ed8'; $allowed_widths = array( 'compact', 'normal', 'wide' ); $allowed_heights = array( 'auto', '400', '500', 'full' ); $panel_width_raw = sanitize_key( wp_unslash( $_POST['widget_panel_width'] ?? 'normal' ) ); $panel_height_raw = sanitize_key( wp_unslash( $_POST['widget_panel_height'] ?? 'auto' ) ); $s['widget_panel_width'] = in_array( $panel_width_raw, $allowed_widths, true ) ? $panel_width_raw : 'normal'; $s['widget_panel_height'] = in_array( $panel_height_raw, $allowed_heights, true ) ? $panel_height_raw : 'auto'; $s['show_on_mobile'] = ( isset( $_POST['show_on_mobile'] ) && sanitize_text_field( wp_unslash( $_POST['show_on_mobile'] ) ) === 'true' ); $s['widget_features'] = array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['widget_features'] ?? array() ) ); $s['profiles_enabled'] = isset( $_POST['profiles_enabled'] ) && sanitize_text_field( wp_unslash( $_POST['profiles_enabled'] ) ) === 'true'; $allowed_profiles = array( 'vision', 'adhd', 'seizure', 'dyslexia', 'keyboard' ); $enabled_profiles_raw = isset( $_POST['enabled_profiles'] ) ? (array) $_POST['enabled_profiles'] : array(); $s['enabled_profiles'] = array_values( array_intersect( array_map( 'sanitize_key', $enabled_profiles_raw ), $allowed_profiles ) ); $autofix_raw = isset( $_POST['autofix_features'] ) ? (array) $_POST['autofix_features'] : array(); $allowed_autofixes = array( 'skip_link', 'focus_outline', 'html_lang', 'alt_fix' ); $autofix_clean = array_intersect( $autofix_raw, $allowed_autofixes ); $s['autofix_features'] = array_map( 'sanitize_key', $autofix_clean ); $s['scanner_enabled'] = ( isset( $_POST['scanner_enabled'] ) && sanitize_text_field( wp_unslash( $_POST['scanner_enabled'] ) ) === 'true' ); $scan_level_raw = isset( $_POST['scan_level'] ) ? sanitize_text_field( wp_unslash( $_POST['scan_level'] ) ) : 'AA'; $s['scan_level'] = in_array( $scan_level_raw, array( 'A', 'AA' ), true ) ? $scan_level_raw : 'AA'; $s['notify_email'] = sanitize_email( wp_unslash( $_POST['notify_email'] ?? get_option( 'admin_email' ) ) ); update_option( 'accessiq_settings', $s ); $this->opts = $s; $accessiq_click_raw = isset( $_POST['analytics_enable_click_tracking'] ) ? sanitize_text_field( wp_unslash( $_POST['analytics_enable_click_tracking'] ) ) : 'false'; $accessiq_behavior_raw = isset( $_POST['analytics_enable_behavior_tracking'] ) ? sanitize_text_field( wp_unslash( $_POST['analytics_enable_behavior_tracking'] ) ) : 'false'; $accessiq_excl_raw = isset( $_POST['analytics_exclude_logged_in'] ) ? sanitize_text_field( wp_unslash( $_POST['analytics_exclude_logged_in'] ) ) : 'false'; $accessiq_replace_raw = isset( $_POST['analytics_replace_dashboard'] ) ? sanitize_text_field( wp_unslash( $_POST['analytics_replace_dashboard'] ) ) : 'false'; update_option( 'accessiq_analytics_enable_click_tracking', 'true' === $accessiq_click_raw ); update_option( 'accessiq_analytics_enable_behavior_tracking', 'true' === $accessiq_behavior_raw ); update_option( 'accessiq_analytics_exclude_logged_in', 'true' === $accessiq_excl_raw ); update_option( 'accessiq_analytics_replace_dashboard', 'true' === $accessiq_replace_raw ); $accessiq_retention_raw = isset( $_POST['analytics_data_retention'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['analytics_data_retention'] ) ) ) : 90; update_option( 'accessiq_analytics_data_retention', in_array( $accessiq_retention_raw, array( 30, 60, 90, 180 ), true ) ? $accessiq_retention_raw : 90 ); do_action( 'accessiq_settings_saved' ); wp_send_json_success( array( 'message' => __( 'Settings saved!', 'accessiq' ) ) ); } public function render_page() { include ACCESSIQ_DIR . 'admin/views/dashboard.php'; } public function render_issues_page() { include ACCESSIQ_DIR . 'admin/views/issues.php'; } public function render_scanner_page() { include ACCESSIQ_DIR . 'admin/views/scanner.php'; } public function render_reports_page() { include ACCESSIQ_DIR . 'admin/views/reports.php'; } public function render_settings_page() { include ACCESSIQ_DIR . 'admin/views/settings.php'; } public function get( $key, $default = null ) { return $this->opts[ $key ] ?? $default; } /** * Add Accessibility Score column to Posts / Pages list tables. * * @param array $columns Existing columns. * @return array */ public function add_accessibility_column( $columns ) { $columns['accessiq_score'] = '⚡ ' . __( 'Accessibility', 'accessiq' ); return $columns; } /** * Render Accessibility Score column cell. * * @param string $column Column key. * @param int $post_id Current post ID. */ public function render_accessibility_column( $column, $post_id ) { if ( 'accessiq_score' !== $column ) { return; } global $wpdb; $url = get_permalink( $post_id ); if ( ! $url ) { echo '' . esc_html__( 'Not scanned', 'accessiq' ) . ''; return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $scan = $wpdb->get_row( $wpdb->prepare( "SELECT p.ID, pm.meta_value AS score FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_accessiq_score' LEFT JOIN {$wpdb->postmeta} pm2 ON pm2.post_id = p.ID AND pm2.meta_key = '_accessiq_url' WHERE p.post_type = %s AND pm2.meta_value = %s ORDER BY p.post_date DESC LIMIT 1", ACCESSIQ_SCAN_CPT, $url ) ); if ( ! $scan || null === $scan->score ) { echo '' . esc_html__( 'Not scanned', 'accessiq' ) . ''; return; } $score = (int) $scan->score; $color = $score >= 80 ? '#10d98e' : ( $score >= 50 ? '#f59e0b' : '#ef4444' ); $label = $score >= 80 ? __( 'Good', 'accessiq' ) : ( $score >= 50 ? __( 'Fair', 'accessiq' ) : __( 'Poor', 'accessiq' ) ); printf( '%2$s/100 · %3$s', esc_attr( $color ), esc_html( $score ), esc_html( $label ) ); } }