HEX
Server: Apache/2.4.65 (Debian)
System: Linux 88f31f35b0b8 6.1.0-38-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.147-1 (2025-08-02) x86_64
User: www-data (33)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/rewrite-rules-inspector/src/Core/RewriteRules.php
<?php

declare(strict_types=1);

namespace Automattic\RewriteRulesInspector\Core;

/**
 * Service class for handling rewrite rules generation and filtering.
 *
 * @package Automattic\RewriteRulesInspector\Core
 * @since 1.5.0
 */
final class RewriteRules {

	/**
	 * Sources of rules.
	 *
	 * @var array $sources
	 */
	private array $sources = [];

	/**
	 * Get the rewrite rules for the current view.
	 *
	 * @since 1.5.0
	 * @return array Array of rewrite rules with their sources.
	 */
	public function get_rules(): array {
		global $wp_rewrite;

		$rewrite_rules_array = [];
		$rewrite_rules       = get_option( 'rewrite_rules' );
		if ( ! $rewrite_rules ) {
			$rewrite_rules = [];
		}

		// Track down which rewrite rules are associated with which methods by breaking it down.
		$rewrite_rules_by_source = $this->generate_rules_by_source( $wp_rewrite );

		// Apply the filters used in core just in case.
		foreach ( $rewrite_rules_by_source as $source => $rules ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- core hook.
			$rewrite_rules_by_source[ $source ] = apply_filters( $source . '_rewrite_rules', $rules );
			if ( 'post_tag' === $source ) {
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core hook.
				$rewrite_rules_by_source[ $source ] = apply_filters( 'tag_rewrite_rules', $rules );
			}
		}

		// Match rules with their sources.
		foreach ( $rewrite_rules as $rule => $rewrite ) {
			$rewrite_rules_array[ $rule ]['rewrite'] = $rewrite;
			foreach ( $rewrite_rules_by_source as $source => $rules ) {
				if ( array_key_exists( $rule, $rules ) ) {
					$rewrite_rules_array[ $rule ]['source'] = $source;
				}
			}

			if ( ! isset( $rewrite_rules_array[ $rule ]['source'] ) ) {
				$rewrite_rules_array[ $rule ]['source'] = apply_filters( 'rewrite_rules_inspector_source', 'other', $rule, $rewrite );
			}
		}

		// Find any rewrite rules that should've been generated but weren't.
		$maybe_missing       = $wp_rewrite->rewrite_rules();
		$rewrite_rules_array = array_reverse( $rewrite_rules_array, true );
		foreach ( $maybe_missing as $rule => $rewrite ) {
			if ( ! array_key_exists( $rule, $rewrite_rules_array ) ) {
				$rewrite_rules_array[ $rule ] = [
					'rewrite' => $rewrite,
					'source'  => 'missing',
				];
			}
		}

		// Prepend rules so it's obvious.
		$rewrite_rules_array = array_reverse( $rewrite_rules_array, true );

		// Allow static sources of rewrite rules to override, etc.
		$rewrite_rules_array = apply_filters( 'rri_rewrite_rules', $rewrite_rules_array );

		// Set the sources used in our filtering.
		$sources = [ 'all' ];
		foreach ( $rewrite_rules_array as $rule => $data ) {
			$sources[] = $data['source'];
		}

		$this->sources = array_unique( $sources );

		// Apply filtering based on search and source.
		$rewrite_rules_array = $this->apply_filters( $rewrite_rules_array );

		return $rewrite_rules_array;
	}

	/**
	 * Generate rewrite rules by source.
	 *
	 * @since 1.5.0
	 * @param \WP_Rewrite $wp_rewrite WordPress rewrite object.
	 * @return array Array of rules organized by source.
	 */
	private function generate_rules_by_source( \WP_Rewrite $wp_rewrite ): array {
		$rewrite_rules_by_source = [];

		// Core permastructs.
		$rewrite_rules_by_source['post']     = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->permalink_structure, EP_PERMALINK );
		$rewrite_rules_by_source['date']     = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_date_permastruct(), EP_DATE );
		$rewrite_rules_by_source['root']     = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->root . '/', EP_ROOT );
		$rewrite_rules_by_source['comments'] = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->root . $wp_rewrite->comments_base, EP_COMMENTS, true, true, true, false );
		$rewrite_rules_by_source['search']   = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_search_permastruct(), EP_SEARCH );
		$rewrite_rules_by_source['author']   = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_author_permastruct(), EP_AUTHORS );
		$rewrite_rules_by_source['page']     = $wp_rewrite->page_rewrite_rules();

		// Extra permastructs including tags, categories, etc.
		foreach ( $wp_rewrite->extra_permastructs as $permastructname => $permastruct ) {
			if ( is_array( $permastruct ) ) {
				// Pre 3.4 compat.
				if ( count( $permastruct ) === 2 ) {
					$rewrite_rules_by_source[ $permastructname ] = $wp_rewrite->generate_rewrite_rules( $permastruct[0], $permastruct[1] );
				} else {
					$rewrite_rules_by_source[ $permastructname ] = $wp_rewrite->generate_rewrite_rules( $permastruct['struct'], $permastruct['ep_mask'], $permastruct['paged'], $permastruct['feed'], $permastruct['forcomments'], $permastruct['walk_dirs'], $permastruct['endpoints'] );
				}
			} else {
				$rewrite_rules_by_source[ $permastructname ] = $wp_rewrite->generate_rewrite_rules( $permastruct, EP_NONE );
			}
		}

		return $rewrite_rules_by_source;
	}

	/**
	 * Apply search and source filters to rules.
	 *
	 * @since 1.5.0
	 * @param array $rewrite_rules_array Array of rewrite rules.
	 * @return array Filtered array of rewrite rules.
	 */
	private function apply_filters( array $rewrite_rules_array ): array {
		$match_path = $this->get_search_path();

		$should_filter_by_source = ! empty( $_GET['source'] ) && 'all' !== $_GET['source'] && in_array( $_GET['source'], $this->sources, true );

		// Filter based on match or source if necessary.
		foreach ( $rewrite_rules_array as $rule => $data ) {
			// If we're searching rules based on URL and there's no match, don't return it.
			if ( $match_path !== '' && $match_path !== '0' && ! preg_match( sprintf( '#^%s#', $rule ), $match_path ) ) {
				unset( $rewrite_rules_array[ $rule ] );
			} elseif ( $should_filter_by_source && $data['source'] !== $_GET['source'] ) {
				unset( $rewrite_rules_array[ $rule ] );
			}
		}

		return $rewrite_rules_array;
	}

	/**
	 * Get the search path from URL parameter.
	 *
	 * @since 1.5.0
	 * @return string The search path.
	 */
	private function get_search_path(): string {
		$match_path = '';
		
		if ( ! empty( $_GET['s'] ) ) {
			$input = sanitize_text_field( $_GET['s'] );
			
			// If the input doesn't start with http:// or https://, treat it as a path.
			if ( ! preg_match( '/^https?:\/\//', $input ) ) {
				$match_path = $input;
			} else {
				$match_path = wp_parse_url( esc_url( $input ), PHP_URL_PATH );
			}
			
			// Ensure we have a string value.
			if ( null === $match_path ) {
				$match_path = '';
			}
			
			$wordpress_subdir_for_site = wp_parse_url( home_url(), PHP_URL_PATH );
			if ( ! empty( $wordpress_subdir_for_site ) ) {
				$match_path = str_replace( $wordpress_subdir_for_site, '', $match_path );
			}

			$match_path = ltrim( $match_path, '/' );
		}

		return $match_path;
	}

	/**
	 * Get available sources for filtering.
	 *
	 * @since 1.5.0
	 * @return array Array of available sources.
	 */
	public function get_sources(): array {
		return $this->sources;
	}
}