Skip to content

Commit 8693e0f

Browse files
committed
Create remove-category-base.php
1 parent c593b7d commit 8693e0f

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

permalinks/remove-category-base.php

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
<?php
2+
/**
3+
* Plugin Name: The SEO Framework - Remove category base
4+
* Plugin URI: https://theseoframework.com/
5+
* Description: Removed the category base from the URL, akin to how Yoast SEO does it.
6+
* Version: 1.0.0
7+
* Author: Sybre Waaijer
8+
* Author URI: https://theseoframework.com/
9+
* License: GPLv3
10+
* Requires at least: 5.9
11+
* Requires PHP: 7.4.0
12+
* Requires Plugins: autodescription
13+
*
14+
* @package My_The_SEO_Framework\Permalinks
15+
*
16+
* This is forked from Yoast SEO plugin: https://github.com/Yoast/wordpress-seo/blob/f61227287fbfdf8c175ddd2168336ac22dfd2540/inc/class-rewrite.php
17+
*
18+
* It's been modified to always remove the category base, and not just when the option is set.
19+
* This is because we do not have access to WPSEO_Options in this fork.
20+
*
21+
* To impove performance, the following changes were made:
22+
* It's also transformed to a functional style, instead of a class.
23+
* It's also made procedural where possible
24+
* Optimized opcodes (namespace escaping, removing unnecessary jumps and redundant variable assignments, etc.).
25+
*
26+
* This fork was requested because this plugin doesn't reintroduce the 'blog' base: https://wordpress.org/plugins/no-category-base-wpml/.
27+
*/
28+
29+
namespace My_The_SEO_Framework;
30+
31+
\defined( 'ABSPATH' ) or die;
32+
33+
\define( 'MY_THE_SEO_FRAMEWORK_REMOVE_CATEGORY_BASE_BASENAME', \plugin_basename( __FILE__ ) );
34+
35+
\add_filter( 'query_vars', __NAMESPACE__ . '\register_query_vars' );
36+
\add_filter( 'term_link', __NAMESPACE__ . '\remove_category_base', 10, 3 );
37+
\add_filter( 'request', __NAMESPACE__ . '\redirect_base' );
38+
\add_filter( 'category_rewrite_rules', __NAMESPACE__ . '\modify_category_rewrite_rules' );
39+
40+
\add_action( 'created_category', __NAMESPACE__ . '\schedule_flush_rewrite_rules' );
41+
\add_action( 'edited_category', __NAMESPACE__ . '\schedule_flush_rewrite_rules' );
42+
\add_action( 'delete_category', __NAMESPACE__ . '\schedule_flush_rewrite_rules' );
43+
44+
\add_action( 'activate_' . \MY_THE_SEO_FRAMEWORK_REMOVE_CATEGORY_BASE_BASENAME, __NAMESPACE__ . '\schedule_flush_rewrite_rules' );
45+
\add_action( 'deactivate_' . \MY_THE_SEO_FRAMEWORK_REMOVE_CATEGORY_BASE_BASENAME, __NAMESPACE__ . '\schedule_flush_rewrite_rules' );
46+
47+
/**
48+
* Override the category link to remove the category base.
49+
*
50+
* @hook term_link 10
51+
* @since 1.0.0
52+
*
53+
* @param string $link Term link, overridden by the function for categories.
54+
* @param WP_Term $term Unused, term object.
55+
* @param string $taxonomy Taxonomy slug.
56+
* @return string
57+
*/
58+
function remove_category_base( $link, $term, $taxonomy ) {
59+
60+
if ( 'category' !== $taxonomy )
61+
return $link;
62+
63+
$category_base_quoted = preg_quote(
64+
\trailingslashit( ltrim( \get_option( 'category_base' ) ?: 'category', '\/' ) ),
65+
'/',
66+
);
67+
68+
return preg_replace(
69+
"/$category_base_quoted/u",
70+
'',
71+
$link,
72+
1,
73+
);
74+
}
75+
76+
/**
77+
* Update the query vars with the redirect var when stripcategorybase is active.
78+
*
79+
* @hook query_vars 10
80+
* @since 1.0.0
81+
*
82+
* @param array<string> $query_vars Main query vars to filter.
83+
* @return array<string> The query vars.
84+
*/
85+
function register_query_vars( $query_vars ) {
86+
$query_vars[] = 'mytsf_category_redirect';
87+
return $query_vars;
88+
}
89+
90+
/**
91+
* Checks whether the redirect needs to be created.
92+
*
93+
* @hook request 10
94+
* @since 1.0.0
95+
*
96+
* @param array<string> $query_vars Query vars to check for existence of redirect var.
97+
* @return array<string> The query vars.
98+
*/
99+
function redirect_base( $query_vars ) {
100+
101+
if ( empty( $query_vars['mytsf_category_redirect'] ) )
102+
return $query_vars;
103+
104+
\wp_safe_redirect(
105+
\trailingslashit( \get_option( 'home' ) ) . \user_trailingslashit( $query_vars['mytsf_category_redirect'], 'category' ),
106+
301,
107+
);
108+
exit;
109+
}
110+
111+
/**
112+
* This function taken and only slightly adapted from WP No Category Base plugin by Saurabh Gupta.
113+
*
114+
* @hook category_rewrite_rules 10
115+
* @since 1.0.0
116+
*
117+
* @return array<string> The category rewrite rules.
118+
*/
119+
function modify_category_rewrite_rules() {
120+
global $wp_rewrite;
121+
122+
$category_rewrite = [];
123+
124+
$taxonomy = \get_taxonomy( 'category' );
125+
$permalink_structure = \get_option( 'permalink_structure' );
126+
127+
$blog_prefix = \is_main_site() && str_starts_with( $permalink_structure, '/blog/' )
128+
? 'blog/'
129+
: '';
130+
131+
$categories = \get_categories( [ 'hide_empty' => false ] );
132+
133+
if ( \is_array( $categories ) && $categories ) {
134+
foreach ( $categories as $category ) {
135+
$category_nicename = $category->slug;
136+
if ( $category->parent === $category->cat_ID ) {
137+
// Recursive recursion.
138+
$category->parent = 0;
139+
} elseif ( false !== $taxonomy->rewrite['hierarchical'] && 0 !== $category->parent ) {
140+
141+
$parents = \get_category_parents( $category->parent, false, '/', true );
142+
143+
if ( ! \is_wp_error( $parents ) )
144+
$category_nicename = $parents . $category_nicename;
145+
146+
unset( $parents );
147+
}
148+
149+
$category_rewrite = add_category_rewrites( $category_rewrite, $category_nicename, $blog_prefix, $wp_rewrite->pagination_base );
150+
151+
// Adds rules for the uppercase encoded URIs.
152+
$category_nicename_filtered = str_contains( $category_nicename, '%' )
153+
? implode(
154+
'/',
155+
array_map(
156+
fn( $encoded ) => str_contains( $encoded, '%' ) ? strtoupper( $encoded ) : $encoded,
157+
explode( '/', $category_nicename ),
158+
),
159+
)
160+
: $category_nicename;
161+
162+
if ( $category_nicename_filtered !== $category_nicename ) {
163+
$category_rewrite = add_category_rewrites( $category_rewrite, $category_nicename_filtered, $blog_prefix, $wp_rewrite->pagination_base );
164+
}
165+
}
166+
unset( $categories, $category, $category_nicename, $category_nicename_filtered );
167+
}
168+
169+
// Redirect support from Old Category Base.
170+
$old_base = $wp_rewrite->get_category_permastruct();
171+
$old_base = str_replace( '%category%', '(.+)', $old_base );
172+
$old_base = trim( $old_base, '/' );
173+
174+
$category_rewrite[ $old_base . '$' ] = 'index.php?mytsf_category_redirect=$matches[1]';
175+
176+
return $category_rewrite;
177+
}
178+
179+
/**
180+
* Adds required category rewrites rules.
181+
*
182+
* @since 1.0.0
183+
*
184+
* @param array<string> $rewrites The current set of rules.
185+
* @param string $category_name Category nicename.
186+
* @param string $blog_prefix Multisite blog prefix.
187+
* @param string $pagination_base WP_Query pagination base.
188+
* @return array<string> The added set of rules.
189+
*/
190+
function add_category_rewrites( $rewrites, $category_name, $blog_prefix, $pagination_base ) {
191+
192+
$rewrite_name = "$blog_prefix($category_name)";
193+
194+
$rewrites += [
195+
"$rewrite_name/feed/(feed|rdf|rss|rss2|atom)/?$" => 'index.php?category_name=$matches[1]&feed=$matches[2]',
196+
"$rewrite_name/$pagination_base/([0-9]{1,})/?$" => 'index.php?category_name=$matches[1]&paged=$matches[2]',
197+
"$rewrite_name/?$" => 'index.php?category_name=$matches[1]',
198+
];
199+
200+
return $rewrites;
201+
}
202+
203+
/**
204+
* Trigger a rewrite_rule flush on shutdown.
205+
*
206+
* @hook created_category 10
207+
* @hook edited_category 10
208+
* @hook delete_category 10
209+
* @since 1.0.0
210+
*
211+
* @return void
212+
*/
213+
function schedule_flush_rewrite_rules() {
214+
\add_action( 'shutdown', 'flush_rewrite_rules' );
215+
}

0 commit comments

Comments
 (0)