Skip to content

Instantly share code, notes, and snippets.

@doiftrue
Last active January 10, 2024 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doiftrue/6b3f03e15a424aba2ea7829abfac1af9 to your computer and use it in GitHub Desktop.
Save doiftrue/6b3f03e15a424aba2ea7829abfac1af9 to your computer and use it in GitHub Desktop.
[wp-kama embed] WordPress: Friendly URLs for a Dual Multi-level Taxonomy >>> https://wp-kama.com/1326
<?php
new Same_Prefix_Post_Taxes_Rewrite();
/**
* How it works.
*
* We have:
* - ``company`` — Flat post type.
* - ``catalog`` — Hierarchical taxonomy.
* - ``location`` — Hierarchical taxonomy.
*
* We need permalinks:
* ``/company/`` prefix for all! Combined queryes for the two taxes. Working pagination!
* Examples:
* - ``/company/bmw7/`` — post.
* - ``/company/almaty/`` — location term archive (top level).
* - ``/company/almaty/auezo/`` — location term archive (level 2).
* - ``/company/almaty/auezo/three/`` — location term archive (level 3).
* - ``/company/avto/`` — catalog term archive (top level).
* - ``/company/avto/light/`` — catalog term archive (level 2).
* - ``/company/avto/light/lyuks/`` — catalog term archive (level 3).
* - ``/company/avto/almaty/`` — catalog + location terms archive (top levels).
* - ``/company/avto/almaty/auezo/`` — catalog + location terms archive (level 1 + level 2).
* - ``/company/avto/light/almaty/auezo/three/`` — catalog + location terms archive (level 2 + level 3).
*
* All of these must support pagination. Ex:
* - ``/company/almaty/page/2/`` — location term pagination page 2.
* - ``/company/avto/light/almaty/auezo/three/page/2/`` — catalog + location terms page 2.
* - etc.
*
* For posts we need additional permalinks. Ex:
* - ``site.kz/company/bmw7/reviews/`` — post.
* - ``site.kz/company/bmw7/gallery/`` — post.
* - ``site.kz/company/bmw7/edit/`` — post.
* - ``site.kz/company/bmw7/price/`` — post.
*
* @version 1.4
*
* @author Kama (wp-kama.com)
*/
class Same_Prefix_Post_Taxes_Rewrite {
static $post_type = 'company';
static $catalog_tax = 'catalog';
static $location_tax = 'location';
static $post_subpages = [
'edit' => 'Редактирование',
'price' => 'Цена',
'gallery' => 'Галлерея',
'reviews' => 'Обзоры',
];
static $request = [];
function __construct(){
add_action( 'init', [ __CLASS__, 'register_post_types' ] );
add_action( 'request', [ __CLASS__, 'fix_request' ] );
add_filter( self::$post_type . '_rewrite_rules', [ __CLASS__, 'post_rewrite_rules' ] );
add_filter( 'saved_' . self::$location_tax, [ __CLASS__, '_location_top_slugs_refresh_cache' ] );
add_filter( 'delete_' . self::$location_tax, [ __CLASS__, '_location_top_slugs_refresh_cache' ] );
add_filter( 'pre_term_link', [ __CLASS__, 'make_term_link' ], 10, 2 );
add_filter( 'query_vars', static function( $vars ){
$vars[] = 'catalog_location';
$vars[] = 'post_subpage';
return $vars;
} );
add_filter( 'get_canonical_url', [ __CLASS__, 'subpages_canonical' ], 10, 2 );
add_filter( 'single'.'_template_hierarchy', [ __CLASS__, 'subpages_template_hierarchy' ] );
isset( $_GET['rwdebug'] ) && add_action( 'wp', [ __CLASS__, '_debug' ] ); // debug
}
static function is_multi_tax_query(){
return count( self::$request ) > 1;
}
static function register_post_types(){
register_taxonomy( self::$catalog_tax, [ self::$post_type ], [
'label' => '', // определяется параметром $labels->name
'labels' => [
'name' => 'Каталог',
'singular_name' => 'Каталог',
],
'public' => true,
'hierarchical' => true,
'rewrite' => false, //[ 'slug'=>self::$post_type, 'hierarchical'=>true, 'with_front'=>false, 'feed'=>false ],
'show_admin_column' => false,
] );
register_taxonomy( self::$location_tax, [ self::$post_type ], [
'label' => '', // определяется параметром $labels->name
'labels' => [
'name' => 'Локация',
'singular_name' => 'Локация',
],
'public' => true,
'hierarchical' => true,
'rewrite' => false, //[ 'slug'=>self::$post_type, 'hierarchical'=>true, 'with_front'=>false, 'feed'=>false ],
'show_admin_column' => false,
] );
register_post_type( self::$post_type, [
'label' => null,
'labels' => [
'name' => 'Компании', // основное название для типа записи
'singular_name' => 'Компания', // название для одной записи этого типа
],
'public' => true,
'menu_position' => null,
'menu_icon' => null,
'hierarchical' => false,
'supports' => [ 'title', 'editor' ],
// 'title','editor','author','thumbnail','excerpt','trackbacks','custom-fields','comments','revisions','page-attributes','post-formats'
'taxonomies' => [ self::$catalog_tax, self::$location_tax ],
'has_archive' => true,
'rewrite' => [ 'with_front'=>false, 'feeds'=>false, 'endpoints'=>false ],
'query_var' => true,
] );
}
static function fix_request( $vars ){
// post request like catalog|location
if( ! empty( $vars['name'] ) && self::$post_type === $vars['post_type'] ){
$name = $vars['name'];
$location_top = self::_location_top_slugs();
$unset_vars__fn = static function( & $vars ){
unset( $vars['post_type'], $vars['name'], $vars[ self::$post_type ] ); // clear
};
// location
if( false !== strpos( $location_top, "|$name|" ) ){
$unset_vars__fn( $vars ); // clear
$vars[ self::$location_tax ] = $name;
}
// catalog
elseif( $term = get_term_by( 'slug', $name, self::$catalog_tax ) ){
$unset_vars__fn( $vars ); // clear
$vars[ self::$catalog_tax ] = $name;
}
}
// special catalog_location request
elseif( ! empty( $vars['catalog_location'] ) ){
$parts = explode( '/', $vars['catalog_location'] );
// maybe company post with post_subpage
if(
count( $parts ) === 2
&& isset( self::$post_subpages[ $parts[1] ] )
&& $post = get_page_by_path( $parts[0], OBJECT, [ self::$post_type ] )
){
$vars[ self::$post_type ] = $parts[0];
$vars['post_type'] = self::$post_type;
$vars['name'] = $parts[0];
$vars['post_subpage'] = $parts[1];
}
// catalog_location request
else {
$location_top = self::_location_top_slugs();
$catalogs = $locations = [];
$lnk = & $catalogs;
foreach( $parts as $part ){
if( false !== strpos( $location_top, "|$part|" ) )
$lnk = & $locations;
$lnk[] = $part;
}
unset( $lnk );
$catalog_slug = end( $catalogs );
$location_slug = end( $locations );
// for check reliability of the URL to redirect to right one
$build_catalog = $catalog_slug ? self::_build_tax_uri( $catalog_slug, self::$catalog_tax ) : '';
$build_location = $location_slug ? self::_build_tax_uri( $location_slug, self::$location_tax ) : '';
// two taxs
if( $catalog_slug && $location_slug ){
// 301 redirect to correct URL
if(
( implode( '/', $catalogs ) !== $build_catalog )
||
( implode( '/', $locations ) !== $build_location )
){
wp_redirect( user_trailingslashit( '/'. self::$post_type ."/$build_catalog/$build_location" ), 301 );
exit;
}
self::$request['catalog'] = get_term_by( 'slug', $catalog_slug, self::$catalog_tax );
self::$request['location'] = get_term_by( 'slug', $location_slug, self::$location_tax );
$vars[ self::$catalog_tax ] = $catalog_slug;
$vars[ self::$location_tax ] = $location_slug;
}
// catalog
elseif( $catalog_slug ){
// 301 redirect to correct URL
if( implode( '/', $catalogs ) !== $build_catalog ){
$url = get_term_link( $catalog_slug, self::$catalog_tax );
if( ! is_wp_error( $url ) ){
wp_redirect( $url, 301 );
exit;
}
}
self::$request['catalog'] = get_term_by( 'slug', $catalog_slug, self::$catalog_tax );
$vars[ self::$catalog_tax ] = $catalog_slug;
}
// location
elseif( $location_slug ){
// 301 redirect to correct URL
if( implode( '/', $locations ) !== $build_location ){
wp_redirect( get_term_link( $location_slug, self::$location_tax ), 301 );
exit;
}
self::$request['location'] = get_term_by( 'slug', $location_slug, self::$location_tax );
$vars[ self::$location_tax ] = $location_slug;
}
}
unset( $vars['catalog_location'] );
}
return $vars;
}
static function post_rewrite_rules( $rules ){
$_first_part = self::$post_type . "/(.+?)";
$_page_part = 'page/?([0-9]{1,})';
$more_riles = [
"$_first_part/$_page_part/?$" => 'index.php?catalog_location=$matches[1]&paged=$matches[2]',
"$_first_part/?$" => 'index.php?catalog_location=$matches[1]',
];
// delete conflict attachment rules
foreach( $rules as $regex => $rule ){
if( false === strpos( $regex, '/attachment/' ) && false !== strpos( $rule, 'attachment=' ) )
unset( $rules[ $regex ] );
}
$rules += $more_riles;
return $rules;
}
static function make_term_link( $url, $term ){
if( $term->taxonomy === self::$catalog_tax || $term->taxonomy === self::$location_tax ){
return user_trailingslashit( '/'. self::$post_type .'/'. self::_build_tax_uri( $term ) );
}
return $url;
}
/**
* Gets parent terms (including current).
*
* @param WP_Term|string $term
* @param string $taxonomy IF term passed as string.
*
* @return WP_Term[]|array
*/
static function parent_terms( $term, $taxonomy = null ){
if( is_string( $term ) )
$term = get_term_by( 'slug', $term, $taxonomy );
if( ! $term )
return [];
$path = [ $term ];
while( $term->parent ){
$term = get_term( $term->parent );
$path[] = $term;
}
return array_reverse( $path );
}
/**
* Build URL part of term of specified taxonomy. EX: 'parent/child/childchild'
*
* @param WP_Term|string $term
* @param string $taxonomy IF term passed as string.
*
* @return string
*/
static function _build_tax_uri( $term, $taxonomy = null ){
$slugs = wp_list_pluck( self::parent_terms( $term, $taxonomy ), 'slug' );
return implode( '/', $slugs );
}
/**
* Get top level location taxonomy slugs as array. Cache the result.
*
* @param bool $refresh
*/
static function _location_top_slugs( $refresh = false ){
$trans_name = 'location_top_slugs';
$top_slugs = get_transient( $trans_name );
if( $refresh || ! $top_slugs ){
$top_slugs = get_terms( [
'taxonomy' => self::$location_tax,
'parent' => 0,
'hide_empty' => false,
] );
$top_slugs = '|'. implode( '|', wp_list_pluck( $top_slugs, 'slug' ) ) .'|';
set_transient( $trans_name, $top_slugs, HOUR_IN_SECONDS );
}
return $top_slugs;
}
static function _location_top_slugs_refresh_cache(){
self::_location_top_slugs( 'refresh' );
}
static function subpages_template_hierarchy( $templates ){
if( $subpage = get_query_var('post_subpage') ){
$subpage_file = 'single-'. self::$post_type ."-$subpage.php";
array_unshift( $templates, $subpage_file );
}
return $templates;
}
static function subpages_canonical( $canonical_url, $post ){
if( $subpage = get_query_var('post_subpage') ){
$canonical_url = user_trailingslashit( rtrim( get_permalink( $post ), '/' ) ."/$subpage" );
}
return $canonical_url;
}
static function _debug(){
print_r( get_queried_object() );
print_r( $GLOBALS['wp'] );
print_r( $GLOBALS['wp_query'] );
print_r( $GLOBALS['wp_rewrite'] );
exit;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment