PluginProbe
WP EXtra – One Click Optimize / trunk
WP EXtra – One Click Optimize vtrunk
8.7.1 8.6.8 8.7.0 trunk 5.9 8.0 8.5.0 8.5.4 8.5.5 8.6.0 8.6.1 8.6.2 8.6.3 8.6.5
wp-extra / src / Modules / Common / Permalinks.php

Permalinks.php in WP EXtra – One Click Optimize trunk, at src/Modules/Common/Permalinks.php

417 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPEXtra\Modules\Common;
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 use WPEXtra\Settings;
9 use WPEXtra\Helper;
10 use WPEXtra\Base;
11
12 class Permalinks extends Base
13 {
14
15 public function __construct()
16 {
17 parent::__construct();
18 add_action('update_option_wp_extra', [$this, 'sync_rewrite_rules'], 10, 2);
19 }
20
21 public function sync_rewrite_rules($old_value, $value)
22 {
23 $keys = ['slug_post_type', 'slug_taxonomy', 'slug_category_post', 'slug_hierarchical_page'];
24 $needs_flush = false;
25 foreach ($keys as $key) {
26 if (($old_value[$key] ?? null) !== ($value[$key] ?? null)) {
27 $needs_flush = true;
28 break;
29 }
30 }
31 if ($needs_flush) {
32 flush_rewrite_rules(false);
33 }
34 }
35
36 protected $features = [
37 'external_links',
38 'redirect_attachment',
39 'redirect_single_post',
40 'slug_post_type',
41 'slug_taxonomy',
42 'slug_category_post',
43 'slug_hierarchical_page',
44 'robots_txt',
45 ];
46
47 public function external_links()
48 {
49 add_filter('the_content', [$this, 'filter_external_links'], 99);
50 }
51
52 public function filter_external_links($content)
53 {
54 if (empty($content) || !is_string($content)) {
55 return $content;
56 }
57
58 $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
59
60 if (class_exists('\WP_HTML_Tag_Processor')) {
61 $processor = new \WP_HTML_Tag_Processor($content);
62
63 while ($processor->next_tag(['tag_name' => 'a'])) {
64 $href = (string) $processor->get_attribute('href');
65 if ($href && preg_match('#^https?://#i', $href)) {
66 $link_host = wp_parse_url($href, PHP_URL_HOST);
67 if ($link_host && strcasecmp($link_host, $site_host) !== 0) {
68 $processor->set_attribute('target', '_blank');
69
70 $rel = (string) $processor->get_attribute('rel');
71 $rel_parts = array_filter(array_map('trim', explode(' ', $rel)));
72
73 if (!in_array('nofollow', $rel_parts, true)) {
74 $rel_parts[] = 'nofollow';
75 }
76 if (!in_array('noopener', $rel_parts, true)) {
77 $rel_parts[] = 'noopener';
78 }
79 if (!in_array('noreferrer', $rel_parts, true)) {
80 $rel_parts[] = 'noreferrer';
81 }
82
83 $processor->set_attribute('rel', implode(' ', array_unique($rel_parts)));
84 }
85 }
86 }
87
88 return $processor->get_updated_html();
89 }
90
91 // Regex fallback
92 return preg_replace_callback('/<a\s+([^>]+)>/i', function ($matches) use ($site_host) {
93 $attrs = $matches[1];
94 if (preg_match('/href=["\'](https?:\/\/[^"\']+)["\']/i', $attrs, $url_match)) {
95 $link_host = wp_parse_url($url_match[1], PHP_URL_HOST);
96 if ($link_host && strcasecmp($link_host, $site_host) !== 0) {
97 if (!preg_match('/target=["\']/i', $attrs)) {
98 $attrs .= ' target="_blank"';
99 }
100 if (preg_match('/rel=["\']([^"\']*)["\']/i', $attrs, $rel_match)) {
101 $rel = $rel_match[1];
102 if (strpos($rel, 'nofollow') === false)
103 $rel .= ' nofollow';
104 if (strpos($rel, 'noopener') === false)
105 $rel .= ' noopener';
106 if (strpos($rel, 'noreferrer') === false)
107 $rel .= ' noreferrer';
108 $attrs = preg_replace('/rel=["\'][^"\']*["\']/i', 'rel="' . trim($rel) . '"', $attrs);
109 } else {
110 $attrs .= ' rel="nofollow noopener noreferrer"';
111 }
112 }
113 }
114 return '<a ' . trim($attrs) . '>';
115 }, $content);
116 }
117
118 public function redirect_attachment()
119 {
120 add_action('template_redirect', [$this, 'redirect_attachment_page']);
121 }
122
123 public function redirect_attachment_page()
124 {
125 if (is_attachment()) {
126 global $post;
127 if ($post && !empty($post->post_parent)) {
128 wp_safe_redirect(esc_url(get_permalink($post->post_parent)), 301);
129 exit;
130 } else {
131 wp_safe_redirect(esc_url(home_url('/')), 301);
132 exit;
133 }
134 }
135 }
136
137 public function redirect_single_post()
138 {
139 add_action('template_redirect', [$this, 'search_results_return_one_post']);
140 }
141
142 public function search_results_return_one_post()
143 {
144 if (is_search()) {
145 global $wp_query;
146 if ($wp_query && $wp_query->post_count == 1 && $wp_query->max_num_pages == 1 && !empty($wp_query->posts[0])) {
147 wp_safe_redirect(get_permalink($wp_query->posts[0]->ID));
148 exit;
149 }
150 }
151 }
152
153 public function robots_txt()
154 {
155 add_filter('robots_txt', [$this, 'filter_robots_txt'], 99, 2);
156 }
157
158 public function filter_robots_txt($output, $public)
159 {
160 $custom = Helper::get_option('robots_txt');
161 if (!empty($custom)) {
162 return $custom;
163 }
164 return $output;
165 }
166
167 public function slug_post_type()
168 {
169 add_filter('request', [$this, 'rempostslug_request_filter'], 15, 1);
170 add_action('pre_get_posts', [$this, 'rempostslug_parse_request'], 1, 1);
171 add_filter('post_type_link', [$this, 'rempostslug_fun'], 10, 3);
172 add_filter('get_the_permalink', [$this, 'rempostslug_fun'], 10, 3);
173 add_filter('the_permalink', [$this, 'rempostslug_fun'], 10, 3);
174 }
175
176 public function slug_taxonomy()
177 {
178 add_filter('request', [$this, 'remtaxslug_change_term_request'], 1, 1);
179 add_filter('term_link', [$this, 'remtaxslug_term_permalink'], 10, 2);
180 add_filter('get_category_link', [$this, 'remtaxslug_term_permalink'], 10, 3);
181 add_filter('get_term_link', [$this, 'remtaxslug_term_permalink'], 10, 3);
182 add_filter('category_link', [$this, 'remtaxslug_term_permalink'], 10, 3);
183 }
184
185 public function rempostslug_fun($post_link, $post)
186 {
187 $post_type_list = (array) Helper::get_option('slug_post_type', []);
188 if (empty($post_type_list) || !($post instanceof \WP_Post)) {
189 return $post_link;
190 }
191 $post_type = get_post_type($post);
192 if (in_array($post_type, $post_type_list, true)) {
193 $uri = is_post_type_hierarchical($post_type) ? get_page_uri($post) : $post->post_name;
194 $post_link = home_url(user_trailingslashit($uri));
195 }
196 return $post_link;
197 }
198
199 public function rempostslug_request_filter($query_vars)
200 {
201 if (is_admin()) {
202 return $query_vars;
203 }
204
205 $post_type_list = (array) Helper::get_option('slug_post_type', []);
206 if (empty($post_type_list)) {
207 return $query_vars;
208 }
209
210 $uri_path = '';
211 if (!empty($query_vars['name'])) {
212 $uri_path = $query_vars['name'];
213 } elseif (!empty($query_vars['pagename'])) {
214 $uri_path = $query_vars['pagename'];
215 } elseif (!empty($query_vars['category_name']) && !empty($query_vars['name'])) {
216 $uri_path = $query_vars['category_name'] . '/' . $query_vars['name'];
217 } else {
218 $uri = strtok($_SERVER['REQUEST_URI'] ?? '', '?');
219 $uri_path = trim((string) $uri, '/');
220 }
221
222 if (!empty($uri_path)) {
223 $existing_page = get_page_by_path($uri_path, OBJECT, 'page');
224 if ($existing_page && 'publish' === $existing_page->post_status) {
225 return $query_vars;
226 }
227
228 $found_post = get_page_by_path($uri_path, OBJECT, $post_type_list);
229 if ($found_post && 'publish' === $found_post->post_status) {
230 unset($query_vars['error'], $query_vars['pagename'], $query_vars['category_name'], $query_vars['category']);
231 $query_vars['name'] = $found_post->post_name;
232 $query_vars['post_type'] = $found_post->post_type;
233 $query_vars[$found_post->post_type] = $uri_path;
234 return $query_vars;
235 }
236
237 if (strpos($uri_path, '/') === false) {
238 global $wpdb;
239 $escaped_types = "'" . implode("','", array_map('esc_sql', $post_type_list)) . "'";
240 $found_post_type = $wpdb->get_var($wpdb->prepare(
241 "SELECT post_type FROM {$wpdb->posts} WHERE post_name = %s AND post_type IN ({$escaped_types}) AND post_status = 'publish' LIMIT 1",
242 $uri_path
243 ));
244
245 if ($found_post_type) {
246 unset($query_vars['error'], $query_vars['pagename'], $query_vars['category_name'], $query_vars['category']);
247 $query_vars['name'] = $uri_path;
248 $query_vars['post_type'] = $found_post_type;
249 $query_vars[$found_post_type] = $uri_path;
250 }
251 }
252 }
253
254 return $query_vars;
255 }
256
257 public function rempostslug_parse_request($query)
258 {
259 if (!$query->is_main_query() || is_admin()) {
260 return;
261 }
262
263 $post_type_list = (array) Helper::get_option('slug_post_type', []);
264 if (empty($post_type_list)) {
265 return;
266 }
267
268 $name = $query->get('name');
269 if (!empty($name)) {
270 $current_post_type = $query->get('post_type');
271 if (empty($current_post_type) || $current_post_type === 'post') {
272 $query->set('post_type', array_unique(array_merge(['post', 'page'], $post_type_list)));
273 }
274 }
275 }
276
277 public function slug_category_post()
278 {
279 add_filter('post_link', [$this, 'filter_category_post_link'], 10, 2);
280 }
281
282 public function filter_category_post_link($permalink, $post)
283 {
284 if (!($post instanceof \WP_Post) || $post->post_type !== 'post') {
285 return $permalink;
286 }
287
288 $cats = get_the_category($post->ID);
289 if (!empty($cats) && !is_wp_error($cats)) {
290 $cat_slug = $cats[0]->slug;
291 return home_url('/' . $cat_slug . '/' . $post->post_name . '/');
292 }
293
294 return $permalink;
295 }
296
297 public function slug_hierarchical_page()
298 {
299 add_filter('request', [$this, 'resolve_hierarchical_page_request'], 20);
300 }
301
302 public function resolve_hierarchical_page_request($query_vars)
303 {
304 if (is_admin()) {
305 return $query_vars;
306 }
307
308 $uri_path = '';
309 if (!empty($query_vars['category_name']) && !empty($query_vars['name'])) {
310 $uri_path = $query_vars['category_name'] . '/' . $query_vars['name'];
311 } elseif (!empty($query_vars['pagename'])) {
312 $uri_path = $query_vars['pagename'];
313 } else {
314 $uri = strtok($_SERVER['REQUEST_URI'] ?? '', '?');
315 $uri_path = trim((string) $uri, '/');
316 }
317
318 if (!empty($uri_path)) {
319 $page = get_page_by_path($uri_path, OBJECT, 'page');
320
321 if ($page && 'publish' === $page->post_status) {
322 unset($query_vars['error'], $query_vars['category_name'], $query_vars['name'], $query_vars['category']);
323 $query_vars['pagename'] = $uri_path;
324 $query_vars['post_type'] = 'page';
325 return $query_vars;
326 }
327
328 $slug = $query_vars['name'] ?? $query_vars['category_name'] ?? (strpos($uri_path, '/') === false ? $uri_path : '');
329 if (!empty($slug)) {
330 $blog_page = get_page_by_path('blog/' . $slug, OBJECT, 'page');
331 if ($blog_page && 'publish' === $blog_page->post_status) {
332 unset($query_vars['error'], $query_vars['name'], $query_vars['category_name'], $query_vars['category']);
333 $query_vars['pagename'] = 'blog/' . $slug;
334 $query_vars['post_type'] = 'page';
335 return $query_vars;
336 }
337 }
338 }
339
340 return $query_vars;
341 }
342
343 public function remtaxslug_change_term_request($query)
344 {
345 $tax_names = (array) Helper::get_option('slug_taxonomy', []);
346 foreach ($tax_names as $current_tax_name) {
347 if (array_key_exists('attachment', $query) && $query['attachment']) {
348 $include_children = true;
349 $name = $query['attachment'];
350 } else {
351 $include_children = false;
352 $name = array_key_exists('name', $query) ? $query['name'] : '';
353 }
354
355 $term = get_term_by('slug', $name, $current_tax_name);
356
357 if (!empty($name) && !empty($term) && !is_wp_error($term)) {
358 if ($include_children) {
359 unset($query['attachment']);
360 $parent = $term->parent;
361 while ($parent) {
362 $parent_term = get_term($parent, $current_tax_name);
363 if ($parent_term && !is_wp_error($parent_term)) {
364 $name = $parent_term->slug . '/' . $name;
365 $parent = $parent_term->parent;
366 } else {
367 break;
368 }
369 }
370 } else {
371 unset($query['name']);
372 }
373
374 switch ($current_tax_name) {
375 case 'category':
376 $query['category_name'] = $name;
377 break;
378 case 'post_tag':
379 $query['tag'] = $name;
380 break;
381 default:
382 $query[$current_tax_name] = $name;
383 break;
384 }
385 }
386 }
387
388 return $query;
389 }
390
391 public function remtaxslug_term_permalink($url, $taxonomy)
392 {
393 if (is_int($taxonomy)) {
394 $taxonomy_term = get_term($taxonomy);
395 if (!($taxonomy_term instanceof \WP_Term)) {
396 return $url;
397 }
398 $taxonomy_slug = $taxonomy_term->slug;
399 $taxonomy_name = $taxonomy_term->taxonomy;
400 } elseif (is_object($taxonomy) && isset($taxonomy->slug, $taxonomy->taxonomy)) {
401 $taxonomy_slug = $taxonomy->slug;
402 $taxonomy_name = $taxonomy->taxonomy;
403 } else {
404 return $url;
405 }
406 $saved_value = (array) Helper::get_option('slug_taxonomy', []);
407 if (empty($saved_value)) {
408 return $url;
409 }
410 if (in_array($taxonomy_name, $saved_value, true)) {
411 $url = trailingslashit(get_option('home')) . user_trailingslashit($taxonomy_slug);
412 }
413
414 return $url;
415 }
416
417 }