# SEF URL: Дружественные адреса для двухуровневой таксономии ## Проблема (Задача): У нас есть: - **компания** — тип записи Flat. - **каталог** — иерархическая таксономия. - **локация** — иерархическая таксономия. Нам нужны постоянные ссылки (пермалинки): Все они должны начинаться с
— пост. * -/company/
. Мы будем объединять запросы для двух таксономий, с поддержкой пагинации. ### Примеры: -/company/bmw7/
— пост. -/company/almaty/
— архив термина локации (уровень 1). -/company/almaty/auezo/
— архив термина локации (уровень 2). -/company/almaty/auezo/three/
— архив термина локации (уровень 3). -/company/avto/
— архив термина каталога (уровень 1). -/company/avto/light/
— архив термина каталога (уровень 2). -/company/avto/light/lyuks/
— архив термина каталога (уровень 3). -/company/avto/almaty/
— архив терминов каталога и локации (уровни 1). -/company/avto/almaty/auezo/
— архив терминов каталога и локации (уровни 1 + 2). -/company/avto/light/almaty/auezo/three/
— архив терминов каталога и локации (уровни 2 + 3). ### Все эти ссылки должны поддерживать пагинацию: -/company/almaty/page/2/
— вторая страница пагинации для локации. -/company/avto/light/almaty/auezo/three/page/2/
— вторая страница для каталога и локации. ## Код, который решает эту задачу: ```php /company/bmw7//company/almaty/
— архив по локации (уровень 1). * -/company/avto/
— архив по каталогу (уровень 1). * * Дополнительные постоянные ссылки для постов: * -/site.kz/company/bmw7/reviews/
— пост с обзорами. * -/site.kz/company/bmw7/gallery/
— пост с галереей. * -/site.kz/company/bmw7/edit/
— редактирование поста. * -/site.kz/company/bmw7/price/
— цена поста. * * @version 1.4 * @autor 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']); } 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' => 'Каталог', 'singular_name' => 'Каталог', ], 'public' => true, 'hierarchical' => true, 'rewrite' => false, 'show_admin_column' => false, ]); // Регистрация таксономии для локации register_taxonomy(self::$location_tax, [self::$post_type], [ 'label' => '', 'labels' => [ 'name' => 'Локация', 'singular_name' => 'Локация', ], 'public' => true, 'hierarchical' => true, 'rewrite' => false, 'show_admin_column' => false, ]); // Регистрация типа записи для компаний register_post_type(self::$post_type, [ 'label' => null, 'labels' => [ 'name' => 'Компании', 'singular_name' => 'Компания', ], 'public' => true, 'hierarchical' => false, 'supports' => ['title', 'editor'], '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){ // Обработка запроса для компании 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]); }; // Проверка на наличие термина локации if(false !== strpos($location_top, "|$name|")){ $unset_vars__fn($vars); // очистка $vars[self::$location_tax] = $name; } // Проверка на наличие термина каталога elseif($term = get_term_by('slug', $name, self::$catalog_tax)){ $unset_vars__fn($vars); // очистка $vars[self::$catalog_tax] = $name; } } // Обработка специального запроса elseif(!empty($vars['catalog_location'])){ $parts = explode('/', $vars['catalog_location']); // Может быть пост с подстраницей 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]; } // Обработка запроса для каталога и локации 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); // Проверка надежности URL для переадресации $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) : ''; // Обработка двух таксономий if($catalog_slug && $location_slug){ // Переадресация на правильный 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; } // Обработка запроса только для каталога elseif($catalog_slug){ // Переадресация на правильный 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; } // Обработка запроса только для локации elseif($location_slug){ // Переадресация на правильный 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_rules = [ "$_first_part/$_page_part/?$" => 'index.php?catalog_location=$matches[1]&paged=$matches[2]', "$_first_part/?$" => 'index.php?catalog_location=$matches[1]', ]; // Удаление конфликтующих правил foreach($rules as $regex => $rule){ if(false === strpos($regex, '/attachment/') && false !== strpos($rule, 'attachment=')) unset($rules[$regex]); } // Добавление новых правил $rules += $more_rules; 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; } /** * Получает родительские термины (включая текущий). * * @param WP_Term|string $term * @param string $taxonomy Если термин передан как строка. * * @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); } /** * Создает часть URL для термина указанной таксономии. Пример: 'parent/child/childchild' * * @param WP_Term|string $term * @param string $taxonomy Если термин передан как строка. * * @return string */ static function _build_tax_uri($term, $taxonomy = null){ $slugs = wp_list_pluck(self::parent_terms($term, $taxonomy), 'slug'); return implode('/', $slugs); } /** * Получает верхние уровни слогов таксономии локации как массив. Кэширует результат. * * @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; } }
Объяснение кода
Этот код создает специальный класс для управления постоянными ссылками в WordPress, используя две иерархические таксономии: каталог и локация. Класс обрабатывает регистрацию постов и таксономий, создание правильных URL и поддержку пагинации.
Важные классы и методы:
register_post_types()
— регистрирует новый тип записи и таксономии.fix_request()
— обрабатывает запросы и определяет, какой контент должен быть загружен.post_rewrite_rules()
— определяет правила для формата URL.
Этот код подходит для разработчиков, которые хотят создавать более удобные адреса для сторонних клиентов, давая им возможность легко навигировать по ресурсам сайта.