If we want to create our own permalinks for custom post types, we can do so by using the WP_Rewrite WordPress object.
We can also create our own permalink rules from scratch, by using the same object.
One very important function while creating permalink rules is the $wp_rewrite->flush_rules() function. This function does the following –
- Deletes the ‘rewrite_rules’ option. This is where all the permalink rules for our blog are stored. When someone clicks on one of our blog pages, the parse_request function retrieves these rewrite rules and translates them into a query request.
- Regenerates new rewrite rules from scratch using the wp_rewrite_rules function. This function regenerates the rules and updates the ‘rewrite_rules’ option.
- It may also write to the .htaccess file if necessary.
When we add new permalink structures with register_post_type or add_permastruct, the structures only get added to the global $wp_rewrite object. It does not manifest as a rewrite rule until after we execute the $wp_rewrite->flush_rules() command.
That is why if we have a plugin that adds new permalink rules (including through register_post_type) we want to run $wp_rewrite->flush_rules() –
- During the activation process to ensure that our new permalink structures show up in the blog rewrite rules.
- During the deactivation process to remove our permalink structures from the blog rewrite rules.
function _activate() { global $wp_rewrite; $wp_rewrite->add_rewrite_tag("%gallery%", '([^/]+)', "gallery="); $wp_rewrite->add_permastruct('gallery', $gallery_structure, false); $wp_rewrite->flush_rules(); } function _deactivate() { global $wp_rewrite; $wp_rewrite->add_permastruct( 'gallery', ''); $wp_rewrite->flush_rules(); }
Note – flush_rules is also run by the WordPress system when we Save Changes in Settings >> Permalinks. Therefore, if our blog permalinks are not working properly, we can manually flush our permalink rules by going into Settings >> Permalinks and doing a save.
Flushing Permalink Rules in WordPress Multi-Site
In WordPress multi-site, we may need to flush rewrite rules for multiple blogs during network activation and deactivation.
However, there is a fly in the ointment. When we change blogs using set_blog_id, we are only changing the database context. The global $wp_rewrite object remains the same, i.e., it is the $wp_rewrite object of the original blog from where we executed the network activate or deactivate.
If we cycle through the blogs of a multi-site configuration and simply execute flush_rules, the wrong permalink rules from the non-updated $wp_rewrite object will be written into the ‘rewrite_rules’ option of each of the blogs. In effect, we would mess up the permalink rules of all our blogs except for the original activation/deactivation blog.
To properly flush permalink rules, we must limit our changes to the database and not access blog specific data stored within the $wp_rewrite object. Below, we do this by adding new permalink rules directly into the ‘rewrite_rules’ database option instead of using the flush_rules function.
function shiba_add_rewrite_rules($permastruct, $ep_mask=EP_NONE) { if (!$permastruct)return; global $wp_rewrite; $wp_rewrite->matches = 'matches'; // this is necessary to write the rules properly $new_rules = $wp_rewrite->generate_rewrite_rules($permastruct, $ep_mask); $rules = get_option('rewrite_rules'); $rules = array_merge($new_rules, $rules); update_option('rewrite_rules', $rules); } // remove all rewrite rules for a given permastruct function shiba_remove_rewrite_rules($permastruct, $ep_mask=EP_NONE) { // replace all tags within permastruct if (!$permastruct)return; global $wp_rewrite; $wp_rewrite->matches = 'matches'; $remove_rules = $wp_rewrite->generate_rewrite_rules($permastruct); $num_rules = count($remove_rules); // Get first rule $rule1 = reset($remove_rules); $key_rule1 = key($remove_rules); $rules = get_option('rewrite_rules'); $i = $num_rules; foreach ($rules as $pretty_link => $query_link) { // find the first rule if (($pretty_link == $key_rule1) && ($query_link == $rule1)) { $i = 0; } if ($i < $num_rules) { // Delete next $num_rules unset($rules[$pretty_link]); $i++; } } update_option('rewrite_rules', $rules); }
Line 5 – Generate permalink rules for our plugin permalink structures. Even though we are using $wp_rewrite here, we are only using one of its functions. Most importantly, that function does not rely on any blog specific data or state stored in the $wp_rewrite object.
Line 6 – Get the ‘rewrite rules’ for the current blog. This is a database operation which does get updated with set_blog_id.
Line 7 – Merge our new permalink rules with the previous blog rules.
Line 8 – Update the ‘rewrite rules’ for the current blog.
In the shiba_remove_rewrite_rules function we use a similar process to remove the permalink rules added by our plugin.
We Are Done!
Now we can flush permalink rules in our regular and multi-site blogs!
A simpler solution for Multisite is to re-initialize the rewrite rules before flushing them:
https://gist.github.com/iandunn/c5a983d1a9d4b09d6d20
Thanks! Will have to try it out.
Hi Shiba,
Thanks for this information. How/where do we call the rewrite functions for multisite? Is it part of the activation routine, and where is the $permalink variable is being passed from?
Thanks!
(actually it’s the $permastruct var that’s being passed, sorry about that)
I pass in the permastruct variable myself. It is simply the permalink structure I want to set my object to, for example-
“/gallery/%monthnum%/%category%/%gallery%”
I call the flush functions during activation and deactivation.
Great! That makes sense. Thank you for the speedy reply. You are pretty, awesome, you know, right? : )
I’m back … I was having trouble with WordPress adding yet another “/blog/” before my custom post type slug. I had to set the “with_front” property of the custom post type rewrite setting to “False” (and not put it in quotes!!! yeah, that was kinda stupid). Anyway, I just though I’d mention it in case anyone else crazy enough to use both Multisite and Custom Post Types comes along.
You can also add rewrite rules within the rewrite_rules_array filter directly. I wrote up this gem earlier today with the purpose of manually creating callback linked action pages. The usage would be something like:
new action_page(‘cart’, array(‘items’));
add_action(‘admin_post_nopriv_cart’, array($this, ‘display_cart’));
add_action(‘admin_post_cart’, array($this, ‘display_cart’));
Still a bit confuse here.. I am trying to add a new category using the WP API (multiple sites), the permalinks are not refreshing.
So when a visitor goes to the page on the site it throws a 404 error. I have to manually go to to wp-admin -> settings -> permalinks and save to solve the problem.
Here is what I am doing that is not working:
add_action(‘init’, ‘flush_permalinks’);
function flush_permalinks(){
global $wp_rewrite;
$wp_rewrite->set_permalink_structure(‘/%postname%/’);
//$wp_rewrite->flush_rules();
// flush_rewrite_rules();
}
Could you find my error? Thanks.
what the $gallery_structure, in $wp_rewrite->add_permastruct(‘gallery’, $gallery_structure, false); contains, (I understand its a struct based on google search). BUt its initialized or assigned a value?
$gallery_structure contains a regular WP permalink structure, e.g. /galleries/%year%/%monthnum%/%gallery%.
http://shibashake.com/wordpress-theme/custom-post-type-permalinks-part-2