ЧПУ для трех таксономий и типа записи одновременно
Очередная заметка с кодом который бесполезен для 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 } }
Объяснять код не буду, долго, сложно, смысла особо нет, потому лень, извините...