get_var( $wpdb->prepare( "SELECT id FROM {$table} WHERE category = %s AND title = %s AND status = %s LIMIT 1", // phpcs:ignore $category, $explain['what_happened'], self::STATUS_OPEN ) ); if ( $existing_id ) { $wpdb->update( $table, array( 'last_seen_at' => $now, 'evidence' => wp_json_encode( $evidence ), 'detail' => wp_json_encode( $explain ), ), array( 'id' => $existing_id ), array( '%s', '%s', '%s' ), array( '%d' ) ); return (int) $existing_id; } $wpdb->insert( $table, array( 'category' => $category, 'severity' => $severity, 'title' => $explain['what_happened'], 'detail' => wp_json_encode( $explain ), 'remediation_hint' => $explain['what_should_you_do'], 'evidence' => wp_json_encode( $evidence ), 'status' => $initial_status, 'first_seen_at' => $now, 'last_seen_at' => $now, ), array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ); return (int) $wpdb->insert_id; } public static function set_status( $id, $status ) { global $wpdb; return $wpdb->update( Argus_DB::table( 'findings' ), array( 'status' => $status ), array( 'id' => $id ), array( '%s' ), array( '%d' ) ); } public static function category_counts() { global $wpdb; $table = Argus_DB::table( 'findings' ); $rows = $wpdb->get_results( "SELECT category, COUNT(*) AS cnt FROM {$table} GROUP BY category ORDER BY cnt DESC" ); // phpcs:ignore $out = array(); foreach ( $rows as $row ) { $out[ $row->category ] = (int) $row->cnt; } return $out; } public static function count_open( $category ) { global $wpdb; $table = Argus_DB::table( 'findings' ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE category = %s AND status = %s", $category, self::STATUS_OPEN ) // phpcs:ignore ); } public static function open_counts() { global $wpdb; $table = Argus_DB::table( 'findings' ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT severity, COUNT(*) AS cnt FROM {$table} WHERE status = %s GROUP BY severity", self::STATUS_OPEN ) // phpcs:ignore ); $counts = array( 'critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0, 'info' => 0, ); foreach ( $rows as $row ) { if ( isset( $counts[ $row->severity ] ) ) { $counts[ $row->severity ] = (int) $row->cnt; } } return $counts; } public static function recent( $limit = 25, $status = null ) { global $wpdb; $table = Argus_DB::table( 'findings' ); if ( $status ) { $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE status = %s ORDER BY last_seen_at DESC LIMIT %d", $status, $limit ) // phpcs:ignore ); } else { $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} ORDER BY last_seen_at DESC LIMIT %d", $limit ) // phpcs:ignore ); } foreach ( $rows as &$row ) { $row->explain = json_decode( $row->detail, true ); $row->evidence = json_decode( $row->evidence, true ); } return $rows; } const PER_PAGE = 10; public static function paginated( $page = 1, $status = null, $per_page = self::PER_PAGE ) { global $wpdb; $table = Argus_DB::table( 'findings' ); $page = max( 1, (int) $page ); $offset = ( $page - 1 ) * $per_page; if ( $status ) { $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE status = %s", $status ) ); // phpcs:ignore $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE status = %s ORDER BY last_seen_at DESC LIMIT %d OFFSET %d", $status, $per_page, $offset ) // phpcs:ignore ); } else { $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" ); // phpcs:ignore $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} ORDER BY last_seen_at DESC LIMIT %d OFFSET %d", $per_page, $offset ) // phpcs:ignore ); } foreach ( $rows as &$row ) { $row->explain = json_decode( $row->detail, true ); $row->evidence = json_decode( $row->evidence, true ); } return array( 'rows' => $rows, 'total' => $total, 'total_pages' => max( 1, (int) ceil( $total / $per_page ) ), 'page' => $page, ); } const NEEDS_ATTENTION_SEVERITIES = array( 'critical', 'high' ); public static function paginated_by_view( $page = 1, $view = 'active', $per_page = self::PER_PAGE ) { global $wpdb; $table = Argus_DB::table( 'findings' ); $page = max( 1, (int) $page ); $offset = ( $page - 1 ) * $per_page; list( $where, $params ) = self::view_where( $view ); $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$where}", $params ) ); // phpcs:ignore $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$where} ORDER BY last_seen_at DESC LIMIT %d OFFSET %d", array_merge( $params, array( $per_page, $offset ) ) ) // phpcs:ignore ); foreach ( $rows as &$row ) { $row->explain = json_decode( $row->detail, true ); $row->evidence = json_decode( $row->evidence, true ); } return array( 'rows' => $rows, 'total' => $total, 'total_pages' => max( 1, (int) ceil( $total / $per_page ) ), 'page' => $page, ); } public static function view_counts() { global $wpdb; $table = Argus_DB::table( 'findings' ); $out = array(); foreach ( array( 'active', 'needs_attention', 'resolved', 'accepted' ) as $view ) { list( $where, $params ) = self::view_where( $view ); $out[ $view ] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$where}", $params ) ); // phpcs:ignore } return $out; } protected static function view_where( $view ) { $severity_placeholders = implode( ',', array_fill( 0, count( self::NEEDS_ATTENTION_SEVERITIES ), '%s' ) ); switch ( $view ) { case 'needs_attention': return array( "status IN (%s,%s) AND severity IN ({$severity_placeholders})", array_merge( array( self::STATUS_OPEN, self::STATUS_ACKNOWLEDGED ), self::NEEDS_ATTENTION_SEVERITIES ), ); case 'resolved': return array( 'status = %s', array( self::STATUS_RESOLVED ) ); case 'accepted': return array( 'status = %s', array( self::STATUS_RISK_ACCEPTED ) ); case 'active': default: return array( "status IN (%s,%s) AND severity NOT IN ({$severity_placeholders})", array_merge( array( self::STATUS_OPEN, self::STATUS_ACKNOWLEDGED ), self::NEEDS_ATTENTION_SEVERITIES ), ); } } public static function delete( $id ) { global $wpdb; $table = Argus_DB::table( 'findings' ); $row = self::get( $id ); if ( ! $row ) { return false; } Argus_Events::record( 'finding_deleted', 'info', sprintf( 'Finding deleted: %s', $row->title ), array( 'finding_id' => $id, 'category' => $row->category, 'severity' => $row->severity ) ); return (bool) $wpdb->delete( $table, array( 'id' => $id ), array( '%d' ) ); } public static function resolve_all_open() { global $wpdb; $table = Argus_DB::table( 'findings' ); $count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE status = %s", self::STATUS_OPEN ) ); // phpcs:ignore if ( 0 === $count ) { return 0; } $wpdb->update( $table, array( 'status' => self::STATUS_RESOLVED ), array( 'status' => self::STATUS_OPEN ), array( '%s' ), array( '%s' ) ); Argus_Events::record( 'findings_bulk_resolved', 'info', sprintf( '%d open finding(s) bulk-resolved by an administrator', $count ), array( 'count' => $count ) ); return $count; } public static function get( $id ) { global $wpdb; $table = Argus_DB::table( 'findings' ); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ) ); // phpcs:ignore if ( $row ) { $row->explain = json_decode( $row->detail, true ); $row->evidence = json_decode( $row->evidence, true ); } return $row; } }