# betterdocs/4.2.0/includes/Core/Rewrite.php

BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ &amp; Chatbot, version 4.2.0. 313 lines.

- Page: https://pluginprobe.com/plugins/betterdocs/4.2.0/code/includes/Core/Rewrite.php
- Raw: https://pluginprobe.com/plugins/betterdocs/4.2.0/raw/includes/Core/Rewrite.php
- Modified: 2025-04-22T11:51:06+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/betterdocs/4.2.0/code/includes/Core/Rewrite.php#L10-L20`.

```php
<?php

namespace WPDeveloper\BetterDocs\Core;

use WP_Post;
use WPDeveloper\BetterDocs\Utils\Base;
use WPDeveloper\BetterDocs\Utils\Database;

class Rewrite extends Base {
	protected $settings;
	protected $database;

	public function __construct( Settings $settings, Database $database ) {
		$this->settings = $settings;
		$this->database = $database;
	}

	public function init() {
		add_action( 'init', [ $this, 'rules' ] );
		add_action( 'betterdocs::settings::saved', [ $this, 'save_permalink_structure' ], 2, 3 );
		add_action( 'template_redirect', [ $this, 'handle_pagination_redirect' ] );
	}

	/**
	 * Handle pagination redirect
	 * Redirects pagination attempts to main archive
	 */
	public function handle_pagination_redirect() {
		if ( ! is_post_type_archive( 'docs' ) ) {
			return;
		}

		$paged = get_query_var( 'paged' );
		if ( $paged > 1 ) {
			global $wp_query;
			$wp_query->set_404();
			status_header( 404 );
			nocache_headers();
		}
	}

	public function remove_knowledge_base_placeholder( $permalink ) {
		$permalink_array = $this->permalink_structure( $permalink, 'arraywithpercent' );
		$permalink_array = array_filter(
			$permalink_array,
			function ( $item ) {
				return $item !== '%knowledge_base%';
			}
		);

		return trailingslashit( implode( '/', $permalink_array ) );
	}

	/**
	 * This method is hooked with an action called 'betterdocs::settings::saved'
	 *
	 * @since 2.5.0
	 *
	 * @param bool $_saved
	 * @param array $_settings
	 * @param array $_old_settings
	 *
	 * @return void
	 */
	public function save_permalink_structure( $_saved, $_settings, $_old_settings ) {
		if ( ! isset( $_settings['permalink_structure'] ) ) {
			return;
		}

		$_permalink_structure = $_settings['permalink_structure'];

		/**
		 * TODO: Let's re-check later to optimize it.
		 */
		if ( ! betterdocs()->is_pro_active() ) {
			$_permalink_structure = $this->remove_knowledge_base_placeholder( $_permalink_structure );
		}

		if ( $_permalink_structure !== $_old_settings['permalink_structure'] ) {
			$this->settings->save( 'permalink_structure', $this->permalink_structure( $_permalink_structure ) );
		}

		$old_docs_page     = isset( $_old_settings['docs_page'] ) ? $_old_settings['docs_page'] : '';
		$current_docs_page = isset( $_settings['docs_page'] ) ? $_settings['docs_page'] : '';

		/**
		 * This block of code decides whether it needs to be flushed or not.
		 * Flush happens after register the post type.
		 */
		switch ( true ) {
			case $_permalink_structure !== $_old_settings['permalink_structure']:
			case $_settings['docs_slug'] !== $_old_settings['docs_slug']:
			case $_settings['builtin_doc_page'] !== $_old_settings['builtin_doc_page']:
			case $current_docs_page !== $old_docs_page:
			case $_settings['tag_slug'] !== $_old_settings['tag_slug']:
			case $_settings['category_slug'] !== $_old_settings['category_slug']:
				$this->database->set_transient( 'betterdocs_flush_rewrite_rules', true );
				break;
		}
	}

	public function permalink_structure( $structure = '', $output = 'string' ) {
		if ( empty( $structure ) ) {
			$structure = $this->settings->get( 'permalink_structure', 'docs' );
		}

		$docs_slug = $this->get_base_slug();

		$_structure_array = explode( '%', $structure );
		if ( $_structure_array[0] == '/' ) {
			$structure = $docs_slug . $structure;
		} elseif ( $_structure_array[0] == '' ) {
			$structure = $docs_slug . '/' . $structure;
		}

		$structure = trim( $structure, '/' );

		if ( $output === 'string' ) {
			return $structure;
		}

		if ( $output === 'array' ) {
			$structure = explode( '/', $structure );
			$structure = array_map(
				function ( $item ) {
					return trim( $item, '/%' );
				},
				$structure
			);
		}

		if ( $output === 'arraywithpercent' ) {
			$structure = explode( '/', $structure );
			$structure = array_map(
				function ( $item ) {
					return trim( $item, '/' );
				},
				$structure
			);
		}

		return $structure;
	}

	public function hierarchy_based_slug_switch() {
		$hierarchy_slug = $this->settings->get( 'enable_category_hierarchy_slugs', false );
		return $hierarchy_slug;
	}

	public function get_base_slug() {
		$docs_slug        = $this->settings->get( 'docs_slug', 'docs' );
		$docs_page        = $this->settings->get( 'docs_page', 0 );
		$builtin_doc_page = $this->settings->get( 'builtin_doc_page', true );

		if ( ! $builtin_doc_page && $docs_page !== 0 ) {
			$post_info = get_post( $docs_page );
			$docs_slug = $post_info instanceof WP_Post ? $post_info->post_name : $docs_slug;
		}

		$docs_slug = trim( $docs_slug, '/' );

		return $docs_slug;
	}

	public function normalzie_doc_perma_structure( $structure ) {
		$structure = $this->permalink_structure( $structure, 'arraywithpercent' );
		$structure = array_reduce(
			$structure,
			function ( $carry, $item ) {
				if ( strpos( $item, '%' ) !== false ) {
					$carry[] = $item;
				} elseif ( $carry && strpos( end( $carry ), '%' ) === false ) {
						$carry[ count( $carry ) - 1 ] .= "/$item";
				} else {
					$carry[] = $item;
				}

				return $carry;
			},
			[]
		);

		$structure[] = '%docs%';

		$group = array_filter(
			$structure,
			function ( $item ) {
				return strpos( $item, '%' ) === 0;
			}
		);

		return [
			'raw'   => $structure,
			'group' => $group
		];
	}

	public function make_regex( $segments ) {
		return array_reduce(
			$segments,
			function ( $carry, $item ) {
				$carry .= ( strpos( $item, '%' ) !== false ? '([^/]+)' : $item ) . '/';
				return $carry;
			},
			''
		) . '?$';
	}

	public function make_query( $segments ) {
		$query   = [];
		$matchId = 1;

		foreach ( $segments as $segment ) {
			$query[] = trim( $segment, '%' ) . '=$matches[' . $matchId . ']';
			++$matchId;
		}

		return 'index.php?' . implode( '&', $query ) . '&post_type=docs';
	}

	public function rules() {
		/**
		 * docs can be dynamic / as its a root slug
		 *
		 * https://url/docs/kb-slug
		 * https://url/docs/kb-slug/category-slug
		 * https://url/docs-category/category-slug
		 *
		 * Docs Visit:
		 * docs can be dynamic / as its a root slug
		 * or docs can be change by settings.
		 *
		 * https://url/docs/(docs-slug)
		 * https://url/docs/(cat-slug)/(docs-slug)
		 * https://url/docs/(kb-slug)/(docs-slug)
		 * https://url/docs/(kb-slug)/(cat-slug)/(docs-slug)
		 * https://url/docs/(cat-slug)/(kb-slug)/(docs-slug)
		 */

		$base 			= $this->get_base_slug();
		$hierarchy_slug = $this->hierarchy_based_slug_switch();

		// Add rule to handle pagination attempts
		$this->add_rewrite_rule(
			$base . '/page/([0-9]+)/?$',
			'index.php?post_type=docs',
			'top'
		);

        $base = $this->get_base_slug();
        $this->add_rewrite_rule( $base . '/(feed|rdf|rss|rss2|atom)/?$', 'index.php?post_type=docs&feed=$matches[1]' );
        $this->add_rewrite_rule( $base . '/authors/([0-9]+)/?$', 'index.php?post_type=docs&author=$matches[1]' );
		$this->add_rewrite_rule( $base . '/authors/([0-9]+)/page/([0-9]+)/?$', 'index.php?post_type=docs&author=$matches[1]&page=$matches[2]' );

		if( $hierarchy_slug ) { // reigster hierarchy based slug rewrite rule, for single doc permalink
			$this->add_rewrite_rule( $base . '/(.+?)/([^/]+)(?:/([0-9]+))?/?$', 'index.php?doc_category=$matches[1]&docs=$matches[2]&post_type=docs' );

			$rewrite_rules = get_option('rewrite_rules');

			if( ! isset( $rewrite_rules[$base . '/(.+?)/([^/]+)(?:/([0-9]+))?/?$'] ) ) { // if this nested rule is not set then flush the rules
				error_log(12);
				flush_rewrite_rules();
			}
		}

		/**
		 * This code of blocks used to determine single docs permalink.
		 */
		$_docs_perma_struct    = betterdocs()->settings->get( 'permalink_structure', 'docs' );
		$_normalized_structure = $this->normalzie_doc_perma_structure( $_docs_perma_struct );

		$_feed_regex   = $_normalized_structure['raw'];
		$_feed_regex[] = '(feed|rdf|rss|rss2|atom)';
		$_feed_group   = $_normalized_structure['group'];
		$_feed_group[] = '%feed%';

		$this->add_rewrite_rule( $this->make_regex( $_feed_regex ), $this->make_query( $_feed_group ) );

		$this->add_rewrite_rule(
			$this->make_regex( $_normalized_structure['raw'] ),
			$this->make_query( $_normalized_structure['group'] )
		);

		$this->add_rewrite_rule( $base . '/(feed|rdf|rss|rss2|atom)/?$', 'index.php?post_type=docs&feed=$matches[1]' );
	}

	public function add_rewrite_rule( $regex, $query, $after = 'top' ) {
		add_rewrite_rule( $regex, $query, $after );
	}

	public function docs_type_rewrite( $rewrite, $slug ) {
		$permalink           = betterdocs()->settings->get( 'permalink_structure', 'docs/' );
		$permalink_structure = $this->permalink_structure( $permalink, 'array' );
		$permalink           = apply_filters( 'betterdocs_docs_type_rewrite_permalink', $permalink, $slug, $permalink_structure );

		/**
		 * TODO: Let's re-check later for optimization.
		 */
		if ( ! betterdocs()->is_pro_active() ) {
			$permalink = $this->remove_knowledge_base_placeholder( $permalink );
		}

		return apply_filters(
			'betterdocs_docs_rewrite',
			[
				'slug'       => trim( $permalink, '/' ),
				'with_front' => false
			],
			$slug
		);
	}
}

```
