0 ) { return $post_id; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Bounded keyset scan, no cacheable query result. return (int) $wpdb->get_var( "SELECT MAX(ID) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post','page')" ); } /** * Eligible IDs after the last completed post. * * @param int $after Last processed ID. * @param int $upper Initial upper bound. * @param int $post_id Single-post restriction, or zero. * @return int[] */ public static function ids( int $after, int $upper, int $post_id = 0 ): array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Keyset pagination avoids a growing OFFSET or full post cache. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID > %d AND ID <= %d AND (%d = 0 OR ID = %d) AND post_status = 'publish' AND post_type IN ('post','page') ORDER BY ID LIMIT %d", $after, $upper, $post_id, $post_id, self::BATCH_SIZE ) ); return array_map( 'intval', $ids ); } /** * Detect and match one post. The caller checks the connection before providing episodes. * * @param \WP_Post $post Source. * @param callable $episodes Lazy catalogue factory. * @phpstan-param callable(): (iterable>|Migration_Matcher) $episodes * @return array */ public static function scan( \WP_Post $post, callable $episodes ): array { $report = array( 'post_id' => $post->ID, 'hash' => '', 'items' => array(), 'revision_id' => 0, ); if ( ! current_user_can( 'edit_post', $post->ID ) ) { $report['status'] = 'forbidden'; return $report; } $detected = Migration_Detectors::detect( $post ); if ( is_wp_error( $detected ) ) { $report['status'] = 'error'; $report['error'] = $detected->get_error_message(); return $report; } try { $report['hash'] = self::hash( $post ); $report['evidence'] = $detected; $report['status'] = 'scanned'; $catalogue = $detected ? $episodes() : array(); $matcher = $catalogue instanceof Migration_Matcher ? $catalogue : new Migration_Matcher( $catalogue ); foreach ( $detected as $embed ) { // Wrapper bytes live only in evidence, which is revalidated before a swap. unset( $embed['prefix'], $embed['suffix'] ); $report['items'][] = array_merge( $embed, $matcher->find( $embed ) ); if ( ! empty( $embed['range_ambiguous'] ) ) { $report['status'] = 'ambiguous'; $report['items'][ count( $report['items'] ) - 1 ]['status'] = 'ambiguous'; } } } catch ( \RuntimeException $error ) { $report['status'] = 'error'; $report['items'] = array(); $report['error'] = __( 'Matching failed safely. Check the content encoding and PHP regular expression limits.', 'showfm' ); } return $report; } /** * Detect any show.fm block, including nested ones, without rendering. * * @param string $content Content. */ public static function already( string $content ): bool { return Migration_Tokens::match( '~