PluginProbe ʕ •ᴥ•ʔ
Permalink Manager Lite / 2.2.16
Permalink Manager Lite v2.2.16
2.5.4 2.5.3.4 2.2.18 2.2.19.2 2.2.19.3 2.2.19.3.1 2.2.2 2.2.20 2.2.20.1 2.2.20.3 2.2.4 2.2.5 2.2.6 2.2.7.2 2.2.7.3 2.2.7.5 2.2.7.6 2.2.8.4 2.2.8.5 2.2.8.6 2.2.8.7 2.2.8.9 2.2.9.1 2.2.9.2 2.2.9.2.1 2.2.9.3 2.2.9.4 2.2.9.6 2.2.9.7 2.2.9.9 2.3.0 2.3.1.1 2.4.0 2.4.1 2.4.1.2 2.4.1.3 2.4.1.4 2.4.1.5 2.4.1.6 2.4.2 2.4.2.1 2.4.3 2.4.3.1 2.4.3.2 2.4.3.3 2.4.3.4 2.4.4 2.4.4.1 2.4.4.2 2.4.4.3 2.5.0 2.5.1 2.5.1.1 2.5.1.2 2.5.1.3 2.5.1.4 2.5.2 2.5.2.1 2.5.2.2 2.5.2.3 2.5.2.4 2.5.3 2.5.3.1 2.5.3.2 2.5.3.3 trunk 0.2 0.3 0.3.1 0.3.2 0.3.3 0.3.4 0.4 0.4.1 0.4.2 0.4.3 0.4.4 0.4.6 0.4.7 0.4.8 0.4.9 0.5.3 0.5.4 1.0.0 1.0.1 1.0.4 1.1.0 1.1.1 1.1.2 1.11.6.3 2.0.0 2.0.3 2.0.4 2.0.4.3 2.0.5.1 2.0.5.2 2.0.5.3 2.0.5.3.1 2.0.5.4 2.0.5.4a 2.0.5.5 2.0.5.6 2.0.5.6.1 2.0.5.7 2.0.5.9a 2.0.6.2.1 2.0.6.2a 2.0.6.3 2.1.0 2.1.1 2.1.2.4 2.2.0 2.2.1.1 2.2.1.2 2.2.11 2.2.12 2.2.13.1 2.2.14 2.2.15.1 2.2.16 2.2.17
permalink-manager / includes / core / permalink-manager-core-functions.php
permalink-manager / includes / core Last commit date
permalink-manager-actions.php 4 years ago permalink-manager-admin-functions.php 4 years ago permalink-manager-core-functions.php 4 years ago permalink-manager-debug.php 4 years ago permalink-manager-gutenberg.php 4 years ago permalink-manager-helper-functions.php 4 years ago permalink-manager-language-plugins.php 4 years ago permalink-manager-third-parties.php 4 years ago permalink-manager-uri-functions-post.php 4 years ago permalink-manager-uri-functions.php 4 years ago
permalink-manager-core-functions.php
882 lines
1 <?php
2
3 /**
4 * Core functions
5 */
6 class Permalink_Manager_Core_Functions extends Permalink_Manager_Class {
7
8 public function __construct() {
9 add_action( 'init', array($this, 'init_hooks'), 99);
10 }
11
12 function init_hooks() {
13 global $permalink_manager_options;
14
15 // Trailing slashes
16 add_filter( 'permalink_manager_filter_final_term_permalink', array($this, 'control_trailing_slashes'), 9);
17 add_filter( 'permalink_manager_filter_final_post_permalink', array($this, 'control_trailing_slashes'), 9);
18 add_filter( 'permalink_manager_filter_post_sample_uri', array($this, 'control_trailing_slashes'), 9);
19 add_filter( 'wpseo_canonical', array($this, 'control_trailing_slashes'), 9);
20 add_filter( 'wpseo_opengraph_url', array($this, 'control_trailing_slashes'), 9);
21 add_filter( 'paginate_links', array($this, 'control_trailing_slashes'), 9);
22
23 /**
24 * Detect & canonical URL/redirect functions
25 */
26 // Do not trigger in back-end
27 if(is_admin()) { return false; }
28
29 // Do not trigger if Customizer is loaded
30 if(function_exists('is_customize_preview') && is_customize_preview()) { return false; }
31
32 // Use the URIs set in this plugin
33 add_filter( 'request', array($this, 'detect_post'), 0, 1 );
34
35 // Redirect from old URIs to new URIs + adjust canonical redirect settings
36 add_action( 'template_redirect', array($this, 'new_uri_redirect_and_404'), 1);
37 add_action( 'wp', array($this, 'adjust_canonical_redirect'), 0, 1);
38
39 // Case insensitive permalinks
40 if(!empty($permalink_manager_options['general']['case_insensitive_permalinks'])) {
41 add_action( 'parse_request', array($this, 'case_insensitive_permalinks'), 0);
42 }
43 // Force 404 on non-existing pagination pages
44 if(!empty($permalink_manager_options['general']['pagination_redirect'])) {
45 add_action( 'wp', array($this, 'fix_pagination_pages'), 0);
46 }
47 }
48
49 /**
50 * The most important Permalink Manager function
51 */
52 public static function detect_post($query, $request_url = false, $return_object = false) {
53 global $wpdb, $wp, $wp_rewrite, $permalink_manager, $permalink_manager_uris, $wp_filter, $permalink_manager_options, $pm_query;
54
55 // Check if the array with custom URIs is set
56 if(!(is_array($permalink_manager_uris))) return $query;
57
58 // Used in debug mode & endpoints
59 $old_query = $query;
60
61 /**
62 * 1. Prepare URL and check if it is correct (make sure that both requested URL & home_url share the same protoocl and get rid of www prefix)
63 */
64 $request_url = (!empty($request_url)) ? parse_url($request_url, PHP_URL_PATH) : $_SERVER['REQUEST_URI'];
65 $request_url = strtok($request_url, "?");
66
67 // Make sure that either $_SERVER['SERVER_NAME'] or $_SERVER['HTTP_HOST'] are set
68 if(empty($_SERVER['HTTP_HOST']) && empty($_SERVER['SERVER_NAME'])) { return $query; }
69
70 $http_host = (!empty($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : preg_replace('/www\./i', '', $_SERVER['SERVER_NAME']);
71 $request_url = sprintf("http://%s%s", str_replace("www.", "", $http_host), $request_url);
72 $raw_home_url = trim(get_option('home'));
73 $home_url = preg_replace("/http(s)?:\/\/(www\.)?(.+?)\/?$/", "http://$3", $raw_home_url);
74
75 if(filter_var($request_url, FILTER_VALIDATE_URL)) {
76 // Check if "Deep Detect" is enabled
77 $deep_detect_enabled = apply_filters('permalink_manager_deep_uri_detect', $permalink_manager_options['general']['deep_detect']);
78
79 // Sanitize the URL
80 // $request_url = filter_var($request_url, FILTER_SANITIZE_URL);
81
82 // Keep only the URI
83 $request_url = str_replace($home_url, "", $request_url);
84
85 // Hotfix for language plugins
86 if(filter_var($request_url, FILTER_VALIDATE_URL)) {
87 $request_url = parse_url($request_url, PHP_URL_PATH);
88 }
89
90 $request_url = trim($request_url, "/");
91
92 // Get all the endpoints & pattern
93 $endpoints = Permalink_Manager_Helper_Functions::get_endpoints();
94 $pattern = "/^(.+?)(?|\/({$endpoints})(?|\/(.*)|$)|\/()([\d]+)\/?)?$/i";
95
96 // Use default REGEX to detect post
97 preg_match($pattern, $request_url, $regex_parts);
98 $uri_parts['lang'] = false;
99 $uri_parts['uri'] = (!empty($regex_parts[1])) ? $regex_parts[1] : "";
100 $uri_parts['endpoint'] = (!empty($regex_parts[2])) ? $regex_parts[2] : "";
101 $uri_parts['endpoint_value'] = (!empty($regex_parts[3])) ? $regex_parts[3] : "";
102
103 // Allow to filter the results by third-parties + store the URI parts with $pm_query global
104 $uri_parts = apply_filters('permalink_manager_detect_uri', $uri_parts, $request_url, $endpoints);
105
106 // Support comment pages
107 preg_match("/(.*)\/{$wp_rewrite->comments_pagination_base}-([\d]+)/", $uri_parts['uri'], $regex_parts);
108 if(!empty($regex_parts[2])) {
109 $uri_parts['uri'] = $regex_parts[1];
110 $uri_parts['endpoint'] = 'cpage';
111 $uri_parts['endpoint_value'] = $regex_parts[2];
112 }
113
114 // Support pagination endpoint
115 if($uri_parts['endpoint'] == $wp_rewrite->pagination_base) {
116 $uri_parts['endpoint'] = 'page';
117 }
118
119 // Stop the function if $uri_parts is empty
120 if(empty($uri_parts)) return $query;
121
122 // Store the URI parts in a separate global variable
123 $pm_query = $uri_parts;
124
125 // Get the URI parts from REGEX parts
126 $lang = $uri_parts['lang'];
127 $uri = $uri_parts['uri'];
128 $endpoint = $uri_parts['endpoint'];
129 $endpoint_value = $uri_parts['endpoint_value'];
130
131 // Trim slashes
132 $uri = trim($uri, "/");
133
134 // Ignore URLs with no URI grabbed
135 if(empty($uri)) return $query;
136
137 // Store an array with custom permalinks in a separate variable
138 $all_uris = $permalink_manager_uris;
139
140 // Check what content type should be loaded in case of duplicate ("posts" or "terms")
141 $duplicates_priority = apply_filters('permalink_manager_duplicates_priority', false);
142 if($duplicates_priority !== false) {
143 $uri_count = array_count_values($all_uris);
144
145 foreach($uri_count as $duplicated_uri => $count) {
146 if($count <= 1) { continue; }
147
148 $duplicates_ids = array_keys($all_uris, $duplicated_uri);
149
150 foreach($duplicates_ids as $id) {
151 if($duplicates_priority == 'posts' && !is_numeric($id)) {
152 unset($all_uris[$id]);
153 } else if($duplicates_priority !== 'posts' && is_numeric($id)) {
154 unset($all_uris[$id]);
155 }
156 }
157 }
158 }
159
160 // Exclude draft posts
161 $exclude_drafts = (isset($permalink_manager_options['general']['ignore_drafts'])) ? $permalink_manager_options['general']['ignore_drafts'] : false;
162 $exclude_drafts = apply_filters('permalink_manager_exclude_drafts', $exclude_drafts);
163
164 if($exclude_drafts) {
165 $post_ids = $wpdb->get_col("SELECT DISTINCT ID FROM {$wpdb->posts} AS p WHERE p.post_status = 'draft' ORDER BY ID DESC");
166 if(!empty($post_ids)) {
167 foreach($post_ids as $post_id) {
168 unset($permalink_manager_uris[$post_id]);
169 }
170 }
171 }
172
173 // Flip array for better performance
174 $all_uris = array_flip($all_uris);
175
176 // Attempt 1.
177 // Find the element ID
178 $element_id = isset($all_uris[$uri]) ? $all_uris[$uri] : false;
179
180 // Atempt 2.
181 // Decode both request URI & URIs array & make them lowercase (and save in a separate variable)
182 if(empty($element_id)) {
183 $uri = strtolower(urldecode($uri));
184
185 foreach($all_uris as $raw_uri => $uri_id) {
186 $raw_uri = urldecode($raw_uri);
187 $all_uris[$raw_uri] = $uri_id;
188 }
189
190 // Convert array keys lowercase
191 $all_uris = array_change_key_case($all_uris, CASE_LOWER);
192
193 $element_id = isset($all_uris[$uri]) ? $all_uris[$uri] : $element_id;
194 }
195
196 // Atempt 3.
197 // Check again in case someone used post/tax IDs instead of slugs
198 if($deep_detect_enabled && is_numeric($endpoint_value) && isset($all_uris["{$uri}/{$endpoint_value}"])) {
199 $element_id = $all_uris["{$uri}/{$endpoint_value}"];
200 $endpoint_value = $endpoint = "";
201 }
202
203 // Atempt 4.
204 // Check again for attachment custom URIs
205 if(empty($element_id) && isset($old_query['attachment'])) {
206 $element_id = isset($all_uris["{$uri}/{$endpoint}/{$endpoint_value}"]) ? $all_uris["{$uri}/{$endpoint}/{$endpoint_value}"] : $element_id;
207
208 if($element_id) {
209 $endpoint_value = $endpoint = "";
210 }
211 }
212
213 // Allow to filter the item_id by third-parties after initial detection
214 $element_id = apply_filters('permalink_manager_detected_element_id', $element_id, $uri_parts, $request_url);
215
216 // Clear the original query before it is filtered
217 $query = ($element_id) ? array() : $query;
218
219 /**
220 * 3A. Custom URI assigned to taxonomy
221 */
222 if(strpos($element_id, 'tax-') !== false) {
223 // Remove the "tax-" prefix
224 $term_id = intval(preg_replace("/[^0-9]/", "", $element_id));
225
226 // Filter detected post ID
227 $term_id = apply_filters('permalink_manager_detected_term_id', intval($term_id), $uri_parts, true);
228
229 // Get the variables to filter wp_query and double-check if taxonomy exists
230 $term = get_term($term_id);
231 $term_taxonomy = (!empty($term->taxonomy)) ? $term->taxonomy : false;
232
233 // Check if term is allowed
234 $disabled = ($term_taxonomy && Permalink_Manager_Helper_Functions::is_term_excluded($term)) ? true : false;
235
236 // Proceed only if the term is not removed and its taxonomy is not disabled
237 if(!$disabled && $term_taxonomy) {
238 // Get some term data
239 if($term_taxonomy == 'category') {
240 $query_parameter = 'category_name';
241 } else if($term_taxonomy == 'post_tag') {
242 $query_parameter = 'tag';
243 } else {
244 $query["taxonomy"] = $term_taxonomy;
245 $query_parameter = $term_taxonomy;
246 }
247 $term_ancestors = get_ancestors($term_id, $term_taxonomy);
248 $final_uri = $term->slug;
249
250 // Fix for hierarchical terms
251 if(!empty($term_ancestors)) {
252 foreach ($term_ancestors as $parent_id) {
253 $parent = get_term((int) $parent_id, $term_taxonomy);
254 if(!empty($parent->slug)) {
255 $final_uri = $parent->slug . '/' . $final_uri;
256 }
257 }
258 }
259
260 //$query["term"] = $final_uri;
261 $query["term"] = $term->slug;
262 //$query[$query_parameter] = $final_uri;
263 $query[$query_parameter] = $term->slug;
264 } else if($disabled) {
265 $broken_uri = true;
266 $query = $old_query;
267 } else {
268 $query = $old_query;
269 }
270 }
271 /**
272 * 3B. Custom URI assigned to post/page/cpt item
273 */
274 else if(isset($element_id) && is_numeric($element_id)) {
275 // Fix for revisions
276 $is_revision = wp_is_post_revision($element_id);
277 if($is_revision) {
278 $revision_id = $element_id;
279 $element_id = $is_revision;
280 }
281
282 // Filter detected post ID
283 $element_id = apply_filters('permalink_manager_detected_post_id', $element_id, $uri_parts);
284
285 $post_to_load = get_post($element_id);
286 $final_uri = (!empty($post_to_load->post_name)) ? $post_to_load->post_name : false;
287 $post_type = (!empty($post_to_load->post_type)) ? $post_to_load->post_type : false;
288
289 // Check if post is allowed
290 $disabled = ($post_type && Permalink_Manager_Helper_Functions::is_post_excluded($post_to_load)) ? true : false;
291
292 // Proceed only if the term is not removed and its taxonomy is not disabled
293 if(!$disabled && $post_type) {
294 $post_type_object = get_post_type_object($post_type);
295
296 // Fix for hierarchical CPT & pages
297 if(!(empty($post_to_load->ancestors)) && !empty($post_type_object->hierarchical)) {
298 foreach ($post_to_load->ancestors as $parent) {
299 $parent = get_post($parent);
300 if($parent && $parent->post_name) {
301 $final_uri = $parent->post_name . '/' . $final_uri;
302 }
303 }
304 }
305
306 // Alter the final query array
307 if($post_to_load->post_status == 'private' && (!is_user_logged_in() || current_user_can('read_private_posts', $element_id) !== true)) {
308 $element_id = null;
309 $query = $old_query;
310 } else if($post_to_load->post_status == 'draft' || empty($final_uri)) {
311 if(is_user_logged_in()) {
312 if($post_type == 'page') {
313 $query['page_id'] = $element_id;
314 } else {
315 $query['p'] = $element_id;
316 }
317
318 $query['preview'] = true;
319 $query['post_type'] = $post_type;
320 } else if($post_to_load->post_status == 'draft') {
321 $query['pagename'] = '-';
322 $query['error'] = '404';
323
324 $element_id = 0;
325 } else {
326 $query = $old_query;
327 }
328 } else if($post_type == 'page') {
329 $query['pagename'] = $final_uri;
330 // $query['post_type'] = $post_type;
331 } else if($post_type == 'post') {
332 $query['name'] = $final_uri;
333 } else if($post_type == 'attachment') {
334 $query['attachment'] = $final_uri;
335 } else {
336 // Get the query var
337 $query_var = (!empty($post_type_object->query_var)) ? $post_type_object->query_var : $post_type;
338
339 $query['name'] = $final_uri;
340 $query['post_type'] = $post_type;
341 $query[$query_var] = $final_uri;
342 }
343 } else if($disabled) {
344 $broken_uri = true;
345 $query = $old_query;
346 } else {
347 $query = $old_query;
348 }
349 }
350
351 /**
352 * 4. Auto-remove removed term custom URI & redirects (works if enabled in plugin settings)
353 */
354 if(!empty($broken_uri) && (!empty($permalink_manager_options['general']['auto_fix_duplicates'])) && $permalink_manager_options['general']['auto_fix_duplicates'] == 1) {
355 // Do not trigger if WP Rocket cache plugin is turned on
356 if(!defined('WP_ROCKET_VERSION') && is_array($permalink_manager_uris)) {
357 $broken_element_id = (!empty($revision_id)) ? $revision_id : $element_id;
358 $remove_broken_uri = (!empty($broken_element_id)) ? Permalink_Manager_Actions::force_clear_single_element_uris_and_redirects($broken_element_id) : '';
359
360 // Reload page if success
361 if($remove_broken_uri && !headers_sent()) {
362 header("Refresh:0");
363 exit();
364 }
365 }
366 }
367
368 /**
369 * 5A. Endpoints
370 */
371 if(!empty($element_id) && empty($disabled) && (!empty($endpoint) || !empty($endpoint_value))) {
372 if(is_array($endpoint)) {
373 foreach($endpoint as $endpoint_name => $endpoint_value) {
374 $query[$endpoint_name] = $endpoint_value;
375 }
376 } else if($endpoint == 'feed') {
377 $query[$endpoint] = 'feed';
378 } else if($endpoint == 'embed') {
379 $query[$endpoint] = true;
380 } else if($endpoint == 'page') {
381 $endpoint = 'paged';
382 if(is_numeric($endpoint_value)) {
383 $query[$endpoint] = $endpoint_value;
384 } else {
385 $query = $old_query;
386 }
387 } else if($endpoint == 'trackback') {
388 $endpoint = 'tb';
389 $query[$endpoint] = 1;
390 } else if(empty($endpoint) && is_numeric($endpoint_value)) {
391 $query['page'] = $endpoint_value;
392 } else {
393 $query[$endpoint] = $endpoint_value;
394 }
395
396 // Fix for attachments
397 if(!empty($query['attachment'])) {
398 $query = array('attachment' => $query['attachment'], 'do_not_redirect' => 1);
399 }
400 }
401
402 /**
403 * 5B. Endpoints - check if any endpoint is set with $_GET parameter
404 */
405 if(!empty($element_id) && $deep_detect_enabled && !empty($_GET)) {
406 $get_endpoints = array_intersect($wp->public_query_vars, array_keys($_GET));
407
408 if(!empty($get_endpoints)) {
409 // Append query vars from $_GET parameters
410 foreach($get_endpoints as $endpoint) {
411 // Numeric endpoints
412 $endpoint_value = (in_array($endpoint, array('page', 'paged', 'attachment_id'))) ? filter_var($_GET[$endpoint], FILTER_SANITIZE_NUMBER_INT) : $_GET[$endpoint];
413
414 // Ignore page endpoint if its value is 1
415 if(in_array($endpoint, array('page', 'paged')) && $endpoint_value == 1) { continue; }
416
417 // Replace whitespaces with '+' and sanitize the value
418 $endpoint_value = preg_replace('/\s+/', '+', $endpoint_value);
419 $query[$endpoint] = sanitize_text_field($endpoint_value);
420 }
421 }
422 }
423
424 /**
425 * 6. Set global with detected item id
426 */
427 if(!empty($element_id) && empty($disabled)) {
428 $pm_query['id'] = $element_id;
429
430 // Make the redirects more clever - see new_uri_redirect_and_404() method
431 $query['do_not_redirect'] = 1;
432 }
433 }
434
435 /**
436 * 7. Debug data
437 */
438 if(!empty($term_taxonomy)) {
439 $content_type = "Taxonomy: {$term_taxonomy}";
440 } else if(!empty($post_type)) {
441 $content_type = "Post type: {$post_type}";
442 } else {
443 $content_type = '';
444 }
445 $uri_parts = (!empty($uri_parts)) ? $uri_parts : '';
446 $query = apply_filters('permalink_manager_filter_query', $query, $old_query, $uri_parts, $pm_query, $content_type);
447
448 if($return_object && !empty($term)) {
449 return $term;
450 } else if($return_object && !empty($post_to_load)) {
451 return $post_to_load;
452 } else {
453 return $query;
454 }
455 }
456
457 /**
458 * Trailing slash & remove BOM and double slashes
459 */
460 static function control_trailing_slashes($permalink) {
461 global $permalink_manager_options;
462
463 // Ignore empty permalinks
464 if(empty($permalink)) { return $permalink; }
465
466 // Keep the original permalink in a separate variable
467 $original_permalink = $permalink;
468
469 $trailing_slash_setting = (!empty($permalink_manager_options['general']['trailing_slashes'])) ? $permalink_manager_options['general']['trailing_slashes'] : "";
470
471 // Remove trailing slashes from URLs that end with file extension (eg. .html)
472 if(preg_match('/(http(?:s)?:\/\/(?:[^\/]+)\/.*\.([a-zA-Z]{3,4}))\/?$/', $permalink)) {
473 $permalink = preg_replace('/^(?!http(?:s):\/\/[^\/]+\/$)(.+?)([\/]*)(\[\?\#][^\/]+|$)/', '$1$3', $permalink); // Instead of untrailingslashit()
474 } else {
475 // Add trailing slashes
476 if(in_array($trailing_slash_setting, array(1, 10))) {
477 $permalink = preg_replace('/(.+?)([\/]*)(\[\?\#][^\/]+|$)/', '$1/$3', $permalink); // Instead of trailingslashit()
478 }
479 // Remove trailing slashes
480 else if(in_array($trailing_slash_setting, array(2, 20))) {
481 $permalink = preg_replace('/(.+?)([\/]*)(\[\?\#][^\/]+|$)/', '$1$3', $permalink); // Instead of untrailingslashit()
482 }
483 // Default settings
484 else {
485 $permalink = user_trailingslashit($permalink);
486 }
487 }
488
489 // Remove double slashes
490 $permalink = preg_replace('/([^:])(\/{2,})/', '$1/', $permalink);
491
492 // Remove trailing slashes from URLs that end with query string or anchors
493 $permalink = preg_replace('/([\?\#]{1}[^\/]+)([\/]+)$/', '$1', $permalink);
494
495 return apply_filters('permalink_manager_control_trailing_slashes', $permalink, $original_permalink);
496 }
497
498 /**
499 * Display 404 if requested page does not exist in pagination
500 */
501 function fix_pagination_pages() {
502 global $wp_query, $pm_query;
503
504 // 1. Get the queried object
505 $post = get_queried_object();
506
507 // 2. Check if post object is defined
508 if((!empty($post->post_type) && !empty($post->post_content)) || (!empty($wp_query->max_num_pages))) {
509 // 2A. Check if pagination is detected
510 $current_page = (!empty($wp_query->query_vars['page'])) ? $wp_query->query_vars['page'] : 1;
511 $current_page = (empty($wp_query->query_vars['page']) && !empty($wp_query->query_vars['paged'])) ? $wp_query->query_vars['paged'] : $current_page;
512
513 // 2B. Count post pages
514 $post_content = (!empty($post->post_content)) ? $post->post_content : '';
515 $num_pages = (is_home() || is_archive() || is_search()) ? $wp_query->max_num_pages : substr_count(strtolower($post_content), '<!--nextpage-->') + 1;
516
517 $is_404 = ($current_page > 1 && ($current_page > $num_pages)) ? true : false;
518 }
519 // 3. Force 404 if no posts are loaded
520 else if(!empty($wp_query->query['paged']) && $wp_query->post_count == 0) {
521 $is_404 = true;
522 }
523 // 4. Force 404 if endpoint value is not set
524 else if(!empty($pm_query['endpoint']) && $pm_query['endpoint'] == 'page' && empty($pm_query['endpoint_value'])) {
525 $is_404 = true;
526 }
527
528 // 5. Block non-existent pages (Force 404 error)
529 if(!empty($is_404)) {
530 $wp_query->query = $wp_query->queried_object = $wp_query->queried_object_id = null;
531 $wp_query->set_404();
532 status_header(404);
533 nocache_headers();
534 $pm_query = '';
535 }
536 }
537
538 /**
539 * Redirects
540 */
541 function new_uri_redirect_and_404() {
542 global $wp_query, $wp, $wp_rewrite, $wpdb, $permalink_manager_uris, $permalink_manager_redirects, $permalink_manager_external_redirects, $permalink_manager_options, $pm_query;
543
544 // Get the redirection mode & trailing slashes settings
545 $redirect_mode = (!empty($permalink_manager_options['general']['redirect'])) ? $permalink_manager_options['general']['redirect'] : false;
546 $trailing_slashes_mode = (!empty($permalink_manager_options['general']['trailing_slashes'])) ? $permalink_manager_options['general']['trailing_slashes'] : false;
547 $trailing_slashes_redirect = (!empty($permalink_manager_options['general']['trailing_slashes_redirect'])) ? $permalink_manager_options['general']['trailing_slashes_redirect'] : false;
548 $extra_redirects = (!empty($permalink_manager_options['general']['extra_redirects'])) ? $permalink_manager_options['general']['extra_redirects'] : false;
549 $canonical_redirect = (!empty($permalink_manager_options['general']['canonical_redirect'])) ? $permalink_manager_options['general']['canonical_redirect'] : false;
550 $old_slug_redirect = (!empty($permalink_manager_options['general']['old_slug_redirect'])) ? $permalink_manager_options['general']['old_slug_redirect'] : false;
551 $endpoint_redirect = (!empty($permalink_manager_options['general']['endpoint_redirect'])) ? $permalink_manager_options['general']['endpoint_redirect'] : false;
552 $pagination_redirect = (!empty($permalink_manager_options['general']['pagination_redirect'])) ? $permalink_manager_options['general']['pagination_redirect'] : false;
553 $copy_query_redirect = (!empty($permalink_manager_options['general']['copy_query_redirect'])) ? $permalink_manager_options['general']['copy_query_redirect'] : false;
554 $redirect_type = '-';
555
556 // Get home URL
557 $home_url = rtrim(get_option('home'), "/");
558 $home_dir = parse_url($home_url, PHP_URL_PATH);
559
560 // Set up $correct_permalink variable
561 $correct_permalink = '';
562
563 // Get query string & URI
564 if(empty($_SERVER['REQUEST_URI'])) { return; }
565
566 $query_string = ($copy_query_redirect && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
567 $old_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
568
569 // Fix for WP installed in directories (remove the directory name from the URI)
570 if(!empty($home_dir)) {
571 $home_dir_regex = preg_quote(trim($home_dir), "/");
572 $old_uri = preg_replace("/{$home_dir_regex}/", "", $old_uri, 1);
573 }
574
575 // Do not use custom redirects on author pages, search & front page
576 if(!is_author() && !is_front_page() && !is_home() && !is_feed() && !is_search() && empty($_GET['s'])) {
577 // Sometimes $wp_query indicates the wrong object if requested directly
578 $queried_object = get_queried_object();
579
580 // Unset 404 if custom URI is detected
581 if(isset($pm_query['id']) && (empty($queried_object->post_status) || $queried_object->post_status !== 'private')) {
582 $wp_query->is_404 = false;
583 }
584
585 /**
586 * 1A. External redirect
587 */
588 if(!empty($pm_query['id']) && !empty($permalink_manager_external_redirects[$pm_query['id']])) {
589 $external_url = $permalink_manager_external_redirects[$pm_query['id']];
590
591 if(filter_var($external_url, FILTER_VALIDATE_URL)) {
592 // Allow redirect
593 $wp_query->query_vars['do_not_redirect'] = 0;
594
595 wp_redirect($external_url, 301, PERMALINK_MANAGER_PLUGIN_NAME);
596 exit();
597 }
598 }
599
600 /**
601 * 1B. Custom redirects
602 */
603 if(empty($wp_query->query_vars['do_not_redirect']) && $extra_redirects && !empty($permalink_manager_redirects) && is_array($permalink_manager_redirects) && !empty($wp->request) && !empty($pm_query['uri'])) {
604 $uri = $pm_query['uri'];
605 $endpoint_value = $pm_query['endpoint_value'];
606
607 // Make sure that URIs with non-ASCII characters are also detected + Check the URLs that end with number
608 $decoded_url = urldecode($uri);
609 $endpoint_url = "{$uri}/{$endpoint_value}";
610
611 // Convert to lowercase to make case insensitive
612 $force_lowercase = apply_filters('permalink_manager_force_lowercase_uris', true);
613
614 if($force_lowercase) {
615 $uri = strtolower($uri);
616 $decoded_url = strtolower($decoded_url);
617 $endpoint_url = strtolower($endpoint_url);
618 }
619
620 // Check if the URI is not assigned to any post/term's redirects
621 foreach($permalink_manager_redirects as $element => $redirects) {
622 if(!is_array($redirects)) { continue; }
623
624 if(in_array($uri, $redirects) || in_array($decoded_url, $redirects) || (is_numeric($endpoint_value) && in_array($endpoint_url, $redirects))) {
625 // Post is detected
626 if(is_numeric($element)) {
627 $correct_permalink = get_permalink($element);
628 }
629 // Term is detected
630 else {
631 $term_id = intval(preg_replace("/[^0-9]/", "", $element));
632 $correct_permalink = get_term_link($term_id);
633 }
634
635 // The custom redirect is found so there is no need to query the rest of array
636 continue;
637 }
638 }
639
640 $redirect_type = (!empty($correct_permalink)) ? 'custom_redirect' : $redirect_type;
641 }
642
643 // Ignore WP-Content links
644 if(!empty($_SERVER['REQUEST_URI']) && (strpos($_SERVER['REQUEST_URI'], '/wp-content') !== false)) { return false; }
645
646 /**
647 * 1C. Pagination redirect
648 */
649 if($pagination_redirect && ((isset($wp_query->query_vars['paged']) && $wp_query->query_vars['paged'] == 1) || (isset($wp_query->query_vars['page']) && $wp_query->query_vars['page'] == 1 && !empty($pm_query['endpoint_value'])))) {
650 $pm_query['endpoint'] = $pm_query['endpoint_value'] = '';
651 $wp_query->query_vars['do_not_redirect'] = 0;
652 }
653
654 /**
655 * 1D. Enhance native redirect
656 */
657 if($canonical_redirect && empty($wp_query->query_vars['do_not_redirect']) && !empty($queried_object) && empty($correct_permalink)) {
658 // Affect only posts with custom URI and old URIs
659 if(!empty($queried_object->ID) && isset($permalink_manager_uris[$queried_object->ID]) && empty($wp_query->query['preview'])) {
660 // Ignore posts with specific statuses
661 if(!(empty($queried_object->post_status)) && in_array($queried_object->post_status, array('draft', 'pending', 'auto-draft', 'future'))) {
662 return;
663 }
664
665 // Check if the post is excluded
666 if(Permalink_Manager_Helper_Functions::is_post_excluded($queried_object)) { return; }
667
668 // Get the real URL
669 $correct_permalink = get_permalink($queried_object->ID);
670 }
671 // Affect only terms with custom URI and old URIs
672 else if(!empty($queried_object->term_id) && isset($permalink_manager_uris["tax-{$queried_object->term_id}"]) && defined('PERMALINK_MANAGER_PRO')) {
673 // Check if the term is excluded
674 if(Permalink_Manager_Helper_Functions::is_term_excluded($queried_object)) { return; }
675
676 // Get the real URL
677 $correct_permalink = get_term_link($queried_object->term_id, $queried_object->taxonomy);
678 }
679
680 $redirect_type = (!empty($correct_permalink)) ? 'native_redirect' : $redirect_type;
681 }
682
683 /**
684 * 1E. Old slug redirect
685 */
686 if($old_slug_redirect && !empty($pm_query['uri']) && empty($wp_query->query_vars['do_not_redirect']) && is_404() && empty($correct_permalink)) {
687 $slug = basename($pm_query['uri']);
688
689 $post_id = $wpdb->get_var($wpdb->prepare("SELECT post_id from {$wpdb->postmeta} WHERE meta_key = '_wp_old_slug' AND meta_value = %s", $slug));
690 if(!empty($post_id)) {
691 $correct_permalink = get_permalink($post_id);
692 $redirect_type = 'old_slug_redirect';
693 }
694 }
695
696 /**
697 * 2. Check if URL contains duplicated slashes
698 */
699 if(!empty($old_uri) && ($old_uri !== '/') && preg_match('/\/{2,}/', $old_uri)) {
700 $new_uri = ltrim(preg_replace('/([^:])([\/]+)/', '$1/', $old_uri), '/');
701 $correct_permalink = sprintf("%s/%s", $home_url, $new_uri);
702
703 $redirect_type = ($redirect_type == '-') ? 'duplicated_slash_redirect' : $redirect_type;
704 }
705
706 /**
707 * 3. Prevent redirect loop
708 */
709 if(!empty($correct_permalink) && is_string($correct_permalink) && !empty($wp->request) && !empty($redirect_type) && $redirect_type !== 'slash_redirect') {
710 $current_uri = trim($wp->request, "/");
711 $redirect_uri = trim(parse_url($correct_permalink, PHP_URL_PATH), "/");
712
713 $correct_permalink = ($redirect_uri == $current_uri) ? null : $correct_permalink;
714 }
715
716 /**
717 * 4. Add endpoints to redirect URL
718 */
719 if(!empty($correct_permalink) && $endpoint_redirect && ($redirect_type !== 'slash_redirect') && (!empty($pm_query['endpoint_value']) || !empty($pm_query['endpoint']))) {
720 $endpoint_value = $pm_query['endpoint_value'];
721
722 if(empty($pm_query['endpoint']) && is_numeric($endpoint_value)) {
723 $correct_permalink = sprintf("%s/%d", trim($correct_permalink, "/"), $endpoint_value);
724 } else if(isset($pm_query['endpoint']) && !empty($endpoint_value)) {
725 if($pm_query['endpoint'] == 'cpage') {
726 $correct_permalink = sprintf("%s/%s-%s", trim($correct_permalink, "/"), $wp_rewrite->comments_pagination_base, $endpoint_value);
727 } else {
728 $correct_permalink = sprintf("%s/%s/%s", trim($correct_permalink, "/"), $pm_query['endpoint'], $endpoint_value);
729 }
730 } else {
731 $correct_permalink = sprintf("%s/%s", trim($correct_permalink, "/"), $pm_query['endpoint']);
732 }
733 }
734 } else {
735 $queried_object = '-';
736 }
737
738 /**
739 * 5. Check trailing slashes (ignore links with query parameters)
740 */
741 if($trailing_slashes_mode && $trailing_slashes_redirect && empty($correct_permalink) && empty($query_string) && !empty($old_uri) && !is_front_page()) {
742 // Check if $old_uri ends with slash or not
743 $ends_with_slash = (substr($old_uri, -1) == "/") ? true : false;
744 $trailing_slashes_mode = (preg_match("/.*\.([a-zA-Z]{3,4})\/?$/", $old_uri) && $trailing_slashes_mode == 1) ? 2 : $trailing_slashes_mode;
745
746 // Ignore empty URIs
747 if($old_uri != "/") {
748 $new_uri = trim($old_uri, '/');
749
750 // 2A. Force trailing slashes
751 if($trailing_slashes_mode == 1 && $ends_with_slash == false) {
752 $correct_permalink = sprintf("%s/%s/", $home_url, $new_uri);
753 }
754 // 2B. Remove trailing slashes
755 else if($trailing_slashes_mode == 2 && $ends_with_slash == true) {
756 $correct_permalink = sprintf("%s/%s", $home_url, $new_uri);
757 }
758 // 2C. Remove duplicated trailing slashes
759 else if($trailing_slashes_mode == 1 && preg_match('/[\/]{2,}$/', $old_uri)) {
760 $correct_permalink = sprintf("%s/%s/", $home_url, $new_uri);
761 }
762 }
763
764 $redirect_type = (!empty($correct_permalink)) ? 'slash_redirect' : '-';
765 }
766
767 /**
768 * 6. WWW prefix | SSL mismatch redirect
769 */
770 if(!empty($permalink_manager_options['general']['sslwww_redirect'])) {
771 $home_url_has_www = (strpos($home_url, 'www.') !== false) ? true : false;
772 $requested_url_has_www = (strpos($_SERVER['HTTP_HOST'], 'www.') !== false) ? true : false;
773 $home_url_has_ssl = (strpos($home_url, 'https') !== false) ? true : false;
774 $requested_url_has_ssl = is_ssl();
775
776 if(($home_url_has_www !== $requested_url_has_www) || ($home_url_has_ssl !== $requested_url_has_ssl)) {
777 $new_uri = ltrim($old_uri, '/');
778 $correct_permalink = sprintf("%s/%s", $home_url, $new_uri);
779
780 $redirect_type = 'www_redirect';
781 }
782 }
783
784 /**
785 * 7. Debug redirect
786 */
787 $correct_permalink = apply_filters('permalink_manager_filter_redirect', $correct_permalink, $redirect_type, $queried_object);
788
789 /**
790 * 8. Ignore default URIs (or do nothing if redirects are disabled)
791 */
792 if(!empty($correct_permalink) && is_string($correct_permalink) && !empty($redirect_mode)) {
793 // Allow redirect
794 $wp_query->query_vars['do_not_redirect'] = 0;
795
796 // Append query string
797 $correct_permalink = (!empty($query_string)) ? sprintf("%s?%s", strtok($correct_permalink, "?"), $query_string) : $correct_permalink;
798
799 // Adjust trailing slashes
800 $correct_permalink = self::control_trailing_slashes($correct_permalink);
801
802 // Prevent redirect loop
803 $rel_old_uri = wp_make_link_relative($old_uri);
804 $rel_correct_permalink = wp_make_link_relative($correct_permalink);
805
806 if($rel_old_uri !== $rel_correct_permalink) {
807 wp_safe_redirect($correct_permalink, $redirect_mode, PERMALINK_MANAGER_PLUGIN_NAME);
808 exit();
809 }
810 }
811 }
812
813 function adjust_canonical_redirect() {
814 global $permalink_manager_options, $permalink_manager_uris, $wp, $wp_rewrite;
815
816 // Adjust rewrite settings for trailing slashes
817 $trailing_slash_setting = (!empty($permalink_manager_options['general']['trailing_slashes'])) ? $permalink_manager_options['general']['trailing_slashes'] : "";
818 if(in_array($trailing_slash_setting, array(1, 10))) {
819 $wp_rewrite->use_trailing_slashes = true;
820 } else if(in_array($trailing_slash_setting, array(2, 20))) {
821 $wp_rewrite->use_trailing_slashes = false;
822 }
823
824 // Get endpoints
825 $endpoints = Permalink_Manager_Helper_Functions::get_endpoints();
826 $endpoints_array = ($endpoints) ? explode("|", $endpoints) : array();
827
828 // Check if any endpoint is called (fix for feed and similar endpoints)
829 foreach($endpoints_array as $endpoint) {
830 if(!empty($wp->query_vars[$endpoint]) && !in_array($endpoint, array('attachment', 'page', 'paged'))) {
831 $wp->query_vars['do_not_redirect'] = 1;
832 break;
833 }
834 }
835
836 // Do nothing for posts and terms without custom URIs (when canonical redirect is enabled)
837 if(is_singular() || is_tax() || is_category() || is_tag()) {
838 $element = get_queried_object();
839 if(!empty($element->ID)) {
840 $custom_uri = (!empty($permalink_manager_uris[$element->ID])) ? $permalink_manager_uris[$element->ID] : "";
841 } else if(!empty($element->term_id)) {
842 $custom_uri = (!empty($permalink_manager_uris["tax-{$element->term_id}"])) ? $permalink_manager_uris["tax-{$element->term_id}"] : "";
843 }
844 }
845
846 if(empty($permalink_manager_options['general']['canonical_redirect'])) {
847 remove_action('template_redirect', 'redirect_canonical');
848 }
849
850 if(empty($permalink_manager_options['general']['old_slug_redirect'])) {
851 remove_action('template_redirect', 'wp_old_slug_redirect');
852 }
853
854 if(!empty($wp->query_vars['do_not_redirect'])) {
855 // RankMath
856 remove_action('template_redirect', 'do_redirection', 11);
857 remove_action('wp', 'do_redirection', 11);
858
859 // SEOPress
860 remove_action('template_redirect', 'seopress_category_redirect', 1);
861
862 remove_action('template_redirect', 'wp_old_slug_redirect');
863 remove_action('template_redirect', 'redirect_canonical');
864 add_filter('wpml_is_redirected', '__return_false', 99, 2);
865 add_filter('pll_check_canonical_url', '__return_false', 99, 2);
866 }
867 }
868
869 /**
870 * Case insensitive permalinks
871 */
872 function case_insensitive_permalinks() {
873 global $permalink_manager_options, $permalink_manager_uris;
874
875 if(!empty($_SERVER['REQUEST_URI'])) {
876 $_SERVER['REQUEST_URI'] = strtolower($_SERVER['REQUEST_URI']);
877 $permalink_manager_uris = array_map('strtolower', $permalink_manager_uris);
878 }
879 }
880
881 }
882