Начиная с третьей версии wordpress предоставляет в наше распоряжение такой любопытный инструмент как произвольные типы записей (они же пользовательские, они же таксономии). Да, автор в курсе, что об этом написано уже немало строк в том числе и на русском языке, однако, столкнувшись с таксономиями в реальности, не всегда можно найти подробные примеры их использования (конечно, не считая скудного описания на официальном сайте wordpress). А посему… ну, как обычно, пишу, чтобы не забыть, ибо старые проекты, содержащие весь код, сохраняю редко.

Оглавление

Как создать?

Вообще, (вы удивитесь) wordpress уже содержит несколько примеров типов записей. Таковыми являются:

  • пост (post -> single.php)
  • страница (page -> page.php)
  • вложения они же медиафайлы (attachments)
  • редакции (revisions)
  • элементы меню (nav_menu_item)

Когда вы создаёте свой тип записи, он, как правило, базируется на каком-то уже существующем и наследует поведение встроенного типа. В примере создадим новый тип записи «Новости», взяв за основу обычный пост.

"Wordpress News Example"

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

<?php
/* includes/post-types.php */
/*
** Custom types
** Icons for Type:
** https://developer.wordpress.org/resource/dashicons/
 */

// News
function create_news() {
    register_post_type('news', array(
      'labels' => array(
        'name'            => __( 'News' ),
        'singular_name'   => __( 'News' ),
        'add_new'         => __( 'Add news' ),
        'add_new_item'    => __( 'Add news item' ),
        'edit'            => __( 'Edit news' ),
        'edit_item'       => __( 'Edit news item' ),
        'new_item'        => __( 'Single news' ),
        'all_items'       => __( 'All news' ),
        'view'            => __( 'View news' ),
        'view_item'       => __( 'View single news' ),
        'search_items'    => __( 'Search news' ),
        'not_found'       => __( 'News not found' ),
    ),
    'public' => true, // show in admin panel?
    'menu_position' => 5,
    'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
    'taxonomies' => array( '' ),
    'has_archive' => true,
    'capability_type' => 'post',
    'menu_icon'   => 'dashicons-admin-site',
    'rewrite' => array('slug' => 'news'),
    ));
}
add_action( 'init', 'create_news' );

Что сделали? Зарегистрировали новый тип записи с именем news. В массиве он принимает возможные значения, определяющие то, как будут выводиться названия тех или иных действий при редактировании новостей в административной панели. При этом использовали названия, учитывающие локализацию. Если вы делаете шаблон, не предназначенный для «мультиязычной» аудитории, можно ограничиться наименованиями на русском.

Кроме этого, в админке пункт новостей находится на пятом месте. А сам тип записи поддерживает такие плюшки как заголовок, редактор, возможность прикреплять изображение и выводить краткое описание новости.

Пункт taxonomies предназначен для связывания типа записи с произвольной же таксономией. Но это не является темой данной заметки и будет обсуждаться в будующих пцбликациях.

"Wordpress Custom Post Type"

Следуем дальше: статьи наших новостей имеют архив и собственную иконку, которая будет отображаться для администратора. В самом начале листинга указана страница, где можно посмотреть имеющиеся иконки для wordpress и использовать их, дабы отделить мух от котлет (обычную запись от новости): так удобнее.

Ну, и наконец пути к нашим новостям будут иметь адрес вида mydomain.com/news/my-first-news. Отлично. Инициализируем новый тип записи при помощи add_action и топаем в админку: обновлять роутинг (сохранить изменения), иначе wordpress не сможет найти страницы с news.

"Wordpress Routing"

Кстати, google требует, чтобы новости оканчивались не красивым названием, а ID. Поэтому иногда нужно знать как переопределить стандартные пути к ним. Сделать это можно в functions.php вот таким хитрым образом:

<?php
/* functions.php */
/* Change links for news post type */
function news_permalink($permalink, $post = 0){
    if ( $post->post_type == 'news' ){
        return $permalink .$post->ID .'/';
    } else {
        return $permalink;
    }
}
add_filter('post_type_link', 'news_permalink', 1, 3);

function news_rewrite_init(){
    add_rewrite_rule(
        'news/.+?/([0-9]+)?$',
        'index.php?post_type=news&p=$matches[1]',
        'top'
    );
}
add_action( 'init', 'news_rewrite_init');

Как добавить дополнительную информацию?

Иногда помимо добавления самого типа записи требуется расширить его представление в административной панели. К примеру, надо вывести информацию об оригинальном источнике новости. В таком случае придётся обратиться к неким мистическим сущностям wordpress, название которых слабо переводится на русский, (и потому нет желания его коверкать, придумывая неологизмы) это metaboxes.

<?php
/* includes/metaboxes.php */
function links_custom_meta() {
    add_meta_box( 'links_meta',
                  __('Source'),
                  'links_meta_callback',
                  'news',
                  'advanced',
                  'default' );
}
add_action('add_meta_boxes', 'links_custom_meta');

Что всё это значит:

  • links_meta -> идентификатор
  • Source -> название блока
  • links_meta_callback -> обратный вызов
  • news -> к какому типу записи применить
  • advanced/normal -> в каком месте вывести
  • default/height -> приоритет в контексте других блоков

Какие поля будет включать блок? Обратимся к функции обратного вызова.

<?php
function links_meta_callback( $post ) {
    echo _e('<p>Original news link:</p>');
    wp_nonce_field( basename(__FILE__), 'links_nonce');
    $links_stored_meta = get_post_meta( $post->ID );
    ?>
    <p>
        <label for="links-news-original" class="links-row-title">
            <?php _e( '<i>Site:</i><br>', 'links-textdomain' )?>
        </label><br>
        <input type="text" name="links-news-original" id="links-news-original" value="<?php if ( isset ( $links_stored_meta['links-news-original'] ) ) echo $links_stored_meta['links-news-original'][0]; ?>" />
    </p>
<?php
}

Проиллюстрируем сказанное:

"Wordpress Metaboxes"

Наконец, научим metabox запоминать ту информацию, которая была записана в поле.

<?php
/**
 * Saves the custom meta input
 */
function links_meta_save( $post_id ) {

    // Checks save status
    $is_autosave = wp_is_post_autosave( $post_id );
    $is_revision = wp_is_post_revision( $post_id );
    $is_valid_nonce = ( isset( $_POST[ 'links_nonce' ] ) && wp_verify_nonce( $_POST[ 'links_nonce' ], basename( __FILE__ ) ) ) ? 'true' : 'false';

    // Exits script depending on save status
    if ( $is_autosave || $is_revision || !$is_valid_nonce ) {
        return;
    }

    // Checks for input and sanitizes/saves if needed
    if( isset( $_POST[ 'links-news-original' ] ) ) {
        update_post_meta( $post_id, 'links-news-original', sanitize_text_field( $_POST[ 'links-news-original' ] ) );
    }
}
add_action( 'save_post', 'links_meta_save' );

Как вывести на странице?

Есть два варианта: сделать шаблон по правилам wordpress или же вывести на странице с шаблоном собственным.

Для шаблона, который будет выводить каждую отдельную запись, создадим страницу с именем single-news.php и примерно таким содержанием:

<?php get_header(); ?>
<?php
// Retrieves the stored value from the database
$meta_url = get_post_meta( get_the_ID(), 'links-news-original', true );
?>
<main>
  <article>
    <?php the_post(); ?>
    <?php the_title(); ?>
    <?php the_content(); ?>
    <div class="copyright-news">
        <?php if( !empty( $meta_url ) ) {
            echo 'По материалам: <a href="'.$meta_url.'" target="_blank">'.$meta_url.'</a>';
        } ?>
    </div>
  </article>
</main>
<?php get_footer(); ?>

Может так случиться, что для особого типа записи надо будет выводить свой шаблон. Зачем? А там другое оформление или даже разметка. Тогда записываем ещё одну вещь: проверим тип записи, и если он совпадает с нужным, пусть движок использует страницу, отличную от стандартной. Сделаем это для страницы архива, которая не воспринимает указание шаблона а-ля archive-news.

<?php
// Custom News Archive
function template_news($template) {
  global $wp_query;
  $post_type = get_query_var('post_type');
  if( $wp_query->is_search && $post_type == 'news') {
    return locate_template('archive-news');
  }
  return $template;
}
add_filter('template_include', 'template_news');

Записи в таком шаблоне выводятся как обычно, без дополнительных запросов с wp_query. ⤧  Следующая запись Адаптивное меню на CSS