*/ public function due( array $buckets, int $limit, int $now ): array { global $wpdb; if ( empty( $buckets ) || $limit <= 0 ) { return array(); } $table = Schema::table( 'links' ); $in = implode( ',', array_fill( 0, count( $buckets ), '%d' ) ); $params = array_merge( array( $table ), $buckets, array( Clock::to_mysql( $now ), $limit ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Placeholders generated above. return (array) $wpdb->get_results( $wpdb->prepare( "SELECT id, url, host, is_internal, status, last_result, fail_count, first_failure_at FROM %i WHERE host_bucket IN ({$in}) AND dismissed = 0 AND next_check_at IS NOT NULL AND next_check_at <= %s ORDER BY next_check_at ASC LIMIT %d", $params ) ); } /** * Whether any non-dismissed link is due. * * @param int $now Current timestamp. */ public function has_due( int $now ): bool { global $wpdb; $table = Schema::table( 'links' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin table. return null !== $wpdb->get_var( $wpdb->prepare( 'SELECT id FROM %i WHERE dismissed = 0 AND next_check_at IS NOT NULL AND next_check_at <= %s LIMIT 1', $table, Clock::to_mysql( $now ) ) ); } /** * Number of links checked since a timestamp (used by the hourly rate limit). * * @param int $since Timestamp. */ public function checked_since( int $since ): int { global $wpdb; $table = Schema::table( 'links' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin table. return (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE last_checked_at >= %s', $table, Clock::to_mysql( $since ) ) ); } /** * One link row. * * @param int $id Link ID. */ public function find( int $id ): ?object { global $wpdb; $table = Schema::table( 'links' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin table. $row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, $id ) ); return is_object( $row ) ? $row : null; } /** * Store a check outcome. * * @param int $id Link ID. * @param array $fields Column values (already validated). */ public function save_result( int $id, array $fields ): void { global $wpdb; $formats = array(); foreach ( $fields as $key => $value ) { $formats[] = is_int( $value ) ? '%d' : '%s'; } $wpdb->update( Schema::table( 'links' ), $fields, array( 'id' => $id ), $formats, array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin table. } /** * Make links due now (re-check), selected by ID. * * @param int[] $ids Link IDs. * @return int Rows affected. */ public function mark_due( array $ids ): int { global $wpdb; if ( empty( $ids ) ) { return 0; } $in = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); $params = array_merge( array( Schema::table( 'links' ), Clock::mysql() ), array_map( 'intval', $ids ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $in only holds %d placeholders. return (int) $wpdb->query( $wpdb->prepare( "UPDATE %i SET next_check_at = %s WHERE id IN ({$in})", $params ) ); } /** * Make every non-dismissed link with one of the given statuses due now. * * @param string[] $statuses Statuses. * @return int Rows affected. */ public function mark_due_by_status( array $statuses ): int { global $wpdb; if ( empty( $statuses ) ) { return 0; } $in = implode( ',', array_fill( 0, count( $statuses ), '%s' ) ); $params = array_merge( array( Schema::table( 'links' ), Clock::mysql() ), $statuses ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $in only holds %s placeholders. return (int) $wpdb->query( $wpdb->prepare( "UPDATE %i SET next_check_at = %s WHERE dismissed = 0 AND status IN ({$in})", $params ) ); } /** * Delete links and all their instances. * * @param int[] $ids Link IDs. * @return int Links deleted. */ public function delete( array $ids ): int { global $wpdb; $ids = array_values( array_filter( array_map( 'intval', $ids ) ) ); if ( empty( $ids ) ) { return 0; } $in = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $in only holds %d placeholders. $wpdb->query( $wpdb->prepare( "DELETE FROM %i WHERE link_id IN ({$in})", array_merge( array( Schema::table( 'instances' ) ), $ids ) ) ); $deleted = (int) $wpdb->query( $wpdb->prepare( "DELETE FROM %i WHERE id IN ({$in})", array_merge( array( Schema::table( 'links' ) ), $ids ) ) ); // phpcs:enable return $deleted; } /** * Set or clear the "not broken" flag. * * @param int[] $ids Link IDs. * @param bool $dismissed Flag. */ public function set_dismissed( array $ids, bool $dismissed ): int { global $wpdb; if ( empty( $ids ) ) { return 0; } $table = Schema::table( 'links' ); $in = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); $ids = array_map( 'intval', $ids ); if ( $dismissed ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Placeholders generated above. return (int) $wpdb->query( $wpdb->prepare( "UPDATE %i SET dismissed = 1 WHERE id IN ({$in})", array_merge( array( $table ), $ids ) ) ); } // Un-dismissed links become due immediately so their state is fresh. $params = array_merge( array( $table, Clock::mysql() ), $ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Placeholders generated above. return (int) $wpdb->query( $wpdb->prepare( "UPDATE %i SET dismissed = 0, next_check_at = %s WHERE id IN ({$in})", $params ) ); } }