get_dashboard_css() ); } /** * Handle the "Run now" POST action. * * Validates nonce and capability before dispatching to PRV_Probe_Runner. * Redirects back to the admin page with a status query arg. * * Side effects: Triggers a full probe run; redirects. * * @return void */ public function handle_run_now(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'pr-vision' ), 403 ); } $nonce = isset( $_POST[ self::NONCE_FIELD ] ) ? sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) ) : ''; if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) { wp_die( esc_html__( 'Security check failed.', 'pr-vision' ), 403 ); } $runner = new PRV_Probe_Runner(); $counts = $runner->run(); $redirect = add_query_arg( array( 'page' => self::MENU_SLUG, 'prv_run_done' => 1, 'prv_probed' => (int) $counts['probed'], 'prv_skipped' => (int) ( $counts['skipped_budget'] + $counts['skipped_error'] ), ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect ); exit; } /** * Render the full PR Vision admin page. * * Side effects: Outputs HTML directly. * * @return void */ public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'pr-vision' ) ); } echo '
'; echo '

' . esc_html__( 'PR Vision — AI Visibility', 'pr-vision' ) . '

'; $this->render_run_done_notice(); echo '
'; echo ''; wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); submit_button( __( 'Run now', 'pr-vision' ), 'secondary', 'prv_run_now_btn', false ); echo '
'; $registry = PRV_Collector_Registry::instance(); foreach ( $registry->get_collectors() as $key => $collector ) { $panel = $registry->get_panel( $key ); if ( null === $panel ) { continue; } $data = $collector->collect(); $panel->render( $data ); } echo '
'; } /** * Render a success or partial notice after a Run now redirect. * * @return void */ private function render_run_done_notice(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $_GET['prv_run_done'] ) ) { return; } $probed = isset( $_GET['prv_probed'] ) ? absint( wp_unslash( $_GET['prv_probed'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, Generic.Formatting.MultipleStatementAlignment.NotSameWarning, Generic.Formatting.MultipleStatementAlignment.IncorrectWarning // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash $skipped = isset( $_GET['prv_skipped'] ) ? absint( wp_unslash( $_GET['prv_skipped'] ) ) : 0; echo '

'; echo esc_html( sprintf( /* translators: 1: probed count, 2: skipped count */ __( 'Run complete: %1$d probes collected, %2$d skipped (budget or error).', 'pr-vision' ), $probed, $skipped ) ); echo '

'; } /** * Return the combined CSS string for all PR Vision admin screens. * * Delegates to PRV_Admin_Page_Css to keep this class under 300 lines. * * @return string CSS string. */ private function get_dashboard_css(): string { return PRV_Admin_Page_Css::get(); } }