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/Admin/ViewRenderer.php
<?php

declare(strict_types=1);

namespace Automattic\RewriteRulesInspector\Admin;

/**
 * Renderer class for handling view output and template rendering.
 *
 * @package Automattic\RewriteRulesInspector\Admin
 * @since 1.5.0
 */
final class ViewRenderer {

	/**
	 * Plugin directory path.
	 *
	 * @var string $plugin_dir_path
	 */
	private string $plugin_dir_path;

	/**
	 * Constructor.
	 *
	 * @since 1.5.0
	 * @param string $plugin_dir_path Plugin directory path.
	 */
	public function __construct( string $plugin_dir_path ) {
		$this->plugin_dir_path = $plugin_dir_path;
	}

	/**
	 * Render the main rules view.
	 *
	 * @since 1.5.0
	 * @param array  $rules Array of rewrite rules.
	 * @param array  $permastructs Array of permastructs.
	 * @param object $wp_list_table WordPress list table object.
	 * @param array  $url_test_results Optional URL test results.
	 */
	public function render_rules_view( array $rules, array $permastructs, $wp_list_table, ?array $url_test_results = null, bool $flush_success = false ): void {
		// Bump view stats or do something else on page load.
		do_action( 'rri_view_rewrite_rules', $rules );

		// Determine current tab.
		$has_permastructs = ! empty( $permastructs );
		$default_tab      = 'rules';
		$current_tab      = isset( $_GET['tab'] ) ? sanitize_key( (string) $_GET['tab'] ) : $default_tab;
		if ( ! $has_permastructs && 'permastructs' === $current_tab ) {
			$current_tab = $default_tab;
		}

		?>
		<div class="wrap rri-admin-page">
			<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>

			<nav class="nav-tab-wrapper" aria-label="<?php esc_attr_e( 'Rewrite Rules Inspector sections', 'rewrite-rules-inspector' ); ?>">
				<?php
					$rules_url = add_query_arg( 'tab', 'rules' );
					$rules_cls = 'nav-tab' . ( 'rules' === $current_tab ? ' nav-tab-active' : '' );
					printf(
						'<a href="%1$s" class="%2$s">%3$s</a>',
						esc_url( $rules_url ),
						esc_attr( $rules_cls ),
						esc_html__( 'Rewrite Rules', 'rewrite-rules-inspector' )
					);

					if ( $has_permastructs ) {
						$perma_url = add_query_arg( 'tab', 'permastructs' );
						$perma_cls = 'nav-tab' . ( 'permastructs' === $current_tab ? ' nav-tab-active' : '' );
						printf(
							'<a href="%1$s" class="%2$s">%3$s</a>',
							esc_url( $perma_url ),
							esc_attr( $perma_cls ),
							esc_html__( 'Permastructs', 'rewrite-rules-inspector' )
						);
					}
				?>
			</nav>

			<?php if ( 'rules' === $current_tab ) : ?>
				<div class="rri-section" id="rewrite-rules-section" tabindex="-1">
					<h2 class="screen-reader-text"><?php esc_html_e( 'Rewrite Rules', 'rewrite-rules-inspector' ); ?></h2>
					<?php if ( $flush_success ) : ?>
						<?php $this->render_flush_success_message(); ?>
					<?php endif; ?>
					<?php $this->render_rules_messages( $rules ); ?>
					<?php if ( $url_test_results ) : ?>
						<?php $this->render_url_test_results( $url_test_results ); ?>
					<?php endif; ?>
					<?php $wp_list_table->display(); ?>
				</div>
			<?php elseif ( 'permastructs' === $current_tab && $has_permastructs ) : ?>
				<div class="rri-section" id="permastructs-section" tabindex="-1">
					<h2 class="screen-reader-text"><?php esc_html_e( 'Permastructs', 'rewrite-rules-inspector' ); ?></h2>
					<?php $this->render_permastructs_table( $permastructs ); ?>
				</div>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Render rules-related messages (errors, warnings).
	 *
	 * @since 1.5.0
	 * @param array $rules Array of rewrite rules.
	 */
	private function render_rules_messages( array $rules ): void {
		$missing_count = 0;
		foreach ( $rules as $rule ) {
			if ( 'missing' === $rule['source'] ) {
				++$missing_count;
			}
		}

		$has_filter = isset( $_GET['source'] ) && 'all' !== $_GET['source'];

		if ( empty( $rules ) ) {
			// With a rule source filter active, suppress the top-level notice to keep URL Test Results position consistent.
			if ( ! $has_filter ) {
				$error_message = apply_filters( 'rri_message_no_rules', __( 'No rewrite rules yet, try flushing.', 'rewrite-rules-inspector' ) );
				echo '<div class="rri-notice rri-notice-error" role="status" aria-live="polite"><p>' . wp_kses_post( $error_message ) . '</p></div>';
			}
		} elseif ( $missing_count > 0 ) {
			/* translators: %d: Count of missing rewrite rules */
			$error_message = apply_filters( 'rri_message_missing_rules', sprintf( _n( '%d rewrite rule may be missing, try flushing.', '%d rewrite rules may be missing, try flushing.', $missing_count, 'rewrite-rules-inspector' ), $missing_count ) );
			echo '<div class="rri-notice rri-notice-error" role="status" aria-live="polite"><p>' . wp_kses_post( $error_message ) . '</p></div>';
		}
	}


	/**
	 * Render the permastructs table.
	 *
	 * @since 1.5.0
	 * @param array $permastructs Array of permastructs.
	 */
	private function render_permastructs_table( array $permastructs ): void {
		$template_path = $this->plugin_dir_path . 'views/permastructs-table.php';
		if ( file_exists( $template_path ) ) {
			include $template_path;
		}
	}

	/**
	 * Render URL test results.
	 *
	 * @since 1.5.0
	 * @param array $results URL test results.
	 */
	private function render_url_test_results( array $results ): void {
		?>
		<div class="rri-url-test-results">
			<h3><?php esc_html_e( 'URL Test Results', 'rewrite-rules-inspector' ); ?></h3>
			
			<div class="rri-test-summary">
				<p><strong><?php esc_html_e( 'URL Tested:', 'rewrite-rules-inspector' ); ?></strong> <code><?php echo esc_html( $results['url'] ); ?></code></p>
				<p><strong><?php esc_html_e( 'Path Tested:', 'rewrite-rules-inspector' ); ?></strong> <code><?php echo esc_html( $results['path'] ); ?></code></p>
				
				<?php
					$tested_count = (int) $results['total_rules_tested'];
					$has_filter = isset( $_GET['source'] ) && 'all' !== $_GET['source'];
					// If a filter yields zero rules to test, show a concise message here to keep layout consistent.
					if ( $has_filter && 0 === $tested_count ) : ?>
						<div class="rri-first-match">
							<h4><?php esc_html_e( 'No rewrite rules match the selected rule source.', 'rewrite-rules-inspector' ); ?></h4>
						</div>
					<?php endif; 
					// Only show 404 notice for URLs that would actually result in a 404
					// Skip for homepage, admin URLs, and other special cases
					$tested_path = trim( $results['path'], '/' );
					$is_special_url = empty( $tested_path ) || strpos( $tested_path, 'wp-admin' ) === 0 || strpos( $tested_path, 'wp-content' ) === 0 || strpos( $tested_path, 'wp-includes' ) === 0;
					
					if ( $results['is_404'] && $tested_count > 0 && ! $is_special_url ) : ?>
						<div class="rri-notice rri-notice-error" role="status" aria-live="polite">
							<p><strong><?php esc_html_e( 'Result: 404 Error', 'rewrite-rules-inspector' ); ?></strong></p>
							<p><?php 
								printf(
									/* translators: %d: Number of rules tested */
									esc_html__( 'This URL does not match any of the %d rewrite rules and would result in a 404 error.', 'rewrite-rules-inspector' ),
									$tested_count
								);
							?></p>
						</div>
					<?php elseif ( $results['is_404'] && $tested_count > 0 && $is_special_url ) : ?>
						<div class="rri-notice" role="status" aria-live="polite">
							<p><strong><?php esc_html_e( 'Result: No matching rules', 'rewrite-rules-inspector' ); ?></strong></p>
							<p><?php 
								printf(
									/* translators: %d: Number of rules tested */
									esc_html__( 'This URL does not match any of the %d rewrite rules, but it may be handled by WordPress core routing.', 'rewrite-rules-inspector' ),
									$tested_count
								);
							?></p>
						</div>
					<?php endif; ?>

				<?php if ( ! $results['is_404'] ) : ?>

					<?php if ( $results['first_match'] ) : ?>
						<div class="rri-first-match">
							<h4><?php esc_html_e( 'First Match (WordPress will use this):', 'rewrite-rules-inspector' ); ?></h4>
							<table class="widefat">
								<tr>
									<th><?php esc_html_e( 'Rule Pattern', 'rewrite-rules-inspector' ); ?></th>
									<td><code><?php echo esc_html( $results['first_match']['rule'] ); ?></code></td>
								</tr>
								<tr>
									<th><?php esc_html_e( 'Rewrite Target', 'rewrite-rules-inspector' ); ?></th>
									<td><code><?php echo esc_html( $results['first_match']['rewrite'] ); ?></code></td>
								</tr>
								<tr>
									<th><?php esc_html_e( 'Source', 'rewrite-rules-inspector' ); ?></th>
									<td><?php echo esc_html( $results['first_match']['source'] ); ?></td>
								</tr>
								<?php if ( ! empty( $results['first_match']['query_vars'] ) ) : ?>
									<tr>
										<th><?php esc_html_e( 'Query Variables', 'rewrite-rules-inspector' ); ?></th>
										<td>
											<?php 
											$query_vars = $results['first_match']['query_vars'];
											
											// Handle both string and array formats
											if ( is_string( $query_vars ) ) {
												// Remove index.php? prefix if present
												$query_string = $query_vars;
												if ( strpos( $query_string, 'index.php?' ) === 0 ) {
													$query_string = substr( $query_string, 10 );
												}
												echo '<code>' . esc_html( $query_string ) . '</code>';
											} else {
												// Array format - show key-value pairs, removing index.php? prefix from keys
												foreach ( $query_vars as $key => $value ) : 
													// Remove index.php? prefix from key if present
													$clean_key = $key;
													if ( strpos( $key, 'index.php?' ) === 0 ) {
														$clean_key = substr( $key, 10 );
													}
												?>
													<code><?php echo esc_html( $clean_key ); ?> = <?php echo esc_html( $value ); ?></code><br>
												<?php endforeach;
											}
											?>
										</td>
									</tr>
								<?php endif; ?>
							</table>
						</div>
					<?php endif; ?>

					<?php if ( count( $results['matches'] ) > 1 ) : ?>
						<div class="rri-all-matches">
							<h4><?php esc_html_e( 'Additional Matching Rules:', 'rewrite-rules-inspector' ); ?></h4>
							<p><?php 
								printf(
									/* translators: %d: Number of additional matches */
									esc_html__( 'See the table below for all %d matching rules in priority order.', 'rewrite-rules-inspector' ),
									count( $results['matches'] )
								);
							?></p>
						</div>
					<?php endif; ?>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Show a message when you've successfully flushed your rewrite rules.
	 *
	 * @since 1.5.0
	 */
	public function render_flush_success_message(): void {
		echo '<div class="rri-notice rri-notice-success" role="status" aria-live="polite"><p>' . esc_html__( 'Rewrite rules flushed.', 'rewrite-rules-inspector' ) . '</p></div>';
	}
}