Статьи

Добавить Архив Типа Связи в Ваше Меню

Обычный запрос, особенно для тех, кто создал пользовательские типы сообщений, такие как «Новости» или «События», заключается в добавлении ссылки на страницу архива их типа сообщений в их меню навигации. Однако в настоящее время это можно сделать только вручную, введя URL-адрес архива типа записи. Помимо того, что это довольно не элегантное решение, у этого решения есть несколько недостатков: оно не всегда отображается как «текущее», если вы измените структуру постоянной ссылки, это может привести к разрыву ссылки, ручное добавление URL-адресов утомительно, а ссылка не отображается как « current ‘когда на пост такого типа.

В этом уроке я покажу вам, как создать плагин, который создает мета-поле на вашей странице «Внешний вид» -> «Меню», которое позволяет вам добавлять ссылки на архивы пост-типа. Эти ссылки не страдают от недостатков, упомянутых выше.


Этот плагин будет называться «Мои архивные ссылки типа поста », и для этого сначала создайте папку my-post-type-archive-links в папке / wp-content / plugins / , а внутри создайте файл my- post-type-archive-links.php . Этот файл является основным файлом плагина. Мы собираемся обернуть его в класс — это просто, чтобы нам не приходилось беспокоиться о конфликтах имен наших функций с WordPress или другими плагинами: нам просто нужно убедиться, что имя нашего класса уникально. Добавьте следующее в my-post-type-archive-links.php

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<?php
/*
Plugin Name: My Post Type Archive Links
Version: 1.0
Description: Adds a metabox to the Appearance -> Menu page to add post type archive links
Author: Stephen Harris
Author URI: http://profiles.wordpress.org/users/stephenh1988/
*/
 
class My_Post_Type_Archive_Link {
    //Everything will go here
}
My_Post_Type_Archive_Link::load();
?>

Все в этом уроке будет находиться внутри этого класса.


Когда файл плагина загружается, он запускает метод класса load() . Этот метод будет отвечать за добавление действий и фильтров на различные хуки WordPress. Мы рассмотрим каждый из них на последующих шагах, но он также предоставляет полезную сводку. Добавьте следующий метод в наш класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public function load(){
    // Hook function to add the metabox to the Menu page
    add_action( ‘admin_init’, array(__CLASS__,’add_meta_box’));
 
    // Javascript for the meta box
    add_action( ‘admin_enqueue_scripts’, array(__CLASS__,’metabox_script’) );
 
    // Ajax callback to create menu item and add it to menu
    add_action(‘wp_ajax_my-add-post-type-archive-links’, array( __CLASS__, ‘ajax_add_post_type’));
 
    // Assign menu item the appropriate url
    add_filter( ‘wp_setup_nav_menu_item’, array(__CLASS__,’setup_archive_item’) );
 
    // Make post type archive link ‘current’
    add_filter( ‘wp_nav_menu_objects’, array(__CLASS__,’maybe_make_current’));
}

Давайте подведем итог, что делает каждая из этих частей:

  1. Добавьте метабокс — достаточно понятный. Подключенная функция будет отвечать за добавление нашего мета-блока.
  2. Ставить JavaScript в очередь — мы используем хук admin_enqueue_scripts, чтобы поставить в очередь наш файл JavaScript. Наш JavaScript, когда нажимают «добавить в меню», вызывает запрос AJAX.
  3. AJAX callback — эта функция отвечает за обработку вышеуказанного AJAX-запроса. Это создаст пункты меню и добавит их в меню.
  4. Настройка пункта меню — это гарантирует, что когда ссылка на архив появится в вашем меню, она правильно указывает на тип архива.
  5. Может быть, сделать текущий — всякий раз, когда появляется меню, его элементы пропускаются через фильтр, мы гарантируем, что класс ‘ current-menu-item ‘ будет добавлен к соответствующей ссылке типа сообщения.

Сначала мы определяем наш метод add_meta_box , который просто вызывает функцию WordPress add_meta_box() . Детали этой функции были рассмотрены много раз прежде, но если вы не уверены, вы можете прочитать об этом на страницах Кодекса .

1
2
3
public function add_meta_box() {
    add_meta_box( ‘post-type-archives’, __(‘Post Types’,’my-post-type-archive-links’),array(__CLASS__,’metabox’),’nav-menus’ ,’side’,’low’);
}

Далее мы определяем функцию обратного вызова метабокса, которая отвечает за отображение внутренних частей метабокса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public function metabox( ) {
    global $nav_menu_selected_id;
 
    //Get post types
    $post_types = get_post_types(array(‘public’=>true,’_builtin’=>false), ‘object’);?>
 
    <!— Post type checkbox list —>
    <ul id=»post-type-archive-checklist»>
    <?php foreach ($post_types as $type):?>
        <li><label><input type=»checkbox» value =»<?php echo esc_attr($type->name); ?>» /> <?php echo esc_attr($type->labels->name);
    <?php endforeach;?>
    </ul><!— /#post-type-archive-checklist —>
 
    <!— ‘Add to Menu’ button —>
    <p class=»button-controls» >
        <span class=»add-to-menu» >
            <input type=»submit» id=»submit-post-type-archives» <?php disabled( $nav_menu_selected_id, 0 );
        
    </p>
<?php
}

Этот метод просто получает все открытые пользовательские типы get_post_types() с помощью get_post_types() а затем перебирает их, чтобы создать список флажков. Каждый флажок имеет имя типа сообщения в качестве значения. На следующем шаге мы добавим JavaScript, который будет срабатывать, когда пользователь нажимает кнопку «Добавить в меню».


Мы только хотим включить наш JavaScript на странице Внешний вид -> Меню администратора. Мы использовали хук admin_enqueue_scripts который срабатывает только на страницах администратора и передает хук страницы в качестве аргумента. Хук для страницы Внешний вид -> Меню — nav-menus.php . После постановки в очередь нашего скрипта мы используем wp_localize_script чтобы сделать одноразовый номер доступным в нашем JavaScript. Мы включили его в запрос AJAX, чтобы убедиться, что действие было запланировано.

01
02
03
04
05
06
07
08
09
10
public function metabox_script($hook) {
    if( ‘nav-menus.php’ != $hook )
        return;
 
    //On Appearance>Menu page, enqueue script:
    wp_enqueue_script( ‘my-post-type-archive-links_metabox’, plugins_url(‘/metabox.js’, __FILE__), array(‘jquery’));
 
    //Add nonce variable
    wp_localize_script(‘my-post-type-archive-links_metabox’,’MyPostTypeArchiveLinks’, array(‘nonce’=>wp_create_nonce(‘my-add-post-type-archive-links’)));
}

На предыдущем шаге кнопка «Добавить в меню» получила идентификатор submit-post-type-archives . Теперь мы используем jQuery для нацеливания на эту кнопку и, при нажатии, отправляем запрос AJAX, чтобы создать пункт меню и добавить его в меню. Следующее — единственная часть этого урока, которая живет за пределами нашего класса. Он должен находиться в файле с именем metabox.js , в нашей папке плагинов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
jQuery(document).ready(function($) {
    $(‘#submit-post-type-archives’).click(function(event) {
        event.preventDefault();
 
        /* Get checked boxes */
        var postTypes = [];
        $(‘#post-type-archive-checklist li :checked’).each(function() {
            postTypes.push($(this).val());
        });
 
        /* Send checked post types with our action, and nonce */
        $.post( ajaxurl, {
                action: «my-add-post-type-archive-links»,
                posttypearchive_nonce: MyPostTypeArchiveLinks.nonce,
                post_types: postTypes
            },
 
            /* AJAX returns html to add to the menu */
            function( response ) {
                $(‘#menu-to-edit’).append(response);
            }
        );
    })
});

Обратите внимание на адрес, по ajaxurl мы отправляем запрос: ajaxurl . Мы не определили это нигде. Это глобальная переменная, установленная WordPress только на стороне администратора, которая указывает на страницу, которую WordPress использует для обработки запросов AJAX. При нажатии кнопки отправки на этот URL-адрес отправляются имена проверенных типов сообщений, уникальных действий и одноразовых номеров. Когда WordPress получает запрос, он запускает wp_ajax_my-add-post-type-archive-links . Одноразовый номер является мерой безопасности, помогающей убедиться, что действие было запланировано.


Теперь мы определим функцию обратного вызова AJAX ajax_add_post_type .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public function ajax_add_post_type() {
 
    if ( ! current_user_can( ‘edit_theme_options’ ) )
        die(‘-1’);
 
    check_ajax_referer(‘my-add-post-type-archive-links’, ‘posttypearchive_nonce’);
 
    require_once ABSPATH .
 
    if(empty($_POST[‘post_types’]))
        exit;
 
    // Create menu items and store IDs in array
    $item_ids=array();
    foreach ( (array) $_POST[‘post_types’] as $post_type) {
        $post_type_obj = get_post_type_object($post_type);
 
        if(!$post_type_obj)
            continue;
 
        $menu_item_data= array(
            ‘menu-item-title’ => esc_attr($post_type_obj->labels->name),
            ‘menu-item-type’ => ‘post_type_archive’,
            ‘menu-item-object’ => esc_attr($post_type),
            ‘menu-item-url’ => get_post_type_archive_link($post_type)
        );
 
        //Collect the items’ IDs.
        $item_ids[] = wp_update_nav_menu_item(0, 0, $menu_item_data );
    }
 
    // If there was an error die here
    if ( is_wp_error( $item_ids ) )
        die(‘-1’);
 
    // Set up menu items
    foreach ( (array) $item_ids as $menu_item_id ) {
        $menu_obj = get_post( $menu_item_id );
        if ( ! empty( $menu_obj->ID ) ) {
            $menu_obj = wp_setup_nav_menu_item( $menu_obj );
            $menu_obj->label = $menu_obj->title;
            $menu_items[] = $menu_obj;
        }
    }
 
    // This gets the HTML to returns it to the menu
    if ( ! empty( $menu_items ) ) {
        $args = array(
            ‘after’ => »,
            ‘before’ => »,
            ‘link_after’ => »,
            ‘link_before’ => »,
            ‘walker’ => new Walker_Nav_Menu_Edit
        );
        echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
    }
 
    // Finally don’t forget to exit
    exit;
}

Давайте пройдем этот обратный вызов немного за один раз. Сначала мы проверяем права пользователя, проверяем одноразовый номер и загружаем страницу nav-menu.php (нам нужны некоторые функции).

1
2
3
4
5
6
7
8
9
if ( ! current_user_can( ‘edit_theme_options’ ) )
    die(‘-1’);
 
check_ajax_referer(‘my-add-post-type-archive-links’,’posttypearchive_nonce’);
 
require_once ABSPATH .
 
if(empty($_POST[‘post_types’]))
    exit;

Затем мы создаем пункт меню для каждого выбранного типа записи. Сначала мы проверяем, существует ли тип записи, который мы получили, проверяя значение, возвращаемое get_post_type_object() . Мы можем получить get_post_type_archive_link() ссылку с помощью функции get_post_type_archive_link()

Пункты меню на самом деле представляют собой посты типа post ‘ nav_menu_item ‘ со встроенной мета-публикацией, включая поля, относящиеся к ‘ url ‘, ‘ type ‘ и ‘ object ‘. Тип элемента обычно равен custom , post_type или taxonomy , но мы установим его значение post_type_archive . Мета-значение элемента ‘ object ‘ обычно используется только для элементов типа ‘ post_type ‘ или ‘ taxonomy ‘ и относится к типу записи или таксономии, на которую ссылается ссылка. Мы будем использовать это для хранения типа записи ссылки на архив.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// Create menu items and store IDs in array
$item_ids=array();
foreach ( (array) $_POST[‘post_types’] as $post_type) {
    $post_type_obj = get_post_type_object($post_type);
 
    if(!$post_type_obj)
        continue;
 
    $menu_item_data= array(
        ‘menu-item-title’ => esc_attr($post_type_obj->labels->name),
        ‘menu-item-type’ => ‘post_type_archive’,
        ‘menu-item-object’ => esc_attr($post_type),
        ‘menu-item-url’ => get_post_type_archive_link($post_type)
    );
 
    // Collect the items’ IDs.
    $item_ids[] = wp_update_nav_menu_item(0, 0, $menu_item_data );
}
 
// If there was an error die here
if ( is_wp_error( $item_ids ) )
    die(‘-1’);

Далее мы просто генерируем HTML, который будет добавлен в меню. Мы используем массив $item_ids чтобы получить массив пунктов меню и передать его классу WordPress, чтобы выполнить тяжелую работу за нас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Set up menu items
foreach ( (array) $item_ids as $menu_item_id ) {
    $menu_obj = get_post( $menu_item_id );
    if ( ! empty( $menu_obj->ID ) ) {
        $menu_obj = wp_setup_nav_menu_item( $menu_obj );
        $menu_obj->label = $menu_obj->title;
        $menu_items[] = $menu_obj;
    }
}
 
//This gets the HTML to returns it to the menu
if ( ! empty( $menu_items ) ) {
    $args = array(
        ‘after’ => »,
        ‘before’ => »,
        ‘link_after’ => »,
        ‘link_before’ => »,
        ‘walker’ => new Walker_Nav_Menu_Edit
    );
    echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
}
 
//Finally don’t forget to exit
exit;

К сожалению, из-за ошибки в WordPress, если тип вашего элемента не « taxonomy », « custom » или « post_type », URL удаляется. Чтобы противостоять этому, когда в меню используется ссылка « post_type_archive », мы вручную повторно добавляем URL. Это также обеспечивает актуальность ссылки на архив (в случае, если ваша структура постоянных ссылок была изменена).

1
2
3
4
5
6
7
8
9
public function setup_archive_item($menu_item){
    if($menu_item->type !=’post_type_archive’)
        return $menu_item;
 
    $post_type = $menu_item->object;
    $menu_item->url =get_post_type_archive_link($post_type);
 
    return $menu_item;
}

Наконец, нам нужно сделать элемент «текущим», когда мы находимся на соответствующей странице. Я хочу, чтобы ссылка на архив типа записи была выделена как текущая, если мы находимся на этой странице архива или просматриваем один пост такого типа. Для этого я проверяю:

Чтобы сделать элемент текущим, нам просто нужно добавить current-menu-item к классам элемента, которые хранятся в $item->classes . Затем мы должны перебрать его родителей в меню и добавить классы current_item_parent и current_item_ancestor . Давайте посмотрим на каждый бит в отдельности:

Мы перебираем все пункты меню:

1
2
3
4
5
6
public function maybe_make_current($items) {
    foreach ($items as $item) {
        // This is where we check the item
    }
    return $items;
}

Если элемент не относится к « post_type_archive » или если он есть, но мы не хотим, чтобы он стал «текущим», мы просто переходим к следующему элементу. Напомним, что для наших архивных ссылок тип записи сохраняется как объект элемента. Итак, внутри цикла foreach :

1
2
3
4
5
6
if(‘post_type_archive’ != $item->type)
    continue;
 
$post_type = $item->object;
if(!is_post_type_archive($post_type)&& !is_singular($post_type))
    continue;

Если мы хотим сделать это текущим, мы даем ему соответствующий класс и затем выбираем его родителей в меню. Родитель пункта меню сохраняется как мета-пост с мета-ключом _menu_item_menu_item_parent .

01
02
03
04
05
06
07
08
09
10
11
//Make item current
$item->current = true;
$item->classes[] = ‘current-menu-item’;
 
//Get menu item’s ancestors:
$_anc_id = (int) $item->db_id;
$active_ancestor_item_ids=array();
 
while(( $_anc_id = get_post_meta( $_anc_id, ‘_menu_item_menu_item_parent’, true ) ) && ! in_array( $_anc_id, $active_ancestor_item_ids ) ) {
    $active_ancestor_item_ids[] = $_anc_id;
}

Затем мы перебираем пункты меню и даем родителям и предкам «текущего» класса соответствующие классы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// Loop through the items and give ancestors and parents the appropriate class
foreach ($items as $key=>$parent_item) {
    $classes = (array) $parent_item->classes;
 
    // If menu item is the parent
    if ($parent_item->db_id == $item->menu_item_parent ) {
        $classes[] = ‘current-menu-parent’;
        $items[$key]->current_item_parent = true;
    }
 
    // If menu item is an ancestor
    if ( in_array( intval( $parent_item->db_id ), $active_ancestor_item_ids ) ) {
        $classes[] = ‘current-menu-ancestor’;
        $items[$key]->current_item_ancestor = true;
    }
 
    $items[$key]->classes = array_unique( $classes );
}

Соединяем эту функцию вместе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public function maybe_make_current($items) {
    foreach ($items as $item) {
        if(‘post_type_archive’ != $item->type)
            continue;
 
        $post_type = $item->object;
        if(!is_post_type_archive($post_type)&& !is_singular($post_type))
            continue;
 
        // Make item current
        $item->current = true;
        $item->classes[] = ‘current-menu-item’;
 
        // Get menu item’s ancestors:
        $_anc_id = (int) $item->db_id;
        $active_ancestor_item_ids=array();
 
        while(( $_anc_id = get_post_meta( $_anc_id, ‘_menu_item_menu_item_parent’, true ) ) && ! in_array( $_anc_id, $active_ancestor_item_ids ) ) {
            $active_ancestor_item_ids[] = $_anc_id;
        }
 
        // Loop through ancestors and give them ‘ancestor’ or ‘parent’ class
        foreach ($items as $key=>$parent_item) {
            $classes = (array) $parent_item->classes;
 
            // If menu item is the parent
            if ($parent_item->db_id == $item->menu_item_parent ) {
                $classes[] = ‘current-menu-parent’;
                $items[$key]->current_item_parent = true;
            }
 
            // If menu item is an ancestor
            if ( in_array( intval( $parent_item->db_id ), $active_ancestor_item_ids ) ) {
                $classes[] = ‘current-menu-ancestor’;
                $items[$key]->current_item_ancestor = true;
            }
 
            $items[$key]->classes = array_unique( $classes );
        }
 
    }
    return $items;
}

Осталось только зайти на страницу администрирования плагинов и активировать плагин.


Всегда есть возможности для улучшения. Например, с небольшим количеством jQuery вы можете добавить ссылку «Выбрать все» под флажками или отобразить символ «загрузки» во время обработки AJAX. Теперь этот плагин не самое простое решение — но он работает хорошо и избегает ловушек простого добавления пользовательской ссылки. Вышеприведенный плагин целиком можно найти на моем GitHub . Если у вас есть какие-либо комментарии или предложения, не стесняйтесь оставлять комментарии или связываться со мной через Twitter .