add_action( 'rest_api_init', $this, 'register_routes' ); } /** * Registers every REST route this controller owns. */ public function register_routes(): void { register_rest_route( self::NAMESPACE_V1, '/audit/runs', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'start_run' ), 'permission_callback' => array( $this, 'can_audit' ), ) ); register_rest_route( self::NAMESPACE_V1, '/audit/runs/(?P\d+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_run_status' ), 'permission_callback' => array( $this, 'can_audit' ), ) ); register_rest_route( self::NAMESPACE_V1, '/audit/runs/(?P\d+)/issues', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_run_issues' ), 'permission_callback' => array( $this, 'can_audit' ), ) ); register_rest_route( self::NAMESPACE_V1, '/audit/runs/(?P\d+)/issues/(?P[a-z0-9_]+)/urls', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_issue_urls' ), 'permission_callback' => array( $this, 'can_audit' ), 'args' => array( 'page' => array( 'sanitize_callback' => 'absint' ), 'per_page' => array( 'sanitize_callback' => 'absint' ), ), ) ); register_rest_route( self::NAMESPACE_V1, '/audit/runs/(?P\d+)/urls', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_run_urls' ), 'permission_callback' => array( $this, 'can_audit' ), 'args' => array( 'with_issues' => array( 'sanitize_callback' => 'absint' ), ), ) ); register_rest_route( self::NAMESPACE_V1, '/audit/current-page-issues', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_current_page_issues' ), 'permission_callback' => array( $this, 'can_audit_post' ), 'args' => array( 'post_id' => array( 'required' => true, 'validate_callback' => fn( $value ) => is_numeric( $value ), ), ), ) ); } /** * POST /audit/runs — starts a new full-site audit run and immediately * returns its status row (D4: the caller starts polling GET /audit/runs/{id} * against the run_id in this response right away, not a separate lookup). * * @return WP_REST_Response|WP_Error */ public function start_run() { $scheduler = new ABDS_SEO_Audit_Scheduler(); $run_id = $scheduler->start_audit_run( 'manual', get_current_user_id() ); if ( 0 === $run_id ) { return new WP_Error( 'abds_seo_audit_unavailable', __( 'The background task runner is unavailable, so an audit can\'t be started right now.', 'abds-seo' ), array( 'status' => 503 ) ); } return new WP_REST_Response( $scheduler->get_run_status( $run_id ) ); } /** * GET /audit/runs/{id} — the run's own progress/result row. * * @param WP_REST_Request $request REST request. * @return WP_REST_Response|WP_Error */ public function get_run_status( WP_REST_Request $request ) { $status = ( new ABDS_SEO_Audit_Scheduler() )->get_run_status( (int) $request->get_param( 'id' ) ); if ( null === $status ) { return new WP_Error( 'abds_seo_audit_run_not_found', __( 'Audit run not found.', 'abds-seo' ), array( 'status' => 404 ) ); } return new WP_REST_Response( $status ); } /** * GET /audit/runs/{id}/issues — the run's issues grouped by issue_key, * the shape the IssueList screen renders (mockup Screen 3). Each row * carries its own ABDS_SEO_Issue_Catalog entry (title/why/how_to_fix/ * fix_location/docs_url, M2 1.1) inline — the accordion card (1.3) * renders straight from this response, no separate catalog fetch. * * @param WP_REST_Request $request REST request. * @return WP_REST_Response */ public function get_run_issues( WP_REST_Request $request ): WP_REST_Response { $issues = ( new ABDS_SEO_Audit_Scheduler() )->get_run_issues( (int) $request->get_param( 'id' ) ); $issues = array_map( function ( array $issue ): array { return $issue + ABDS_SEO_Issue_Catalog::get( $issue['issue_key'], $issue['message'] ); }, $issues ); return new WP_REST_Response( $issues ); } /** * GET /audit/runs/{id}/issues/{issue_key}/urls — M2 1.2: the pages one * issue_key affects, for the accordion card's expanded page list. * * @param WP_REST_Request $request REST request. * @return WP_REST_Response */ public function get_issue_urls( WP_REST_Request $request ): WP_REST_Response { $result = ( new ABDS_SEO_Audit_Scheduler() )->get_issue_urls( (int) $request->get_param( 'id' ), (string) $request->get_param( 'issue_key' ), $request->get_param( 'page' ) ? (int) $request->get_param( 'page' ) : 1, $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : ABDS_SEO_Audit_Scheduler::DEFAULT_PER_PAGE ); return new WP_REST_Response( $result ); } /** * GET /audit/runs/{id}/urls[?with_issues=1] — M2 1.2/1.4: every URL * this run checked, worst-first; with_issues=1 embeds each URL's own * issue list, the "By pages" toggle's data source. * * @param WP_REST_Request $request REST request. * @return WP_REST_Response */ public function get_run_urls( WP_REST_Request $request ): WP_REST_Response { $with_issues = (bool) $request->get_param( 'with_issues' ); $pages = ( new ABDS_SEO_Audit_Scheduler() )->get_run_urls( (int) $request->get_param( 'id' ), $with_issues ); if ( $with_issues ) { $pages = array_map( function ( array $page ): array { $page['issues'] = array_map( function ( array $issue ): array { return $issue + ABDS_SEO_Issue_Catalog::get( $issue['issue_key'], $issue['message'] ); }, $page['issues'] ); return $page; }, $pages ); } return new WP_REST_Response( $pages ); } /** * GET /audit/current-page-issues?post_id={id} — M3 2.2: the highlight * overlay's own data source. Always resolves the latest COMPLETED run * itself server-side (ignores any client-supplied run_id — there isn't * one to supply — so the overlay can never be pointed at a stale or * in-progress run by a crafted request). * * @param WP_REST_Request $request REST request. * @return WP_REST_Response */ public function get_current_page_issues( WP_REST_Request $request ): WP_REST_Response { $scheduler = new ABDS_SEO_Audit_Scheduler(); $run_id = $scheduler->get_latest_completed_run_id(); if ( ! $run_id ) { return new WP_REST_Response( array() ); } $issues = $scheduler->get_object_issues( $run_id, 'post', (int) $request->get_param( 'post_id' ) ); $issues = array_map( function ( array $issue ): array { return $issue + ABDS_SEO_Issue_Catalog::get( $issue['issue_key'], $issue['message'] ); }, $issues ); return new WP_REST_Response( $issues ); } }