PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.3
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Core/Rewrite.php +190 -306 4.9.23.5.3 View file →
@@ -1,357 +1,241 @@
1 1 <?php
2 +
2 3 namespace WPDeveloper\BetterDocs\Core;
3 4
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -
9 5 use WP_Post;
10 6 use WPDeveloper\BetterDocs\Utils\Base;
11 7 use WPDeveloper\BetterDocs\Utils\Database;
12 8
13 9 class Rewrite extends Base {
14 - protected $settings;
15 - protected $database;
10 + protected $settings;
11 + protected $database;
16 12
17 - public function __construct( Settings $settings, Database $database ) {
18 - $this->settings = $settings;
19 - $this->database = $database;
20 - }
13 + public function __construct( Settings $settings, Database $database ) {
14 + $this->settings = $settings;
15 + $this->database = $database;
16 + }
21 17
22 - public function init() {
23 - add_action( 'init', [ $this, 'rules' ] );
24 - add_action( 'betterdocs::settings::saved', [ $this, 'save_permalink_structure' ], 2, 3 );
25 - add_action( 'template_redirect', [ $this, 'handle_pagination_redirect' ] );
26 - add_filter( 'term_link', [ $this, 'taxonomy_term_link' ], 10, 3 );
27 - }
18 + public function init() {
19 + add_action( 'init', [$this, 'rules'] );
20 + add_action( 'betterdocs::settings::saved', [$this, 'save_permalink_structure'], 2, 3 );
21 + }
28 22
29 - /**
30 - * Swap a BetterDocs taxonomy base slug for its WPML-translated value in term links.
31 - *
32 - * doc_tag / doc_category are registered with the default-language slug, so
33 - * WordPress' core get_term_link() always builds `/docs-tag/{slug}/` etc. When
34 - * WPML translates the "URL <taxonomy> tax slug" string, rewrite the base segment
35 - * to the active language's slug so term links match the translated archive URL.
36 - * No-op for other taxonomies, when the slug isn't translated, or when the base
37 - * segment isn't present (e.g. MKB category permalinks that omit it).
38 - *
39 - * @param string $termlink
40 - * @param \WP_Term $term
41 - * @param string $taxonomy
42 - * @return string
43 - */
44 - public function taxonomy_term_link( $termlink, $term, $taxonomy ) {
45 - if ( ! is_string( $termlink ) ) {
46 - return $termlink;
47 - }
23 + public function remove_knowledge_base_placeholder( $permalink ) {
24 + $permalink_array = $this->permalink_structure( $permalink, 'arraywithpercent' );
25 + $permalink_array = array_filter( $permalink_array, function ( $item ) {
26 + return $item !== '%knowledge_base%';
27 + } );
48 28
49 - $slug_map = [
50 - 'doc_tag' => trim( $this->settings->get( 'tag_slug', 'docs-tag' ), '/' ),
51 - 'doc_category' => trim( $this->settings->get( 'category_slug', 'docs-category' ), '/' ),
52 - ];
29 + return trailingslashit( implode( '/', $permalink_array ) );
30 + }
53 31
54 - if ( ! isset( $slug_map[ $taxonomy ] ) || $slug_map[ $taxonomy ] === '' ) {
55 - return $termlink;
56 - }
32 + /**
33 + * This method is hooked with an action called 'betterdocs::settings::saved'
34 + *
35 + * @since 2.5.0
36 + *
37 + * @param bool $_saved
38 + * @param array $_settings
39 + * @param array $_old_settings
40 + *
41 + * @return void
42 + */
43 + public function save_permalink_structure( $_saved, $_settings, $_old_settings ) {
44 + if ( ! isset( $_settings['permalink_structure'] ) ) {
45 + return;
46 + }
57 47
58 - $default = $slug_map[ $taxonomy ];
59 - $translated = \WPDeveloper\BetterDocs\Utils\Helper::wpml_translated_tax_slug( $taxonomy, $default );
48 + $_permalink_structure = $_settings['permalink_structure'];
60 49
61 - if ( $translated === $default ) {
62 - return $termlink;
63 - }
50 + /**
51 + * TODO: Let's re-check later to optimize it.
52 + */
53 + if ( ! betterdocs()->is_pro_active() ) {
54 + $_permalink_structure = $this->remove_knowledge_base_placeholder( $_permalink_structure );
55 + }
64 56
65 - return preg_replace( '#/' . preg_quote( $default, '#' ) . '/#', '/' . $translated . '/', $termlink, 1 );
66 - }
57 + if ( $_permalink_structure !== $_old_settings['permalink_structure'] ) {
58 + $this->settings->save( 'permalink_structure', $this->permalink_structure( $_permalink_structure ) );
59 + }
67 60
68 - /**
69 - * Handle pagination redirect
70 - * Redirects pagination attempts to main archive
71 - */
72 - public function handle_pagination_redirect() {
73 - if ( ! is_post_type_archive( 'docs' ) ) {
74 - return;
75 - }
61 + $old_docs_page = isset( $_old_settings['docs_page'] ) ? $_old_settings['docs_page'] : '';
62 + $current_docs_page = isset( $_settings['docs_page'] ) ? $_settings['docs_page'] : '';
76 63
77 - $paged = get_query_var( 'paged' );
78 - if ( $paged > 1 ) {
79 - global $wp_query;
80 - $wp_query->set_404();
81 - status_header( 404 );
82 - nocache_headers();
83 - }
84 - }
64 + /**
65 + * This block of code decides whether it needs to be flushed or not.
66 + * Flush happens after register the post type.
67 + */
68 + switch ( true ) {
69 + case $_permalink_structure !== $_old_settings['permalink_structure']:
70 + case $_settings['docs_slug'] !== $_old_settings['docs_slug']:
71 + case $_settings['builtin_doc_page'] !== $_old_settings['builtin_doc_page']:
72 + case $current_docs_page !== $old_docs_page:
73 + case $_settings['tag_slug'] !== $_old_settings['tag_slug']:
74 + case $_settings['category_slug'] !== $_old_settings['category_slug']:
75 + $this->database->set_transient( 'betterdocs_flush_rewrite_rules', true );
76 + break;
77 + }
78 + }
85 79
86 - public function remove_knowledge_base_placeholder( $permalink ) {
87 - $permalink_array = $this->permalink_structure( $permalink, 'arraywithpercent' );
88 - $permalink_array = array_filter(
89 - $permalink_array,
90 - function ( $item ) {
91 - return $item !== '%knowledge_base%';
92 - }
93 - );
80 + public function permalink_structure( $structure = '', $output = 'string' ) {
81 + if ( empty( $structure ) ) {
82 + $structure = $this->settings->get( 'permalink_structure', 'docs' );
83 + }
94 84
95 - return trailingslashit( implode( '/', $permalink_array ) );
96 - }
85 + $docs_slug = $this->get_base_slug();
97 86
98 - /**
99 - * This method is hooked with an action called 'betterdocs::settings::saved'
100 - *
101 - * @since 2.5.0
102 - *
103 - * @param bool $_saved
104 - * @param array $_settings
105 - * @param array $_old_settings
106 - *
107 - * @return void
108 - */
109 - public function save_permalink_structure( $_saved, $_settings, $_old_settings ) {
110 - if ( ! isset( $_settings['permalink_structure'] ) ) {
111 - return;
112 - }
87 + $_structure_array = explode( '%', $structure );
88 + if ( $_structure_array[0] == '/' ) {
89 + $structure = $docs_slug . $structure;
90 + } else if ( $_structure_array[0] == '' ) {
91 + $structure = $docs_slug . '/' . $structure;
92 + }
113 93
114 - $_permalink_structure = $_settings['permalink_structure'];
94 + $structure = trim( $structure, '/' );
115 95
116 - /**
117 - * TODO: Let's re-check later to optimize it.
118 - */
119 - if ( ! betterdocs()->is_pro_active() ) {
120 - $_permalink_structure = $this->remove_knowledge_base_placeholder( $_permalink_structure );
121 - }
96 + if ( $output === 'string' ) {
97 + return $structure;
98 + }
122 99
123 - if ( $_permalink_structure !== $_old_settings['permalink_structure'] ) {
124 - $this->settings->save( 'permalink_structure', $this->permalink_structure( $_permalink_structure ) );
125 - }
100 + if ( $output === 'array' ) {
101 + $structure = explode( '/', $structure );
102 + $structure = array_map( function ( $item ) {
103 + return trim( $item, "/%" );
104 + }, $structure );
105 + }
126 106
127 - $old_docs_page = isset( $_old_settings['docs_page'] ) ? $_old_settings['docs_page'] : '';
128 - $current_docs_page = isset( $_settings['docs_page'] ) ? $_settings['docs_page'] : '';
107 + if ( $output === 'arraywithpercent' ) {
108 + $structure = explode( '/', $structure );
109 + $structure = array_map( function ( $item ) {
110 + return trim( $item, "/" );
111 + }, $structure );
112 + }
129 113
130 - /**
131 - * This block of code decides whether it needs to be flushed or not.
132 - * Flush happens after register the post type.
133 - */
134 - switch ( true ) {
135 - case $_permalink_structure !== $_old_settings['permalink_structure']:
136 - case $_settings['docs_slug'] !== $_old_settings['docs_slug']:
137 - case $_settings['builtin_doc_page'] !== $_old_settings['builtin_doc_page']:
138 - case $current_docs_page !== $old_docs_page:
139 - case $_settings['tag_slug'] !== $_old_settings['tag_slug']:
140 - case $_settings['category_slug'] !== $_old_settings['category_slug']:
141 - $this->database->set_transient( 'betterdocs_flush_rewrite_rules', true );
142 - break;
143 - }
144 - }
114 + return $structure;
115 + }
145 116
146 - public function permalink_structure( $structure = '', $output = 'string' ) {
147 - if ( empty( $structure ) ) {
148 - $structure = $this->settings->get( 'permalink_structure', 'docs' );
149 - }
117 + public function get_base_slug() {
118 + $docs_slug = $this->settings->get( 'docs_slug', 'docs' );
119 + $docs_page = $this->settings->get( 'docs_page', 0 );
120 + $builtin_doc_page = $this->settings->get( 'builtin_doc_page', true );
150 121
151 - $docs_slug = $this->get_base_slug();
122 + if ( ! $builtin_doc_page && $docs_page !== 0 ) {
123 + $post_info = get_post( $docs_page );
124 + $docs_slug = $post_info instanceof WP_Post ? $post_info->post_name : $docs_slug;
125 + }
152 126
153 - $_structure_array = explode( '%', $structure );
154 - if ( $_structure_array[0] == '/' ) {
155 - $structure = $docs_slug . $structure;
156 - } elseif ( $_structure_array[0] == '' ) {
157 - $structure = $docs_slug . '/' . $structure;
158 - }
127 + $docs_slug = trim( $docs_slug, '/' );
159 128
160 - $structure = trim( $structure, '/' );
129 + return $docs_slug;
130 + }
161 131
162 - if ( $output === 'string' ) {
163 - return $structure;
164 - }
132 + public function normalzie_doc_perma_structure( $structure ) {
133 + $structure = $this->permalink_structure( $structure, 'arraywithpercent' );
134 + $structure = array_reduce( $structure, function ( $carry, $item ) {
135 + if ( strpos( $item, '%' ) !== false ) {
136 + $carry[] = $item;
137 + } else {
138 + if ( $carry && strpos( end( $carry ), '%' ) === false ) {
139 + $carry[count( $carry ) - 1] .= "/$item";
140 + } else {
141 + $carry[] = $item;
142 + }
143 + }
165 144
166 - if ( $output === 'array' ) {
167 - $structure = explode( '/', $structure );
168 - $structure = array_map(
169 - function ( $item ) {
170 - return trim( $item, '/%' );
171 - },
172 - $structure
173 - );
174 - }
145 + return $carry;
146 + }, [] );
175 147
176 - if ( $output === 'arraywithpercent' ) {
177 - $structure = explode( '/', $structure );
178 - $structure = array_map(
179 - function ( $item ) {
180 - return trim( $item, '/' );
181 - },
182 - $structure
183 - );
184 - }
148 + $structure[] = '%docs%';
185 149
186 - return $structure;
187 - }
150 + $group = array_filter( $structure, function ( $item ) {
151 + return strpos( $item, '%' ) === 0;
152 + } );
188 153
189 - public function hierarchy_based_slug_switch() {
190 - $hierarchy_slug = $this->settings->get( 'enable_category_hierarchy_slugs', false );
191 - return $hierarchy_slug;
192 - }
154 + return [
155 + 'raw' => $structure,
156 + 'group' => $group
157 + ];
158 + }
193 159
194 - public function get_base_slug() {
195 - $docs_slug = $this->settings->get( 'docs_slug', 'docs' );
196 - $docs_page = $this->settings->get( 'docs_page', 0 );
197 - $builtin_doc_page = $this->settings->get( 'builtin_doc_page', true );
160 + public function make_regex( $segments ) {
161 + return array_reduce( $segments, function ( $carry, $item ) {
162 + $carry .= ( strpos( $item, '%' ) !== false ? "([^/]+)" : $item ) . '/';
163 + return $carry;
164 + }, '' ) . '?$';
165 + }
198 166
199 - if ( ! $builtin_doc_page && $docs_page !== 0 ) {
200 - $post_info = get_post( $docs_page );
201 - $docs_slug = $post_info instanceof WP_Post ? $post_info->post_name : $docs_slug;
202 - }
167 + public function make_query( $segments ) {
168 + $query = [];
169 + $matchId = 1;
203 170
204 - $docs_slug = trim( $docs_slug, '/' );
171 + foreach ( $segments as $segment ) {
172 + $query[] = trim( $segment, '%' ) . '=$matches[' . $matchId . ']';
173 + $matchId++;
174 + }
205 175
206 - return $docs_slug;
207 - }
176 + return 'index.php?' . implode( '&', $query ) . '&post_type=docs';
177 + }
208 178
209 - public function normalzie_doc_perma_structure( $structure ) {
210 - $structure = $this->permalink_structure( $structure, 'arraywithpercent' );
211 - $structure = array_reduce(
212 - $structure,
213 - function ( $carry, $item ) {
214 - if ( strpos( $item, '%' ) !== false ) {
215 - $carry[] = $item;
216 - } elseif ( $carry && strpos( end( $carry ), '%' ) === false ) {
217 - $carry[ count( $carry ) - 1 ] .= "/$item";
218 - } else {
219 - $carry[] = $item;
220 - }
179 + public function rules() {
180 + /**
181 + * docs can be dynamic / as its a root slug
182 + *
183 + * https://url/docs/kb-slug
184 + * https://url/docs/kb-slug/category-slug
185 + * https://url/docs-category/category-slug
186 + *
187 + * Docs Visit:
188 + * docs can be dynamic / as its a root slug
189 + * or docs can be change by settings.
190 + *
191 + * https://url/docs/(docs-slug)
192 + * https://url/docs/(cat-slug)/(docs-slug)
193 + * https://url/docs/(kb-slug)/(docs-slug)
194 + * https://url/docs/(kb-slug)/(cat-slug)/(docs-slug)
195 + * https://url/docs/(cat-slug)/(kb-slug)/(docs-slug)
196 + */
221 197
222 - return $carry;
223 - },
224 - []
225 - );
198 + /**
199 + * This code of blocks used to determine single docs permalink.
200 + */
201 + $_docs_perma_struct = betterdocs()->settings->get( 'permalink_structure', 'docs' );
202 + $_normalized_structure = $this->normalzie_doc_perma_structure( $_docs_perma_struct );
226 203
227 - $structure[] = '%docs%';
204 + $_feed_regex = $_normalized_structure['raw'];
205 + $_feed_regex[] = '(feed|rdf|rss|rss2|atom)';
206 + $_feed_group = $_normalized_structure['group'];
207 + $_feed_group[] = '%feed%';
228 208
229 - $group = array_filter(
230 - $structure,
231 - function ( $item ) {
232 - return strpos( $item, '%' ) === 0;
233 - }
234 - );
209 + $this->add_rewrite_rule( $this->make_regex( $_feed_regex ), $this->make_query( $_feed_group ) );
235 210
236 - return [
237 - 'raw' => $structure,
238 - 'group' => $group
239 - ];
240 - }
211 + $this->add_rewrite_rule(
212 + $this->make_regex( $_normalized_structure['raw'] ),
213 + $this->make_query( $_normalized_structure['group'] )
214 + );
241 215
242 - public function make_regex( $segments ) {
243 - return array_reduce(
244 - $segments,
245 - function ( $carry, $item ) {
246 - $carry .= ( strpos( $item, '%' ) !== false ? '([^/]+)' : $item ) . '/';
247 - return $carry;
248 - },
249 - '^'
250 - ) . '?$';
251 - }
252 -
253 - public function make_query( $segments ) {
254 - $query = [];
255 - $matchId = 1;
256 -
257 - foreach ( $segments as $segment ) {
258 - $query[] = trim( $segment, '%' ) . '=$matches[' . $matchId . ']';
259 - ++$matchId;
260 - }
261 -
262 - return 'index.php?' . implode( '&', $query ) . '&post_type=docs';
263 - }
264 -
265 - public function rules() {
266 - /**
267 - * docs can be dynamic / as its a root slug
268 - *
269 - * https://url/docs/kb-slug
270 - * https://url/docs/kb-slug/category-slug
271 - * https://url/docs-category/category-slug
272 - *
273 - * Docs Visit:
274 - * docs can be dynamic / as its a root slug
275 - * or docs can be change by settings.
276 - *
277 - * https://url/docs/(docs-slug)
278 - * https://url/docs/(cat-slug)/(docs-slug)
279 - * https://url/docs/(kb-slug)/(docs-slug)
280 - * https://url/docs/(kb-slug)/(cat-slug)/(docs-slug)
281 - * https://url/docs/(cat-slug)/(kb-slug)/(docs-slug)
282 - */
283 -
284 - $base = $this->get_base_slug();
285 - $hierarchy_slug = $this->hierarchy_based_slug_switch();
286 -
287 - // Add rule to handle pagination attempts
288 - $this->add_rewrite_rule(
289 - '^' . $base . '/page/([0-9]+)/?$',
290 - 'index.php?post_type=docs',
291 - 'top'
292 - );
293 -
294 216 $base = $this->get_base_slug();
295 - $this->add_rewrite_rule( '^' . $base . '/(feed|rdf|rss|rss2|atom)/?$', 'index.php?post_type=docs&feed=$matches[1]' );
296 - $this->add_rewrite_rule( '^' . $base . '/authors/([0-9]+)/?$', 'index.php?post_type=docs&author=$matches[1]' );
297 - $this->add_rewrite_rule( '^' . $base . '/authors/([0-9]+)/page/([0-9]+)/?$', 'index.php?post_type=docs&author=$matches[1]&page=$matches[2]' );
217 + $this->add_rewrite_rule( $base . '/(feed|rdf|rss|rss2|atom)/?$', 'index.php?post_type=docs&feed=$matches[1]' );
218 + }
298 219
299 - /**
300 - * This code of blocks used to determine single docs permalink.
301 - */
302 - $_docs_perma_struct = betterdocs()->settings->get( 'permalink_structure', 'docs' );
303 - $_normalized_structure = $this->normalzie_doc_perma_structure( $_docs_perma_struct );
220 + public function add_rewrite_rule( $regex, $query, $after = 'top' ) {
221 + add_rewrite_rule( $regex, $query, $after );
222 + }
304 223
305 - if( $hierarchy_slug ) { // register hierarchy based slug rewrite rule, for single doc permalink
306 - // When MKB or WPML is used, the actual base for single docs might differ from the generic $base
307 - $perma_base = isset( $_normalized_structure['raw'][0] ) && strpos( $_normalized_structure['raw'][0], '%' ) === false
308 - ? $_normalized_structure['raw'][0]
309 - : $base;
310 -
311 - $this->add_rewrite_rule( '^' . $perma_base . '/(.+?)/([^/]+)(?:/([0-9]+))?/?$', 'index.php?doc_category=$matches[1]&docs=$matches[2]&post_type=docs', 'top' );
312 -
313 - // Removed flush_rewrite_rules() from here. Calling flush_rewrite_rules() on the 'init' hook causes
314 - // infinite database updates and destroys custom rules when WPML or MKB dynamically filters keys.
315 - }
224 + public function docs_type_rewrite( $rewrite, $slug ) {
225 + $permalink = betterdocs()->settings->get( 'permalink_structure', 'docs/' );
226 + $permalink_structure = $this->permalink_structure( $permalink, 'array' );
227 + $permalink = apply_filters( 'betterdocs_docs_type_rewrite_permalink', $permalink, $slug, $permalink_structure );
316 228
317 - $_feed_regex = $_normalized_structure['raw'];
318 - $_feed_regex[] = '(feed|rdf|rss|rss2|atom)';
319 - $_feed_group = $_normalized_structure['group'];
320 - $_feed_group[] = '%feed%';
229 + /**
230 + * TODO: Let's re-check later for optimization.
231 + */
232 + if ( ! betterdocs()->is_pro_active() ) {
233 + $permalink = $this->remove_knowledge_base_placeholder( $permalink );
234 + }
321 235
322 - $this->add_rewrite_rule( $this->make_regex( $_feed_regex ), $this->make_query( $_feed_group ) );
323 -
324 - $this->add_rewrite_rule(
325 - $this->make_regex( $_normalized_structure['raw'] ),
326 - $this->make_query( $_normalized_structure['group'] )
327 - );
328 -
329 - $this->add_rewrite_rule( '^' . $base . '/(feed|rdf|rss|rss2|atom)/?$', 'index.php?post_type=docs&feed=$matches[1]' );
330 - }
331 -
332 - public function add_rewrite_rule( $regex, $query, $after = 'top' ) {
333 - add_rewrite_rule( $regex, $query, $after );
334 - }
335 -
336 - public function docs_type_rewrite( $rewrite, $slug ) {
337 - $permalink = betterdocs()->settings->get( 'permalink_structure', 'docs/' );
338 - $permalink_structure = $this->permalink_structure( $permalink, 'array' );
339 - $permalink = apply_filters( 'betterdocs_docs_type_rewrite_permalink', $permalink, $slug, $permalink_structure );
340 -
341 - /**
342 - * TODO: Let's re-check later for optimization.
343 - */
344 - if ( ! betterdocs()->is_pro_active() ) {
345 - $permalink = $this->remove_knowledge_base_placeholder( $permalink );
346 - }
347 -
348 - return apply_filters(
349 - 'betterdocs_docs_rewrite',
350 - [
351 - 'slug' => trim( $permalink, '/' ),
352 - 'with_front' => false
353 - ],
354 - $slug
355 - );
356 - }
236 + return apply_filters( 'betterdocs_docs_rewrite', [
237 + 'slug' => trim( $permalink, '/' ),
238 + 'with_front' => false
239 + ], $slug );
240 + }
357 241 }