can_use(); } /** * Permission: may create pages. * * @return bool */ public function can_publish() { return current_user_can( 'edit_pages' ) && $this->can_use(); } /** * Register routes. * * @return void */ public function routes() { $r = function ( $path, $methods, $cb, $perm = 'can_use', $args = array() ) { register_rest_route( self::NS, $path, array( 'methods' => $methods, 'callback' => array( $this, $cb ), 'permission_callback' => array( $this, $perm ), 'args' => $args, ) ); }; $id = array( 'id' => array( 'type' => 'integer', 'sanitize_callback' => 'absint', ), ); $r( '/summary', 'GET', 'summary' ); $r( '/rules', 'GET', 'rules' ); $r( '/scans', 'GET', 'scans_list' ); $r( '/scans', 'POST', 'scan_start' ); $r( '/scans/active', 'GET', 'scan_active' ); $r( '/scans/(?P\d+)', 'GET', 'scan_get', 'can_use', $id ); $r( '/scans/(?P\d+)/step', 'POST', 'scan_step', 'can_use', $id ); $r( '/scans/(?P\d+)/status', 'POST', 'scan_status', 'can_use', $id ); $r( '/scans/(?P\d+)/pending', 'GET', 'scan_pending', 'can_use', $id ); $r( '/scans/(?P\d+)/rendered', 'POST', 'scan_rendered', 'can_use', $id ); $r( '/scans/(?P\d+)/finish', 'POST', 'scan_finish', 'can_use', $id ); $r( '/scans/(?P\d+)/pages', 'GET', 'scan_pages', 'can_use', $id ); $r( '/issues', 'GET', 'issues_list' ); $r( '/issues/(?P\d+)', 'GET', 'issue_get', 'can_use', $id ); $r( '/issues/(?P\d+)/ignore', 'POST', 'issue_ignore', 'can_use', $id ); $r( '/issues/(?P\d+)/reopen', 'POST', 'issue_reopen', 'can_use', $id ); $r( '/settings', 'GET', 'settings_get' ); $r( '/settings', 'POST', 'settings_save', 'can_manage' ); $r( '/reset-data', 'POST', 'reset_data', 'can_manage' ); } /** * Dashboard summary. * * @return array */ public function summary() { $latest = Scans::latest_complete(); $site = Score::site(); $cats = array(); $titles = Registry::category_titles(); foreach ( $site['categories'] as $cid => $v ) { $cats[] = array( 'id' => $cid, 'title' => isset( $titles[ $cid ] ) ? $titles[ $cid ] : $cid, 'score' => $v, ); } $out = array( 'has_scan' => (bool) $latest, 'score' => $site['score'], 'categories' => $cats, 'counts' => Issues::counts(), 'trend' => Scans::trend( 30 ), 'top_fixes' => Issues::top_fixes( 6 ), 'latest' => $latest, 'active' => Scans::active() ? Runner::progress( Scans::active()['id'] ) : null, 'rules' => count( Registry::all() ), 'settings' => array( 'target' => Settings::get( 'wcag_target' ), 'safe_fixes' => count( array_filter( array_diff_key( Settings::get( 'safe_fixes' ), array( 'focus_colour' => 1 ) ) ) ), ), 'pro' => Plan::has_pro(), ); return $out; } /** * Rules list. * * @return array */ public function rules() { return array( 'rules' => Registry::listing(), 'categories' => Registry::categories(), 'enabled' => Registry::enabled_ids(), ); } /** * Recent scans. * * @return array */ public function scans_list() { return array( 'scans' => Scans::recent( 20 ) ); } /** * Start a scan. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function scan_start( \WP_REST_Request $req ) { $over = array(); if ( null !== $req->get_param( 'max_pages' ) ) { $over['max_pages'] = max( 0, (int) $req->get_param( 'max_pages' ) ); } $rendered = (bool) $req->get_param( 'rendered' ); $id = Runner::start( 'admin', $rendered, $over ); if ( is_wp_error( $id ) ) { return $id; } return $this->scan_payload( $id ); } /** * Progress plus rendered-pass config. * * @param int $id Scan. * @return array */ private function scan_payload( $id ) { return array( 'progress' => Runner::progress( $id ), 'rendered' => array( 'rules' => Registry::enabled_ids( 'rendered' ), ), ); } /** * The active scan, if any. * * @return array */ public function scan_active() { $a = Scans::active(); return $a ? $this->scan_payload( $a['id'] ) : array( 'progress' => null ); } /** * One scan. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function scan_get( \WP_REST_Request $req ) { $id = (int) $req['id']; return Scans::get( $id ) ? $this->scan_payload( $id ) : new \WP_Error( 'a11ymate_not_found', __( 'Scan not found.', 'a11ymate' ), array( 'status' => 404 ) ); } /** * Server-pass step. * * @param \WP_REST_Request $req Request. * @return array */ public function scan_step( \WP_REST_Request $req ) { return array( 'progress' => Runner::step( (int) $req['id'], 3, 20 ) ); } /** * Pause, resume, cancel. * * @param \WP_REST_Request $req Request. * @return array */ public function scan_status( \WP_REST_Request $req ) { return array( 'progress' => Runner::set_status( (int) $req['id'], sanitize_key( (string) $req->get_param( 'status' ) ) ) ); } /** * Pages the browser still has to render. * * @param \WP_REST_Request $req Request. * @return array */ public function scan_pending( \WP_REST_Request $req ) { $limit = max( 1, min( 10, (int) $req->get_param( 'limit' ) ) ); $rows = Scans::pending_pages( (int) $req['id'], 'rendered', $limit ); return array( 'pages' => array_map( static function ( $p ) { return array( 'id' => (int) $p['id'], 'url' => $p['url'], ); }, $rows ), 'progress' => Runner::progress( (int) $req['id'] ), ); } /** * Rendered-pass results for one page. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function scan_rendered( \WP_REST_Request $req ) { $findings = $req->get_param( 'findings' ); $meta = $req->get_param( 'meta' ); $res = Runner::save_rendered( (int) $req['id'], (int) $req->get_param( 'page_id' ), is_array( $findings ) ? $findings : array(), is_array( $meta ) ? $meta : array(), (bool) $req->get_param( 'failed' ) ); return is_wp_error( $res ) ? $res : array( 'progress' => $res ); } /** * Finish. * * @param \WP_REST_Request $req Request. * @return array */ public function scan_finish( \WP_REST_Request $req ) { $summary = Runner::finish( (int) $req['id'] ); return array( 'summary' => $summary, 'progress' => Runner::progress( (int) $req['id'] ), ); } /** * Pages and their scores. * * @param \WP_REST_Request $req Request. * @return array */ public function scan_pages( \WP_REST_Request $req ) { return array( 'pages' => Scans::pages( (int) $req['id'] ) ); } /** * Issues list. * * @param \WP_REST_Request $req Request. * @return array */ public function issues_list( \WP_REST_Request $req ) { $p = array(); foreach ( array( 'state', 'severity', 'category', 'rule', 'mode', 'pass', 'q', 'orderby', 'order', 'page', 'per_page' ) as $k ) { if ( null !== $req->get_param( $k ) ) { $p[ $k ] = sanitize_text_field( (string) $req->get_param( $k ) ); } } return Issues::query( $p ); } /** * One issue. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function issue_get( \WP_REST_Request $req ) { $i = Issues::get( (int) $req['id'] ); return $i ? $i : new \WP_Error( 'a11ymate_not_found', __( 'Issue not found.', 'a11ymate' ), array( 'status' => 404 ) ); } /** * Ignore an issue. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function issue_ignore( \WP_REST_Request $req ) { Issues::ignore( (int) $req['id'], sanitize_key( (string) $req->get_param( 'reason' ) ), (string) $req->get_param( 'note' ) ); return $this->issue_get( $req ); } /** * Re-open an issue. * * @param \WP_REST_Request $req Request. * @return array|\WP_Error */ public function issue_reopen( \WP_REST_Request $req ) { Issues::reopen( (int) $req['id'] ); return $this->issue_get( $req ); } /** * Settings, plus what the settings screen needs to render. * * @return array */ public function settings_get() { $types = array(); foreach ( get_post_types( array( 'public' => true ), 'objects' ) as $pt ) { if ( 'attachment' !== $pt->name ) { $types[] = array( 'name' => $pt->name, 'label' => $pt->labels->name, ); } } $out = array( 'settings' => Settings::all(), 'post_types' => $types, 'categories' => Registry::categories(), 'safe_fixes' => SafeFixes::catalogue(), 'site' => array( 'name' => get_bloginfo( 'name' ), 'email' => get_option( 'admin_email' ), 'url' => home_url(), ), 'pro' => Plan::has_pro(), ); return $out; } /** * Save settings. * * @param \WP_REST_Request $req Request. * @return array */ public function settings_save( \WP_REST_Request $req ) { $body = $req->get_json_params(); Settings::update( is_array( $body ) ? $body : array() ); return $this->settings_get(); } /** * Delete all scan data (scans, pages, issues). * * @return array */ public function reset_data() { global $wpdb; foreach ( array( 'occurrences', 'issues', 'pages', 'scans' ) as $t ) { $table = \A11yMate\Schema::table( $t ); $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } return array( 'ok' => true ); } }