Создание пользовательской страницы архива для произвольного типа записи в плагине WordPress
Я разрабатываю плагин, который создает произвольный тип записи "my_plugin_lesson":
$args = array (
'public' => true,
'has_archive' => true,
'rewrite' => array('slug' => 'lessons', 'with_front' => false)
);
register_post_type ('my_plugin_lesson', $args);
У этого типа записи есть архив, и его URL выглядит так:
http://example.com/lessons
Я хочу кастомизировать внешний вид этого архива - выводить записи в табличном формате вместо стандартного архива WordPress. Я знаю, что можно создать кастомный шаблон архива в теме через файл archive-my_plugin_lesson.php
, но мне нужно, чтобы плагин работал с любой темой.
Как я могу изменить содержимое архивной страницы без добавления или модификации файлов темы?
Дополнение:
Я понимаю, что можно использовать хук archive_template
. Однако это просто заменяет шаблон темы, который все равно должен быть специфичным для каждой темы. Например, практически каждому шаблону темы нужны функции get_header
, get_sidebar
и get_footer
, но каким должен быть id контентного <div>
? В каждой теме он разный.
Что я хочу сделать - это заменить само содержимое своим собственным контентом и использовать его вместо стандартной архивной страницы для моего типа записи.

Вам необходимо подключить фильтр template_include
и выборочно загружать ваш шаблон внутри плагина.
Как хорошая практика, если вы планируете распространять ваш плагин, следует проверять существует ли archive-my_plugin_lesson.php
(или, возможно, myplugin/archive-lesson.php
) в теме, и если нет - использовать версию из плагина.
Таким образом пользователи смогут легко заменить шаблон через тему (или дочернюю тему), не редактируя код плагина.
Этот метод используется популярными плагинами, например WooCommerce, если называть один из них.
add_filter('template_include', 'lessons_template');
function lessons_template( $template ) {
if ( is_post_type_archive('my_plugin_lesson') ) {
$theme_files = array('archive-my_plugin_lesson.php', 'myplugin/archive-lesson.php');
$exists_in_theme = locate_template($theme_files, false);
if ( $exists_in_theme != '' ) {
return $exists_in_theme;
} else {
return plugin_dir_path(__FILE__) . 'archive-lesson.php';
}
}
return $template;
}
Больше информации в Кодексе:

Это всё ещё просто заменяет файл шаблона темы, верно? Что мне нужно поместить в файл archive-lesson.php моего плагина? Он должен быть разным, чтобы работать с каждой темой. Даже стандартные темы "Twenty" не согласованы в том, какие div/section контейнеры окружают контент.

Вы можете использовать хук archive_template
для обработки содержимого шаблона архива темы, используя схему, приведенную ниже. Однако учтите, что вы сможете обработать лишь небольшую часть существующих тем, так как шаблон может содержать практически любые элементы.
Схема заключается в загрузке шаблона в строку ($tpl_str
) в фильтре archive_template
, замене вашего контента, включении строки (с использованием трюка eval( '?>' . $tpl_str );
), а затем возврате пустого файла, чтобы include
в "wp-includes/template-loader.php" стал пустой операцией.
Ниже представлена модифицированная версия кода, который я использую в плагине. Он ориентирован на "классические" шаблоны, использующие get_template_part
, и больше предназначен для обработки одиночных шаблонов, чем архивов, но поможет вам начать. В настройке плагин имеет подкаталог "templates", который содержит пустой файл ("null.php") и шаблоны контента (например, "content-single-posttype1.php", "content-archive-postype1.php"), а также резервный шаблон "single.php" для одиночного случая. Используется пользовательская версия get_template_part
, которая ищет файлы в этом каталоге.
define( 'MYPLUGIN_FOLDER', dirname( __FILE__ ) . '/' );
define( 'MYPLUGIN_BASENAME', basename( MYPLUGIN_FOLDER ) );
add_filter( 'single_template', 'myplugin_single_template' );
add_filter( 'archive_template', 'myplugin_archive_template' );
function myplugin_single_template( $template ) {
static $using_null = array();
// Укажите свои пользовательские типы записей.
$post_types = array( 'posttype1', );
if ( is_single() || is_archive() ) {
$template_basename = basename( $template );
// Эту проверку можно убрать.
if ( $template == '' || substr( $template_basename, 0, 4 ) == 'sing' || substr( $template_basename, 0, 4 ) == 'arch' ) {
$post_type = get_post_type();
$slug = is_archive() ? 'archive' : 'single';
if ( in_array( $post_type, $post_types ) ) {
// Позволяет пользователю переопределить.
if ( $single_template = myplugin_get_template( $slug, $post_type ) ) {
$template = $single_template;
} else {
// Если ещё не проходили через всё это...
if ( empty( $using_null[$slug][$post_type] ) ) {
if ( $template && ( $content_template = myplugin_get_template( 'content-' . $slug, $post_type ) ) ) {
$tpl_str = file_get_contents( $template );
// Вам придётся адаптировать эти регулярные выражения под свой случай — удачи!
if ( preg_match( '/get_template_part\s*\(\s*\'content\'\s*,\s*\'' . $slug . '\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
|| preg_match( '/get_template_part\s*\(\s*\'content\'\s*,\s*get_post_format\s*\(\s*\)\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
|| preg_match( '/get_template_part\s*\(\s*\'content\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE )
|| preg_match( '/get_template_part\s*\(\s*\'[^\']+\'\s*,\s*\'' . $slug . '\'\s*\)/', $tpl_str, $matches, PREG_OFFSET_CAPTURE ) ) {
$using_null[$slug][$post_type] = true;
$tpl_str = substr( $tpl_str, 0, $matches[0][1] ) . 'include \'' . $content_template . '\'' . substr( $tpl_str, $matches[0][1] + strlen( $matches[0][0] ) );
// Этот трюк включает строку $tpl_str.
eval( '?>' . $tpl_str );
}
}
}
if ( empty( $using_null[$slug][$post_type] ) ) {
// Не удалось разобрать — ищем резервный шаблон.
if ( file_exists( MYPLUGIN_FOLDER . 'templates/' . $slug . '.php' ) ) {
$template = MYPLUGIN_FOLDER . 'templates/' . $slug . '.php';
}
} else {
// Успех! "null.php" — это просто пустой файл без содержимого.
$template = MYPLUGIN_FOLDER . 'templates/null.php';
}
}
}
}
}
return $template;
}
function myplugin_archive_template( $template ) {
return myplugin_single_template( $template );
}
Пользовательская версия get_template_part
:
/*
* Версия WP get_template_part(), которая ищет в теме, затем в родительской теме и, наконец, в каталоге шаблонов плагина (подкаталог "templates").
* Также изначально ищет в подкаталоге "myplugin" в темах и родительских темах, чтобы шаблоны плагина можно было хранить отдельно.
*/
function myplugin_get_template( $slug, $part = '' ) {
$template = $slug . ( $part ? '-' . $part : '' ) . '.php';
$dirs = array();
if ( is_child_theme() ) {
$child_dir = get_stylesheet_directory() . '/';
$dirs[] = $child_dir . MYPLUGIN_BASENAME . '/';
$dirs[] = $child_dir;
}
$template_dir = get_template_directory() . '/';
$dirs[] = $template_dir . MYPLUGIN_BASENAME . '/';
$dirs[] = $template_dir;
$dirs[] = MYPLUGIN_FOLDER . 'templates/';
foreach ( $dirs as $dir ) {
if ( file_exists( $dir . $template ) ) {
return $dir . $template;
}
}
return false;
}
Для полноты приведён резервный "single.php", который использует пользовательскую версию get_template_part
:
<?php
get_header(); ?>
<div id="primary" class="content-area">
<div id="content" class="clearfix">
<?php while ( have_posts() ) : the_post(); ?>
<?php if ( $template = myplugin_get_template( 'content-single', get_post_type() ) ) include $template; else get_template_part( 'content', 'single' ); ?>
<?php
// Если комментарии открыты или их хотя бы один, загружаем шаблон комментариев
if ( comments_open() || '0' != get_comments_number() ) :
comments_template();
endif;
?>
<?php endwhile; ?>
</div><!-- #content -->
</div><!-- #primary -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>

Я размышлял над тем же вопросом и придумал следующее гипотетическое решение:
- Внутри плагина создать шорткод, который выводит архивный цикл так, как вам нужно.
- При создании пользовательского типа записи не включать опцию 'archive'.
- Добавить таблицу стилей, которая управляет всеми стилями содержимого вашего цикла.
При активации плагина создать страницу с помощью wp_insert_post, где имя будет соответствовать типу записи, а содержимое - шорткоду.
Вы можете предоставить опции в шорткоде для дополнительной настройки стилей или добавить классы к контейнеру записи, чтобы соответствовать специфичным или пользовательским стилям темы. Пользователь также может добавить дополнительный контент до/после цикла, редактируя страницу.

Хотя я не автор исходного вопроса, я искал решение той же проблемы. Я попробовал ваше гипотетическое решение и теперь могу подтвердить, что оно работает и на практике.

Вы можете использовать фильтр single_template
. Базовый пример, взятый из Кодекса:
function get_custom_post_type_template($single_template) {
global $post;
if ($post->post_type == 'my_post_type') {
$single_template = dirname( __FILE__ ) . '/post-type-template.php';
}
return $single_template;
}
add_filter( "single_template", "get_custom_post_type_template" );
