Last active
October 6, 2024 04:31
-
-
Save amurrell/00d29a86fc1a773274bf049ef545b29f to your computer and use it in GitHub Desktop.
Allow wordpress posts to have hierarchy - figures out slugs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
add_action('registered_post_type', 'make_posts_hierarchical', 99, 2); | |
/** | |
* Ensure posts post type is hierarchal and allows page attributes | |
* | |
* Initial Setup - Runs after each post type is registered | |
*/ | |
function make_posts_hierarchical($post_type, $pto) | |
{ | |
// Return, if not post type posts | |
if ($post_type != 'post') return; | |
// access $wp_post_types global variable | |
global $wp_post_types; | |
// Set post type "post" to be hierarchical | |
$wp_post_types['post']->hierarchical = 1; | |
// Add page attributes to post backend | |
// This adds the box to set up parent and menu order on edit posts. | |
add_post_type_support('post', 'page-attributes'); | |
} | |
/** | |
* | |
* Edit View of Permalink | |
* | |
* This affects editing permalinks, and $permalink is an array [template, replacement] | |
* where replacement is the post_name and template has %postname% in it. | |
* | |
**/ | |
add_filter('get_sample_permalink', function ($permalink, $post_id, $title, $name, $post) { | |
if ($post->post_type != 'post' || !$post->post_parent) { | |
return $permalink; | |
} | |
// Deconstruct the permalink parts | |
$template_permalink = current($permalink); | |
$replacement_permalink = next($permalink); | |
// Find string | |
$postname_string = '%postname%'; | |
$altered_template_with_parent_slug = get_path_from_post_id($post->ID, $postname_string); | |
$new_template = str_replace("/$postname_string/", "/$altered_template_with_parent_slug/", $template_permalink); | |
$new_permalink = [$new_template, $replacement_permalink]; | |
return $new_permalink; | |
}, 99, 5); | |
/** | |
* Alter the link to the post | |
* | |
* This affects get_permalink, the_permalink etc. | |
* This will be the target of the edit permalink link too. | |
* | |
* Note: only fires on "post" post types. | |
*/ | |
add_filter('post_link', function ($post_link, $post, $leavename) { | |
if ($post->post_type != 'post' || !$post->post_parent || $post->post_status == 'draft') { | |
return $post_link; | |
} | |
// this filter can be applied when we want the templating for slug and also when we don't. | |
// so check if the templating is there and continue to support it if so. | |
$post_slug = stristr($post_link, '%postname%') ? '%postname%' : $post->post_name; | |
$path = get_path_from_post_id($post->ID, $post_slug); | |
return home_url($path); | |
}, 99, 3); | |
/** | |
* Before getting posts | |
* | |
* Has to do with routing... adjusts the main query settings | |
* | |
*/ | |
add_action('pre_get_posts', function ($query) { | |
global $wp_query; | |
$original_query = $query; | |
$uri_with_query_string = $_SERVER['REQUEST_URI']; | |
$query_string = $_SERVER['QUERY_STRING']; | |
$uri = str_replace('?' . $query_string, '', $uri_with_query_string); | |
// Do not do this post check all the time | |
if ($uri != '/' && $query->is_main_query() && !is_admin()) { | |
$post = get_post_from_uri($uri); | |
if (!$post) { | |
return $original_query; | |
} | |
// pretty high confidence that we need to override the query. | |
$query->query_vars['post_type'] = ['post']; | |
$query->is_home = false; | |
$query->is_attachment = false; | |
$query->is_page = true; | |
$query->is_single = true; | |
$query->is_404 = false; | |
$query->queried_object_id = $post->ID; | |
$query->set('page_id', $post->ID); | |
$wp_query = $query; | |
return $query; | |
} | |
return $wp_query; | |
}, 0); | |
add_filter('preview_post_link', 'preview_redirect_fix'); | |
function preview_redirect_fix($url) | |
{ | |
global $post; | |
if ($post->post_status == 'draft') { | |
$pieces = (object) parse_url($url); | |
$url = implode('', [ | |
$pieces->scheme, | |
'://', | |
$pieces->host . '/index.php?', | |
$pieces->query | |
]); | |
} | |
return $url; | |
} | |
function get_path_from_post_id($pid, $current_slug = '') | |
{ | |
$pid_original = $pid; | |
$slugs = []; | |
while (!empty($pid)) { | |
// Load the post for pid | |
$p = get_post($pid); | |
// Allow the original post to have a different name (useful for templating %postname% in permalink preview) | |
$slugs[] = $pid == $pid_original && !empty($current_slug) | |
? $current_slug : $p->post_name; | |
// Setup parent post id as new pid | |
$pid = $p->post_parent; | |
} | |
return implode('/', array_reverse($slugs)); | |
} | |
function get_post_from_uri($uri) | |
{ | |
global $wpdb; | |
$basename = basename($uri); | |
$depth = count(explode('/', trim($uri, '/'))); | |
// This inner query says "go get all posts - (ID, SLUG, PARENT_ID) - where slug = (last part of url)" | |
$baseQuery = sprintf("select id, post_name as p1_slug, post_parent as p1_parent | |
from $wpdb->posts where post_type = '%s' and post_name = '%s'", 'post', $wpdb->_real_escape($basename)); | |
// We will use concat to make slugs out of the results! | |
// We will array_reverse the concats and implode with '/' to make the slug. | |
$concats = []; | |
$concats[] = "IFNULL(p1_slug, '')"; | |
// initialize our SQL string with the base query | |
$sql = $baseQuery; | |
// We will do 1 more depth level than we need to confirm the slug would not lazy match | |
// This for loop builds inside out. | |
for ($c = 1; $c < $depth + 2; $c++) { | |
$d = $c; | |
$p = $c + 1; | |
$pre = "select d${d}.*, p${p}.post_name as p${p}_slug, p${p}.post_parent as p${p}_parent from ("; | |
$suf = ") as d${d} left join $wpdb->posts p${p} on p${p}.id = d${d}.p${c}_parent"; | |
$sql = $pre . $sql . $suf; | |
$concats[] = sprintf("IFNULL(p${p}_slug,'')"); | |
} | |
$trimmedUri = trim($uri, '/'); | |
$concatSql = implode(", '/',", array_reverse($concats)); | |
$finalSql = "select * from (select TRIM(BOTH '/' FROM | |
concat($concatSql)) as slug, id from ($sql) as d${c}) as all_slugs | |
where slug = '$trimmedUri';"; | |
$result = $wpdb->get_results($finalSql); | |
if (empty($result) || !($post = current($result))) { | |
return false; | |
} | |
return get_post($post->id); | |
} |
@Githubeys you only need to get a plugin that handles code snippets. And than paste it as php snippet in there.
@amurrell for some reason your previous version works but the above does not seem to do anything at all.
Can you help me troubleshoot please?
The previous version:
add_action('registered_post_type', 'make_posts_hierarchical', 10, 2 );
// Runs after each post type is registered
function make_posts_hierarchical($post_type, $pto){
// Return, if not post type posts
if ($post_type != 'post') return;
// access $wp_post_types global variable
global $wp_post_types;
// Set post type "post" to be hierarchical
$wp_post_types['post']->hierarchical = 1;
// Add page attributes to post backend
// This adds the box to set up parent and menu order on edit posts.
add_post_type_support( 'post', 'page-attributes' );
}
/**
* Get parent post slug
*
* Helpful function to get the post name of a posts parent
*/
function get_parent_post_slug($post) {
if (!is_object($post) || !$post->post_parent) {
return false;
}
return get_post($post->post_parent)->post_name;
}
/**
*
* Edit View of Permalink
*
* This affects editing permalinks, and $permalink is an array [template, replacement]
* where replacement is the post_name and template has %postname% in it.
*
**/
add_filter('get_sample_permalink', function($permalink, $post_id, $title, $name, $post) {
if ($post->post_type != 'post' || !$post->post_parent) {
return $permalink;
}
// Deconstruct the permalink parts
$template_permalink = current($permalink);
$replacement_permalink = next($permalink);
// Find string
$postname_string = '/%postname%/';
// Get parent post
$parent_slug = get_parent_post_slug($post);
$altered_template_with_parent_slug = '/' . $parent_slug . $postname_string;
$new_template = str_replace($postname_string, $altered_template_with_parent_slug, $template_permalink);
$new_permalink = [$new_template, $replacement_permalink];
return $new_permalink;
}, 99, 5);
/**
* Alter the link to the post
*
* This affects get_permalink, the_permalink etc.
* This will be the target of the edit permalink link too.
*
* Note: only fires on "post" post types.
*/
add_filter('post_link', function($post_link, $post, $leavename){
if ($post->post_type != 'post' || !$post->post_parent) {
return $post_link;
}
$parent_slug = get_parent_post_slug($post);
$new_post_link = str_replace($post->post_name, $parent_slug . '/' . $post->post_name, $post_link);
return $new_post_link;
}, 99, 3);
/**
* Before getting posts
*
* Has to do with routing... adjusts the main query settings
*
*/
add_action('pre_get_posts', function($query){
global $wpdb, $wp_query;
$original_query = $query;
$uri = $_SERVER['REQUEST_URI'];
// Do not do this post check all the time
if ( $query->is_main_query() && !is_admin()) {
// get the post_name
$basename = basename($uri);
// find out if we have a post that matches this post_name
$test_query = sprintf("select * from $wpdb->posts where post_type = '%s' and post_name = '%s';", 'post', $basename);
$result = $wpdb->get_results($test_query);
// if no match, return default query, or if there's no parent post, this is not necessary
if (!($post = current($result)) || !$post->post_parent) {
return $original_query;
}
// get the parent slug
$parent_slug = get_parent_post_slug($post);
// concat the parent slug with the post_name to get most of the url
$hierarchal_slug = $parent_slug . '/' . $post->post_name;
// if the concat of parent-slug/post-name is not in the uri, this is not the right post.
if (!stristr($uri, $hierarchal_slug)) {
return $original_query;
}
// pretty high confidence that we need to override the query.
$query->query_vars['post_type'] = ['post'];
$query->is_home = false;
$query->is_attachment = false;
$query->is_page = true;
$query->is_single = true;
$query->queried_object_id = $post->ID;
$query->set('page_id', $post->ID);
return $query;
}
}, 1);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello,
I wanted a plugin that will enable me to set a post as the parent of another post. Would this tweak be helpful?
How about making this tweak into a plugin since it would be difficult to use it the way it is?
I would appreciate your response. Thanks