ЧПУ для трех таксономий и типа записи одновременно
Очередная заметка с кодом который бесполезен для 99% пользователей WordPress. Но для 1% это будет находкой, пожалуй. Речь о ЧПУ, решение сложной задачи. Объяснений что и как в этой заметке нет, потому что это как объяснять анекдот - либо ты понимаешь, либо нет...
Тот кто ко мне обратился с этой задачей, прежде попробовал несколько популярных плагинов, которые справиться с задачей не смогли: Gd custom tools (платный), Custom Post Type Permalinks (бесплатный). Увидев, что нужно сделать, я не удивился - сложно...
Задача
Создать тип записи и три таксы для него:
realty— Тип записи: объявления.country— Многоуровневая таксономия: страна > город (Испания > Валенсия).type_deal— Одноуровневая таксономия. Содержит только 2 термина: sale и rent (продажа, аренда).type_realty— Многоуровневая таксономия: тип недвижимости (дома).
Создать произвольную структуру ссылок (ЧПУ) для такс и записи.
Для таксономий сделать ссылки вида:
-
dom/ estate / %country% /
-
dom/ estate / %country% / %type_deal% /
- dom/ estate / %country% / %type_deal% / %type_realty% /
Для записи (объявлений):
- dom/ estate / %country% / %type_deal% / %type_realty% / id-%post_id% /
Суть, сделать ЧПУ таксономий и записи полностью похожими. Чтобы можно было вручную убирать из URL последние элементы и попадать на уровень выше.
Примеры того что должно получиться в URL
Для всех трех таксономий:
-
dom/estate/spain/- Недвижимость в Испании -
dom/estate/spain/sale/- Купить недвижимость в Испании -
dom/estate/spain/sale/houses/- Купить дом в Испании -
dom/estate/spain/valensiya/sale/houses/- Купить дом в Испании > Валенсия -
dom/estate/spain/valensiya/sale-rent/- Продажа и аренда всей недвижимости в Валенсии. Может выглядеть какdom/estate/spain/valensiya/ -
dom/estate/spain/valensiya/sale-rent/kommercheskaya/magazin/- Продажа и аренда магазинов в Валенсии
Для каждой ссылки таксономии должна работать пагинация .../page/2/, .../page/3/
Для записи:
Все также как и у термина, только в конце еще ID записи:
dom/estate/spain/valensiya/sale/houses/id-123/. 123 - ID записи
Решение
Создаем файл в теме lib/register-ads-post-tax.php и подключаем его в functions.php темы:
require_once TEMPLATEPATH .'/lib/register-ads-post-tax.php';
Код файла:
<?php
## регистрация типов записей и таксономий
add_action('init', 'estate_type_register');
function estate_type_register(){
// post_type realty
register_post_type('realty', array(
'description' => 'Объявления о продаже недвижимости за рубежом.',
'labels' => array(
'name' => 'Объявления',
'singular_name' => 'Объявление',
'add_new' => 'Добавить новое',
'add_new_item' => 'Добавить объявление',
'edit_item' => 'Изменить объявление',
'new_item' => 'Новое объявление',
'view_item' => 'Посмотреть объявления',
'search_items' => 'Поиск объявлений',
'not_found' => 'Не найдено ни одного объявления',
'not_found_in_trash' => 'Объявлений в корзине не найдено',
'parent_item_colon' => 'Parent Объявление:',
'all_items' => 'Все объявления',
'menu_name' => 'Объявления'
),
'taxonomies' => array( 'country', 'type_realty', 'type_deal' ),
'public' => true,
'query_var' => true,
'rewrite' => false,
//'rewrite' => true,
//'rewrite' => array( 'slug'=>'estate/%country%/%post_id%', 'with_front'=>false, 'feeds'=>false, 'pages'=>false, 'feed'=>false, 'paged'=>false ),
'show_in_menu' => true,
'has_archive' => false,
'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'revisions' ),
'show_ui' => true,
'show_in_nav_menus' => false,
));
## Изменяет ссылку на термины таксономий - 'type_deal' и 'type_realty', которая получается функцией get_term_link()
## К какой стране относится берется из URL текущей страницы
add_filter('term_link', 'deal_realty_tax_link_fix', 10, 3);
function deal_realty_tax_link_fix( $link, $term, $taxonomy ){
if( ! in_array( $taxonomy, array('type_deal','type_realty') ) ) return $link;
$uri = $_SERVER['REQUEST_URI'];
if( $taxonomy == 'type_deal' ){
$link = preg_replace('~^(.*?/)(?:'. implode('|', __type_deal_elements()) .')/.*~', '\1', $uri ) . $term->slug;
}
if( $taxonomy == 'type_realty' ){
$link = preg_replace('~^(.*?/(?:'. implode('|', __type_deal_elements()) .')/).*~', '\1', $uri ) . __build_tax_uri( $term );
}
return esc_url( user_trailingslashit( home_url($link) ) );
}
// фокусы со структурой ссылок
if(1){
//add_action('wp', 'xxxxxxx'); // debug
function xxxxxxx($wp){
print_r( get_queried_object() );
print_r( $wp );
print_r( $GLOBALS['wp_rewrite'] );
exit;
}
// регистрируем структуру через add_permastruct, чтобы работала структура ЧПУ для типа записи
add_permastruct('realty', 'estate/%country%/%type_deal%/%type_realty%/id-%post_id%', array(
'with_front' => 0,
'paged' => 0,
'feed' => 0,
'walk_dirs' => 0,
));
/*
add_filter( 'query_vars', function( $vars ){
$vars[] = 'type_deal';
$vars[] = 'type_realty';
return $vars;
} );
*/
// поправим параметры запроса - удалим 'country' и добавим 'post_type=realty', потому что '?p=208' не работает, а '?p=208&post_type=realty' работает
//$this->query_vars = apply_filters( 'request', $this->query_vars );
add_action('request', 'remove_unwanted_query_vars' );
function remove_unwanted_query_vars( $vars ){
if( !empty($vars['country']) ){
$_country = explode('/', $vars['country']);
$vars['country'] = end( $_country ); // последний элемент
}
if( !empty($vars['type_realty']) ){
$_type_realty = explode('/', $vars['type_realty']);
$vars['type_realty'] = end( $_type_realty ); // последний элемент
}
// запрос типа записи 'realty' - параметры: country, type_realty, type_deal
if( isset($vars['p'], $vars['type_realty']) && $vars['post_type'] === 'realty' ){
$deal = __type_deal_elements();
if( $vars['type_deal'] == end($deal)/*'sale-rent'*/ )
unset( $vars['type_deal'] );
}
return $vars;
}
// добавим правила перезаписи
//add_rewrite_rule('^estate/.+?/(?:sale|rent|sale-rent)/.+?/([0-9]+)/?', 'index.php?p=$matches[1]&post_type=realty', 'top' );
// удалим все созданные правила перезаписи и создам новые...
// $rules = apply_filters( $permastructname . '_rewrite_rules', $rules );
// do_action_ref_array( 'generate_rewrite_rules', array( &$this ) );
add_filter('realty'.'_rewrite_rules', 'delete_realty_rewrite_rules' );
function delete_realty_rewrite_rules( $rules ){
/* array(
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/?$] => index.php?attachment=$matches[1]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/embed/?$] => index.php?attachment=$matches[1]&embed=true
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)/embed/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&embed=true
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)/trackback/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&tb=1
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)(?:/([0-9]+))?/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&page=$matches[5]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/?$] => index.php?attachment=$matches[1]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/embed/?$] => index.php?attachment=$matches[1]&embed=true
) */
// должен быть до правил стран
$rules = array(
'estate/(.+?)/('. implode('|', __type_deal_elements() ) /*sale|rent|sale-rent*/ .')/(.+?)/id-([0-9]+)/?$' => 'index.php?post_type=realty&p=$matches[4]&country=$matches[1]&type_deal=$matches[2]&type_realty=$matches[3]',
);
return $rules;
}
## добавим правила перезаписи для 'country', 'type_deal', 'type_realty'
add_filter('country'.'_rewrite_rules', 'add_more_country_rewrite_rules' );
function add_more_country_rewrite_rules( $rules ){
/*Array(
[estate/(.+?)/page/?([0-9]{1,})/?$] => index.php?country=$matches[1]&paged=$matches[2]
[estate/(.+?)/?$] => index.php?country=$matches[1]
)*/
// должен быть после правил типа запии 'realty'
$_first_part = 'estate/(.+?)/('. implode('|', __type_deal_elements() ) /*sale|rent|sale-rent*/ .')';
$_pade_part = 'page/?([0-9]{1,})';
$more_riles = array(
"$_first_part/(.+?)/$_pade_part/?$" => 'index.php?country=$matches[1]&type_deal=$matches[2]&type_realty=$matches[3]&paged=$matches[4]',
"$_first_part/$_pade_part/?$" => 'index.php?country=$matches[1]&type_deal=$matches[2]&paged=$matches[3]',
"$_first_part/(.+?)/?$" => 'index.php?country=$matches[1]&type_deal=$matches[2]&type_realty=$matches[3]',
"$_first_part/?$" => 'index.php?country=$matches[1]&type_deal=$matches[2]',
);
$rules = array_merge( $more_riles, $rules );
//die( print_r($rules) );
return $rules;
}
// array( sale, rent, sale-rent )
function __type_deal_elements(){
$deal_terms = get_terms(array( 'taxonomy'=>'type_deal','hide_empty'=>0, 'fields'=>'id=>slug' ));
$deal_terms[] = implode('-', $deal_terms); // sale-rent
return $deal_terms;
}
/*
add_action('generate_rewrite_rules', 'delete_ads_rewrite_rules' );
function delete_ads_rewrite_rules( $that ){
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/?$] => index.php?attachment=$matches[1]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/attachment/([^/]+)/embed/?$] => index.php?attachment=$matches[1]&embed=true
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)/embed/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&embed=true
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)/trackback/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&tb=1
[estate/(.+?)/%type_deal%/%type_realty%/([0-9]+)(?:/([0-9]+))?/?$] => index.php?country=$matches[1]&%type_deal%$matches[2]&%type_realty%$matches[3]&p=$matches[4]&page=$matches[5]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/?$] => index.php?attachment=$matches[1]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[estate/.+?/%type_deal%/%type_realty%/[0-9]+/([^/]+)/embed/?$] => index.php?attachment=$matches[1]&embed=true
foreach( $that->extra_rules_top as $rule => $_ ) if( preg_match('~^estate/.+type_deal~', $rule) ) unset($that->extra_rules_top[$rule]);
foreach( $that->rules as $rule => $_ ) if( preg_match('~^estate/.+type_deal~', $rule) ) unset($that->rules[$rule]);
}
*/
// добавим post_type в параметры запроса, потому что просто '?p=208' почему-то не работает, а работает '?p=208&post_type=realty'
//do_action_ref_array( 'parse_request', array( &$this ) );
/*
add_action('parse_request', function($wp){
if( strpos($wp->matched_query, 'foo_') ) $wp->query_vars['post_type'] = 'realty';
} );
// дополнительный тег перезаписи, чтобы не учитывать
add_rewrite_tag('%fake_country%', '(.+?)', '&adsfoovar_='); // adsfoovar_ - несуществующий параметр запроса, он будет удален... он не нужен...
// заменим %realty% (%post_name%) на ID
//global $wp_rewrite;
//$wp_rewrite->extra_permastructs['realty'] = str_replace('%realty%', '%post_id%.html', $wp_rewrite->extra_permastructs['realty']);
// Удалим слэш на конце, если нужно. Если слэш есть в структуре ЧПУ, то он будет добавляться и к постоянным страницам.
//add_filter('user_trailingslashit', 'no_ads_trailslash', 170, 2);
function no_ads_trailslash( $string, $type ){
global $wp_rewrite;
if( $type == 'realty' && $wp_rewrite->use_trailing_slashes && $wp_rewrite->using_permalinks() )
return untrailingslashit($string);
return $string;
}
*/
## Отфильтруем ЧПУ произвольного типа
add_filter('post_type_link', 'realty_permalink', 1, 2);
function realty_permalink( $permalink, $post ){
if( false === strpos($permalink, '%country%') ) return $permalink;
//global $wp_rewrite;
//$perm = $wp_rewrite->get_extra_permastruct('realty');
// Получаем элементы таксы 'country'
$country_path = __build_tax_uri( get_the_terms( $post, 'country') ); // много уровней - asd/asd
// type_realty_path
$type_realty_path = __build_tax_uri( get_the_terms( $post, 'type_realty') ); // много уровней - asd/asd
$type_deal_path = __build_tax_uri( get_the_terms( $post, 'type_deal') ); // один уровень - asd
return strtr( $permalink, array(
'%type_deal%' => $type_deal_path,
'%type_realty%' => $type_realty_path,
'%country%' => $country_path,
'%post_id%' => $post->ID,
) );
}
## Получает цепочку из ярлыков указанного термина - вврехи пока у термина не будет родителей - 'parent/child/child'
function __build_tax_uri( $terms ){
if( is_wp_error($terms) || empty($terms) || ! ( is_object(reset($terms)) || is_object($terms) ) )
return 'no_terms'; // элемента таксы нет, а должен быть...
$term = is_object(reset($terms)) ? reset($terms) : $terms;
$path = array( $term->slug );
while( $term->parent ){
$term = get_term( $term->parent );
$path[] = $term->slug;
}
return implode('/', array_reverse($path) );
}
## 404 страница если в URL указаны неправильные названия таксономий.
## По факту, WP затем 301 редиректит на правильный URL...
add_filter('pre_handle_404', 'realty_404_test' );
function realty_404_test( $false ){
if( ! get_queried_object() ) return $false; // ничего не делает...
$_404 = false;
// запись объявления
if( is_singular('realty') ){
$post = get_queried_object();
// type_deal
if( ! $_404 && ( $term_name = get_query_var('type_deal') ) && ! has_term( explode('-', $term_name), 'type_deal', $post ) )
$_404 = 1;
// type_realty
if( ! $_404 && ( $term_name = get_query_var('type_realty') ) && ! has_term( $term_name, 'type_realty', $post ) )
$_404 = 1;
// country
if( ! $_404 && ( $term_name = get_query_var('country') ) && ! has_term( $term_name, 'country', $post ) )
$_404 = 1;
}
// для такс
if( is_tax(['type_deal','type_realty','country']) ){
// type_deal
if( ! $_404 && ( $term_name = get_query_var('type_deal') ) && ! get_term_by('slug', $term_name, 'type_deal') )
$_404 = 1;
// type_realty
if( ! $_404 && ( $term_name = get_query_var('type_realty') ) && ! get_term_by('slug', $term_name, 'type_realty') )
$_404 = 1;
// country
if( ! $_404 && ( $term_name = get_query_var('country') ) && ! get_term_by('slug', $term_name, 'country') )
$_404 = 1;
}
if( $_404 ){
global $wp_query;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
return 1; // обрываем следующие проверки
}
return $false; // ничего не делает...
}
}
## Такса - страна город регион
register_taxonomy('country', array('realty'), array(
'labels' => array(
'name' => 'Страны',
'singular_name' => 'Страна',
'search_items' => 'Search Страны',
'popular_items' => 'Popular Страны',
'all_items' => 'All Страны',
'parent_item' => 'Parent Страна',
'parent_item_colon' => 'Parent Страна:',
'edit_item' => 'Edit Страна',
'view_item' => 'View Страна',
'update_item' => 'Update Страна',
'add_new_item' => 'Add New Страна',
'new_item_name' => 'New Страна Name',
'separate_items_with_commas' => 'Separate Страны with commas',
'add_or_remove_items' => 'Add or remove Страны',
'choose_from_most_used' => 'Choose from the most used Страны',
'not_found' => 'No Страны found.',
'menu_name' => 'Страны'
),
'hierarchical' => true,
'rewrite' => array('slug'=>'estate', 'hierarchical'=>true, 'with_front'=>0, 'feed'=>0 ),
'query_var' => true,
'public' => true,
'show_ui' => true,
'show_tagcloud' => true,
'show_admin_column' => false,
'show_in_nav_menus' => true,
));
## Такса - тип недвижимости
register_taxonomy( 'type_realty', array('realty'), array(
'labels' => array(
'name' => 'Тип недвижимости',
'singular_name' => 'Тип недвижимости',
'search_items' => 'Search Тип недвижимости',
'popular_items' => 'Popular Тип недвижимости',
'all_items' => 'All Тип недвижимости',
'parent_item' => 'Parent Тип недвижимости',
'parent_item_colon' => 'Parent Тип недвижимости:',
'edit_item' => 'Edit Тип недвижимости',
'view_item' => 'View Тип недвижимости',
'update_item' => 'Update Тип недвижимости',
'add_new_item' => 'Add New Тип недвижимости',
'new_item_name' => 'New Тип недвижимости Name',
'separate_items_with_commas' => 'Separate Тип недвижимости with commas',
'add_or_remove_items' => 'Add or remove Тип недвижимости',
'choose_from_most_used' => 'Choose from the most used Тип недвижимости',
'not_found' => 'No Тип недвижимости found.',
'menu_name' => 'Тип недвижимости'
),
'public' => false,
'rewrite' => false,
'hierarchical' => true,
'query_var' => true, // query_var, чтобы запрос на термин работал...
'publicly_queryable' => true, // query_var, чтобы запрос на термин работал...
'show_ui' => true,
'show_in_quick_edit' => true,
'show_tagcloud' => false,
'show_admin_column' => false,
'show_in_nav_menus' => false,
));
## Такса - тип сделки - два типа - 'sale' и 'rent'
register_taxonomy('type_deal', array('realty'), array(
'labels' => array(
'name' => 'Тип сделки',
'singular_name' => 'Тип сделки',
'search_items' => 'Search Тип сделки',
'popular_items' => 'Popular Тип сделки',
'all_items' => 'All Тип сделки',
'parent_item' => 'Parent Тип сделки',
'parent_item_colon' => 'Parent Тип сделки:',
'edit_item' => 'Edit Тип сделки',
'view_item' => 'View Тип сделки',
'update_item' => 'Update Тип сделки',
'add_new_item' => 'Add New Тип сделки',
'new_item_name' => 'New Тип сделки Name',
'separate_items_with_commas' => 'Separate Тип сделки with commas',
'add_or_remove_items' => 'Add or remove Тип сделки',
'choose_from_most_used' => 'Choose from the most used Тип сделки',
'not_found' => 'No Тип сделки found.',
'menu_name' => 'Тип сделки'
),
'public' => false,
'hierarchical' => true, // для удобности выбора
'query_var' => true, // query_var, чтобы запрос на термин работал...
'publicly_queryable' => true, // query_var, чтобы запрос на термин работал...
'rewrite' => false,
'show_ui' => true,
'show_in_quick_edit' => true,
'show_tagcloud' => false,
'show_in_nav_menus' => false,
'show_admin_column' => false,
));
}
## отменим показ выбранного термина наверху в checkbox списке терминов
add_filter( 'wp_terms_checklist_args', 'set_checked_ontop_default', 10 );
function set_checked_ontop_default( $args ){
// изменим параметр по умолчанию на false
if( ! isset($args['checked_ontop']) ) $args['checked_ontop'] = false;
return $args;
}
## Меняем страницу отдельной страны
add_filter('template_include', 'top_country_tpl_file');
function top_country_tpl_file( $template ) {
// термин страницы верхнего уровня
if( is_tax('country') ){
if( ! get_query_var('type_realty') && ! get_query_var('type_deal') && ($term = get_queried_object()) && ! $term->parent ){
$tpl_file = get_stylesheet_directory() . '/taxonomy-country-toplevel.php';
if( file_exists($tpl_file) ) $template = $tpl_file;
}
}
return $template;
}
// дополнительные хуки для станицы создания объявления
// изменение блока метабокса
if( is_admin() ){
// стили в админке связанные с типом поста quest
add_action('admin_print_footer_scripts', 'country_hook_admin_footer_scripts', 99);
function country_hook_admin_footer_scripts(){
$sc = get_current_screen();
if( $sc->base != 'post' || ! in_array($sc->post_type, ['realty']) )
return;
?>
<style>
.postbox div.tabs-panel{ max-height:700px; border:0; }
.category-tabs{ display:none; }
#countrydiv .inside{ margin:0; padding:0; }
#countrydiv #country-adder{ padding: 0 1em; }
#country-all ul.children{ display:none; }
</style>
<script>
jQuery(document).ready(function($){
var $main = $('#country-all'),
$checklist = $main.find('input[type="checkbox"]');
$checklist.prop('type','radio');
// стрелки ▾ ▸
$main.find('label').each(function(){
var $li = $(this).closest('li')
if( $li.find('> .children').length )
$(this).append('<span class="has_ch" style="opacity:.5;">▸</span>');
});
// развернуть/свернуть
$main.on('click', 'input', function(){
var $li = $(this).closest('li'),
$child = $li.find('> .children');
if( $child.length ){
if( $child.is(':visible') ){
$child.slideUp(200);
$child.prev().find('.has_ch').text('▸');
}
else {
$child.slideDown(200);
$child.prev().find('.has_ch').text('▾');
}
}
});
// покажем выбранный внутренний
var $checked = $checklist.filter(':checked')
if( $checked.length )
$checked.parents('ul.children').show()
});
</script>
<?php
}
}
Объяснять код не буду, долго, сложно, смысла особо нет, потому лень, извините...