Custom Post Type Permalink Rules Are Not Updated Correctly When Saving Changes
|Reported by:||MarcusPope||Owned by:|
When changing permalink structures using Common Settings or Custom Structure, clicking Save Changes will only update core system rewrite rules. Custom Post Type rewrite rules are re-used from the previous setting. If you click save changes twice the correct rewrite rules are updated and used.
If you only save once, canonical.php will step in and issue a 301 redirect to the appropriate post. But if you save twice it doesn't have to do any work at all. This is actually causing an infinite 301 redirect with one of my plugins. I have a fix to prevent the recursion, but it still means it cannot find my custom post type content until I update the permalinks settings again. This behavior occurs whether you register post types in "init" or in a theme's functions.php or in a plugin.
I've seen multiple core devs and users say off-handedly that you should save the page once or twice as seen in these links:
The reason this behavior occurs is because flush_rewrite_rules() doesn't actually flush custom post type rewrite rules stored in WP_Rewrite::$extra_permastructs. WP_Rewrite::init() will only reset internal core posts/pages/category/etc structures. And $extra_permastructs rules are created on register_post_type() which occurs before the form post to options-permalink.php has a chance to update the structure and $front values.
So order of execution is:
- Change Permalink Structure and Page Post to options-permalink.php
- Read structure setting from database
- Load system permalinks
- Load plugins and register their permalinks
- options-permalink updates the structure setting value in the database
- Calls flush system permalinks
- Loads new system permalinks and merges with old CPT permalinks because it never calls register_post_type()
On a second save, step 4 will have the correct permalinks structure, so when they are merged with the already correct system permalinks all is well with the universe again.
Ideally flush_rewrite_rules() actually flushes all rewrite rules. But I'm not sure if calling register_post_type() twice in one page load is safe. It could do an ajax post for the first change, and then a full form post for custom post types - but that seems a little hackey. I don't have a good solution yet so I can't provide a patch.
To reproduce and debug this behavior, setup at least one CPT and dump the variable $this->rules at the end of the function rewrite_rules() in rewrite.php. Go into permalinks settings page, change your structure to Numeric, click save. The log file will list all of the rules but the CPT will not have "archives" in front of their URL (unless Numeric was already selected.) Now select a different structure - "Post name", click save, and the CPT urls will have "archives" in front of the url, but the others will not. Click save again, and CPT's will be fixed.
Change History (6)
- Component changed from Permalinks to Rewrite Rules
- Milestone changed from Awaiting Review to 3.5
3 years ago
- Milestone 3.5 deleted
- Resolution set to duplicate
- Status changed from new to closed