insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery Schema::table( 'scans' ), array( 'status' => 'running', 'source' => $source, 'started_at' => gmdate( 'Y-m-d H:i:s' ), 'total_pages' => count( $urls ), 'with_rendered' => $with_rendered ? 1 : 0, 'config' => wp_json_encode( $config ), ) ); $id = (int) $wpdb->insert_id; foreach ( $urls as $row ) { $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery Schema::table( 'pages' ), array( 'scan_id' => $id, 'url' => $row['url'], 'url_hash' => md5( $row['url'] ), 'post_id' => (int) $row['post_id'], 'kind' => $row['kind'], 'rendered_done' => $with_rendered ? 0 : 1, ) ); } return $id; } /** * Get a scan row. * * @param int $id Scan. * @return array|null */ public static function get( $id ) { global $wpdb; $t = Schema::table( 'scans' ); $r = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $t, $id ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $r ? self::shape( $r ) : null; } /** * Update a scan. * * @param int $id Scan. * @param array $data Columns. * @return void */ public static function update( $id, array $data ) { global $wpdb; $wpdb->update( Schema::table( 'scans' ), $data, array( 'id' => (int) $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Shape a scan row for the API. * * @param array $r Row. * @return array */ public static function shape( array $r ) { $r['id'] = (int) $r['id']; $r['total_pages'] = (int) $r['total_pages']; $r['server_done'] = (int) $r['server_done']; $r['rendered_done'] = (int) $r['rendered_done']; $r['with_rendered'] = (bool) $r['with_rendered']; $r['score'] = null === $r['score'] ? null : (int) $r['score']; $r['summary'] = $r['summary'] ? json_decode( $r['summary'], true ) : null; $r['config'] = $r['config'] ? json_decode( $r['config'], true ) : array(); return $r; } /** * The latest finished scan. * * @return array|null */ public static function latest_complete() { global $wpdb; $t = Schema::table( 'scans' ); $r = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM %i WHERE status = 'complete' ORDER BY id DESC LIMIT 1", $t ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $r ? self::shape( $r ) : null; } /** * A scan in progress (running or paused), if any. * * @return array|null */ public static function active() { global $wpdb; $t = Schema::table( 'scans' ); $r = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM %i WHERE status IN ('running','paused') ORDER BY id DESC LIMIT 1", $t ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $r ? self::shape( $r ) : null; } /** * Recent scans, newest first. * * @param int $limit Limit. * @return array */ public static function recent( $limit = 20 ) { global $wpdb; $t = Schema::table( 'scans' ); $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT %d', $t, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return array_map( array( __CLASS__, 'shape' ), (array) $rows ); } /** * Score history for the trend chart (oldest first). * * @param int $limit Limit. * @return array */ public static function trend( $limit = 30 ) { global $wpdb; $t = Schema::table( 'scans' ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id, finished_at, score, source FROM %i WHERE status = 'complete' AND score IS NOT NULL ORDER BY id DESC LIMIT %d", $t, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $rows = array_reverse( (array) $rows ); foreach ( $rows as &$r ) { $r['id'] = (int) $r['id']; $r['score'] = (int) $r['score']; } return $rows; } /** * Pages awaiting a pass. * * @param int $scan_id Scan. * @param string $pass server|rendered. * @param int $limit Limit. * @return array */ public static function pending_pages( $scan_id, $pass, $limit ) { global $wpdb; $t = Schema::table( 'pages' ); if ( 'rendered' === $pass ) { $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE scan_id = %d AND rendered_done = 0 AND server_done = 1 AND error IS NULL ORDER BY id ASC LIMIT %d', $t, $scan_id, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } else { $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE scan_id = %d AND server_done = 0 ORDER BY id ASC LIMIT %d', $t, $scan_id, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } return (array) $rows; } /** * A page row. * * @param int $page_id Page. * @return array|null */ public static function page( $page_id ) { global $wpdb; $t = Schema::table( 'pages' ); $r = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $t, $page_id ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $r ? $r : null; } /** * Update a page row. * * @param int $page_id Page. * @param array $data Data. * @return void */ public static function update_page( $page_id, array $data ) { global $wpdb; $wpdb->update( Schema::table( 'pages' ), $data, array( 'id' => (int) $page_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Recount progress columns from the page rows. * * @param int $scan_id Scan. * @return array */ public static function recount( $scan_id ) { global $wpdb; $t = Schema::table( 'pages' ); $row = $wpdb->get_row( $wpdb->prepare( 'SELECT COUNT(*) total, SUM(server_done) s, SUM(rendered_done) r, SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) errors FROM %i WHERE scan_id = %d', $t, $scan_id ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery self::update( $scan_id, array( 'total_pages' => (int) $row['total'], 'server_done' => (int) $row['s'], 'rendered_done' => (int) $row['r'], ) ); return array( 'total' => (int) $row['total'], 'server_done' => (int) $row['s'], 'rendered_done' => (int) $row['r'], 'errors' => (int) $row['errors'], ); } /** * Pages of a scan with their scores. * * @param int $scan_id Scan. * @return array */ public static function pages( $scan_id ) { global $wpdb; $t = Schema::table( 'pages' ); $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT id, url, title, kind, http_code, score, issue_count, error, server_done, rendered_done FROM %i WHERE scan_id = %d ORDER BY id ASC', $t, $scan_id ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return (array) $rows; } }