Все мы знакомы с тем, как поисковик Google отображает результаты поиска: перед нами появляется страница с заголовком и небольшим сниппетом с текстом для каждого из результатов поискового запроса. При помощи рич-сниппетов (Google Rich Snippets) у нас есть возможность добавить полезные данные к результатам поиска, чтобы выделить конкретную позицию из остальных результатов поисковой выдачи и привлечь таким путем больше посетителей на сайт нашего проекта.
И хотя уже существуют плагины, которые обеспечивают такую функциональность для WordPress, бывают ситуации, в которых полагаться на сторонний плагин для сайта не рекомендуется. Поэтому в сегодняшнем руководстве мы рассмотрим, как интегрировать формат микро-данных в размету вашей темы для WordPress, чтобы отображать, например, кулинарные рецепты и при этом сделать эти данные совместимыми с требованиями рич-сниппетов Google.
Введение в рич-сниппеты Google
Давайте для начала взглянем на пример того, как выглядит такой рич-сниппет:

Я выделил для вас те сниппеты с дополнительной информацией, которую поисковик Google «считывает» со страницы. Как вы сами можете увидеть, рич-сниппеты добавляют по-настоящему полезную информацию к результатам, которые выдает по запросу поисковый движок. В случае с кулинарными рецептами эта дополнительная информация включает фото, рейтинг рецепта, сумму калорий и общее время на приготовления конкретного блюда. Вся эта дополнительная информация дает пользователям куда лучшее представление о содержимом страницы, и повышается вероятность того, что пользователь кликнет по вашей ссылке и перейдет на ваш сайт.
Как включить рич-сниппеты?
Секрет использования рич-сниппетов кроется в структурированной семантической разметке, которая позволяет поисковику Google «понять» контент на странице. Поэтому основное, что вам надо будет сделать — это правильно провести разметку контента, чтобы описать конкретные типы информации на вашем вебсайте.
В данном практическом руководстве мы сосредоточимся на подготовке рич-сниппетов для сайта с кулинарными рецептами. Но в Google есть поддержка рич-сниппетов для целого ряда различных типов контента, а именно:
- Обзоры
- Данные о людях
- Данные о продуктах и товарах
- Бизнесы и организации
- Информация о мероприятиях
- Данные о музыке
Для детальной информации о рич-сниппетах и поддерживаемых типах контента посетите страницу Google Help Center.
Когда речь заходит о разметке вашего контента, есть три основных типа разметки, из которых вам надо выбрать:
- Микро-данные
- Микро-форматы
- RDFa
В этом руководстве мы интегрируем разметку микро-данных со свойствами schema.org, согласно требованиям документации по рич-сниппетам от Google. Стоит сказать, что словарь разметки schema.org распознается не только в поисковике от компании Google, но и в поисковых системах от Yahoo! и Microsoft.
Для детальной информации о примерах вставки кода на ваш сайт посетите Schema.org.
Шаг 1. Создаем настраиваемые типы постов (Custom Post Type)
Поскольку мы будем писать немало кода, давайте создадим отдельный файл под названием recipe-config.php, в котором будут перечислены все наши сниппеты, а сам этот файл включим при помощи PHP-функции include. Чтобы сделать это, откройте ваш файл functions.php в папке с текущей темой сайта и вставьте следующий код в конце файла:
include('recipe-config.php');
Теперь создадим новый файл под названием recipe-config.php. Весь дальнейший код будем включать в состав этого файла.
Начнем с того, что создадим новый настраиваемый тип постов под названием «Рецепты» (Recipe).
add_action( 'init', 'register_my_culinary_recipe' );
function register_my_culinary_recipe() {
$labels = array(
'name' => _x( 'Recipes', 'culinary_recipes' ),
'singular_name' => _x( 'Recipe', 'culinary_recipes' ),
'add_new' => _x( 'Add New', 'culinary_recipes' ),
'add_new_item' => _x( 'Add New Recipe', 'culinary_recipes' ),
'edit_item' => _x( 'Edit Recipe', 'culinary_recipes' ),
'new_item' => _x( 'New Recipe', 'culinary_recipes' ),
'view_item' => _x( 'View Recipe', 'culinary_recipes' ),
'search_items' => _x( 'Search Recipes', 'culinary_recipes' ),
'not_found' => _x( 'No Recipes found', 'culinary_recipes' ),
'not_found_in_trash' => _x( 'No recipes found in Trash', 'culinary_recipes' ),
'parent_item_colon' => '',
'menu_name' => _x( 'Recipes', 'culinary_recipes' )
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'exclude_from_search' => false,
'hierarchical' => false,
'has_archive' => true,
'rewrite' => array('slug' => 'recipe')
);
register_post_type( 'my_culinary_recipe', $args );
}
Теперь если вы перейдете в раздел администратора, там появится новая опция в меню под названием «Recipes». Пока не добавляйте никаких рецептов, потому что нам сначала надо настроить мета-блоки.

Шаг 2. Добавляем настраиваемые мета-ячейки (Custom Meta Boxes)
Установка
Поскольку нам понадобятся всего несколько мета-блоков для различных типов рецептов, чтобы сохранять конкретные типы информации из рецептов, то я собираюсь для этой цели использовать бесплатную библиотеку Custom Meta Boxes and Fields for WordPress. Вы, само собой, можете использовать любой другой сторонний скрипт или создать мета-ячейки с нуля, если захотите.
На Wptuts+ есть отличное пошаговое руководство о том, как их создавать: Reusable Custom Meta Boxes
Сперва скачаем данную библиотеку с GitHub. Как предлагает автор, мы сохраним все файлы скрипта в папке ‘lib/metabox‘. так что начните с того, что создайте папку под названием ‘lib‘ в вашей теме или дочерней теме; затем добавьте папку ‘metabox‘ внутри папки ‘lib‘. Распакуйте и выгрузите все скачиваемые файлы в ‘/wp-content/themes/my-theme/lib/metabox‘.
И наконец, нам надо включить файл init.php. Обычно его включают в состав файла functions.php, но нам надо сделать эту процедуру внутри файла recipe-config.php, поскольку именно в этом новом файле будут хранится специфические функции, связанные с рецептами как типом контента на сайте.
function be_initialize_cmb_meta_boxes() {
if ( !class_exists( 'cmb_Meta_Box' ) ) {
require_once( 'lib/metabox/init.php' );
}
}
add_action( 'init', 'be_initialize_cmb_meta_boxes', 9999 );
Задаем значения мета-ячеек (Meta Boxes)
Чтобы получить возможность работы с Google Rich Snippets, нам нет нужды перечислять все свойства, включенные в спецификацию, хотя у каждого типа контента есть свой набор минимальных требований к информации для сниппетов. В данном практическом руководстве мы собираемся объединить следующие свойства:
- name
- recipeCategory
- image
- description
- ingredients
- instructions
- recipeYield
- prepTime
- cookTime
- totalTime
- datePublished
- author
Учтите, что мы не будем создавать отдельные мета-блоки для всех свойств. К примеру, totalTime будет рассчитываться, исходя из значений prepTime и cookTime.
Итак, давайте добавим несколько мета-блоков:
$prefix = 'mcr_'; // Prefix for all fields
function mcr_create_metaboxes( $meta_boxes ) {
global $prefix;
$meta_boxes[] = array(
'id' => 'recipe-data',
'title' => 'Culinary Recipe',
'pages' => array('my_culinary_recipe'),
'context' => 'normal',
'priority' => 'high',
'show_names' => true,
'fields' => array(
//TITLE - TEXT
array(
'name' => __( 'Recipe Title', 'culinary_recipes' ),
'id' => $prefix . 'name',
'type' => 'text',
),
//RECIPE TYPE - TEXT
array(
'name' => __( 'Recipe Type', 'culinary_recipes' ),
'desc' => __( 'The type of dish: for example, appetizer, entree, dessert, etc.', 'culinary_recipes' ),
'id' => $prefix . 'type',
'type' => 'text_medium',
),
// IMAGE UPLOAD
array(
'name' => 'Recipe Image',
'desc' => 'Image of the dish being prepared.',
'id' => $prefix . 'image',
'type' => 'file',
'save_id' => false, // save ID using true
'allow' => array('url', 'attachment') // limit to just attachments with array( 'attachment' )
),
//SUMMARY - TEXT
array(
'name' => __( 'Summary', 'culinary_recipes' ),
'desc' => __( 'A short summary describing the dish.', 'culinary_recipes' ),
'id' => $prefix . 'summary',
'type' => 'text',
),
//INGREDIENTS - TEXTAREA
array(
'name' => __( 'Ingredients', 'culinary_recipes' ),
'desc' => __( 'Put each ingredient in seaprate line.', 'culinary_recipes' ),
'id' => $prefix . 'ingredients',
'type' => 'textarea',
),
//DIRECTIONS - TEXTAREA
array(
'name' => __( 'Instructions', 'culinary_recipes' ),
'desc' => __( 'Put each instruction in seaprate line.', 'culinary_recipes' ),
'id' => $prefix . 'instructions',
'type' => 'textarea',
),
//YIELD - TEXT
array(
'name' => __( 'Yield', 'culinary_recipes' ),
'desc' => __( 'Enter the number of servings or number of people served', 'culinary_recipes' ),
'id' => $prefix . 'yield',
'type' => 'text_medium',
),
//PREP TIME - TITLE
array(
'name' => __( 'Prep time', 'culinary_recipes' ),
'desc' => __( 'How long does it take to prep?', 'culinary_recipes' ),
'type' => 'title',
'id' => $prefix . 'prep_title'
),
//PREP TIME HOURS - NUMBER
array(
'name' => __( 'Hours', 'culinary_recipes' ),
'id' => $prefix . 'prep_time_hours',
'type' => 'number',
'std' => '0',
),
//PREP TIME MINUTES- NUMBER
array(
'name' => __( 'Minutes', 'culinary_recipes' ),
'id' => $prefix . 'prep_time_minutes',
'type' => 'number',
'std' => '0',
),
//COOK TIME - TITLE
array(
'name' => __( 'Cooking time', 'culinary_recipes' ),
'desc' => __( 'Total time of cooking, baking etc.', 'culinary_recipes' ),
'type' => 'title',
'id' => $prefix . 'coking_title'
),
//COOKING TIME - TEXT
array(
'name' => __( 'Hours', 'culinary_recipes' ),
'id' => $prefix . 'cook_time_hours',
'type' => 'number',
'std' => '0',
),
//COOKING TIME - TEXT
array(
'name' => __( 'Minutes', 'culinary_recipes' ),
'id' => $prefix . 'cook_time_minutes',
'type' => 'number',
'std' => '0',
)
)
);
return $meta_boxes;
}
add_filter( 'cmb_meta_boxes' , 'mcr_create_metaboxes' );
При помощи данного фрагмента кода мы создали мета-блок под названием «Culinary Recipe», который будет отображать только экран для редактирования типа постов и текста с рецептами.

Сами определения полей хранятся в массиве в свойствах ‘fields‘. Давайте посмотрим подробнее:
array(
'name' => __('Summary', 'culinary_recipes'),
'desc' => __('A short summary describing the dish.', 'culinary_recipes'),
'id' => $prefix .'summary',
'type' => 'text',
),
Добавить новое поле здесь так же легко, как скопировать один из элементов массива (представленных выше) и изменить значения для полей ‘name‘, ‘id‘, ‘desc‘ и ‘type‘. Библиотеки Custom Metaboxes и Fields предлагают нам набор заранее заданных типов полей, a также удобный метод для определения собственного типа полей.
Для того, чтобы отображать отдельно значения часов и минут для времени готовки и подготовки блюда, я ввел собственное определение поля под названием ‘number‘. Я использовал один из типов ввода для HTML5: «number» — и создал простую функцию валидации, выбирая переменные значения из набора значений, предоставленных пользователем.
add_action( 'cmb_render_number', 'rrh_cmb_render_number', 10, 2 );
function rrh_cmb_render_number( $field, $meta ) {
echo '<input type="number" min="0" max="60" class="cmb_text_inline" name="', $field['id'], '" id="', $field['id'], '" value="', '' !== $meta ? $meta : $field['std'], '" />','<p class="cmb_metabox_description">', $field['desc'], '</p>';
}
add_filter( 'cmb_validate_number', 'rrh_cmb_validate_number' );
function rrh_cmb_validate_number( $new ) {
return (int)$new;
}
Шаг 3. Отображаем выбранный контент
Теперь мы наконец-то готовы к тому, чтобы написать разметку контента. Мы могли бы создать отдельный файл шаблона для нашего настраиваемого типа постов и поместить разметку прямо в этот шаблон. Но вместо этого мы поместим всю разметку внутрь функции и добавим ее к контенту поста с помощью фильтра the_content ().
Это важный момент, поскольку есть много плагинов, которые добавляют тот или иной вид контента, например кнопки для социальных сетей, в конец поста. Таким способом добавления мы будем уверены, что весь вывод данных в плагине будет отображаться под текстом самого рецепта.
function mcr_display_recipe($content) {
global $post;
$recipe = '';
if ( is_singular( 'my_culinary_recipe' ) ) {
$recipe .= '<div class="recipe">';
$recipe .= '<div itemscope itemtype="http://schema.org/Recipe" >';
$recipe .= '<h2 itemprop="name">'. get_post_meta($post->ID,'mcr_name',true) .'</h2>';
$recipe .= '<img class="alignright" itemprop="image" src="'. get_post_meta($post->ID,'mcr_image',true) .'" />';
$recipe .= '<span class="mcr_meta"><b>Recipe type:</b> <time itemprop="recipeCategory">'. get_post_meta($post->ID,'mcr_type',true) .'</time></span>';
$recipe .= '<span class="mcr_meta"><b>Yield:</b> <span itemprop="recipeYield">'. get_post_meta($post->ID,'mcr_yield',true) .'</span></span>';
$recipe .= '<span class="mcr_meta"><b>Prep time:</b> <time content="'. mcr_time('prep','iso') .'" itemprop="prepTime">'. mcr_time('prep') .'</time></span>';
$recipe .= '<span class="mcr_meta"><b>Cook time:</b> <time content="'. mcr_time('cook','iso') .'" itemprop="cookTime">'. mcr_time('cook') .'</time></span>';
$recipe .= '<span class="mcr_meta"><b>Total time:</b> <time content="'. mcr_total_time('iso') .'" itemprop="totalTime">'. mcr_total_time() .'</time></span>';
$recipe .= '</br>';
$recipe .= '<hr />';
$recipe .= '<span itemprop="description">'. get_post_meta($post->ID,'mcr_summary',true) .'</span><br />';
$recipe .= '<h3>Ingredients:</h3> '. mcr_list_items('ingredients');
$recipe .= '<h3>Directions:</h3> '. mcr_list_items('instructions');
$recipe .= '<span class="mcr_meta">Published on <time itemprop="datePublished" content="'. get_the_date('Y-m-d') .'">'. get_the_date('F j, Y') .'</time></span>';
$recipe .= '<span class="mcr_meta">by <span itemprop="author">'. get_the_author() .'</span></span>';
$recipe .= '</div>';
$recipe .= '</div>';
}
return $content . $recipe;
}
add_filter('the_content', 'mcr_display_recipe', 1);
Давайте разберемся с кодом. Первым делом мы «вытягиваем» общий объект $post, что дает нам доступ к различной полезной информации о посте, который отображается в данный момент.
Затем мы используем условный тег is_singular () для того, чтобы проверить, является ли отдельный пост типа ‘my_culinary_recipe‘ тем, который мы видим в настоящий момент. Это происходит потому, что мы не создавали отдельный шаблон для нашего нового типа постов (для рецептов), а потому WordPress использует более общий шаблон single.php (либо же index.php, если нет single.php) для отображения нашего рецепта. Используя критерий утверждения if, мы можем убедиться, что разметка рецепта не будет отображаться в остальных, обычных постах.
И наконец, мы получаем данные рецепта при помощи функции get_post_meta () и помещаем ее в разметку, которая структурирована согласно формату микро-данных.
Вспомогательные функции
Вы наверняка заметили, что я использовал несколько дополнительных функций: mcr_time (), mcr__total_time () и mcr_list_items (), — чтобы получить и подготовить данные к отображению. Давайте разберем и этот момент.
Свойства, связанные со временем приготовления блюда (prepTime, cookTime и totalTime), задаются в формате ISO 8601. Чтобы учесть этот момент, все наши функции времени будут принимать формат как параметр, и вывод также я оформил соответствующим образом.
function mcr_time($type = 'prep', $format = null) {
global $post;
$hours = get_post_meta($post->ID,'mcr_'.$type.'_time_hours',true);
$minutes = get_post_meta($post->ID,'mcr_'.$type.'_time_minutes',true);
$time = '';
if ($format == 'iso') {
if ($hours > 0) {
$time = 'PT'.$hours.'H';
if($minutes > 0) {
$time .= $minutes.'M';
}
}
else {
$time = 'PT'.$minutes.'M';
}
}
else {
if ($hours > 0) {
if ($hours == 1) {
$time = $hours.' hour ';
}
else {
$time = $hours.' hrs ';
}
if ($minutes > 0) {
$time .= $minutes.' mins';
}
}
else {
$time = $minutes.' mins';
}
}
return $time;
}
Функция mcr_time () подготавливает вывод для времени приготовления и готовности блюда, и принимает 2 параметра:
- $type
- $format
function mcr_total_time($format = null) {
global $post;
$prep_hours = get_post_meta($post->ID,'mcr_prep_time_hours',true);
$prep_minutes = get_post_meta($post->ID,'mcr_prep_time_minutes',true);
$cook_hours = get_post_meta($post->ID,'mcr_cook_time_hours',true);
$cook_minutes = get_post_meta($post->ID,'mcr_cook_time_minutes',true);
$total_minutes = ($prep_hours + $cook_hours)*60 + $prep_minutes + $cook_minutes;
$hours = 0;
$minutes = 0;
if ($total_minutes >= 60) {
$hours = floor($total_minutes / 60);
$minutes = $total_minutes - ($hours * 60);
}
else {
$minutes = $total_minutes;
}
$total_time = '';
if ($format == 'iso') {
if ($hours > 0 ) {
$total_time = 'PT'.$hours.'H';
if ($minutes > 0) {
$total_time .= $minutes.'M';
}
}
else {
$total_time = 'PT'.$minutes.'M';
}
}
else {
if ($hours > 0 ) {
if ($hours == 1) {
$total_time = $hours.' hour ';
}
else {
$total_time = $hours.' hrs ';
}
if ($minutes > 0) {
$total_time .= $minutes.' mins';
}
}
else {
$total_time = $minutes.' mins';
}
}
return $total_time;
}
Функция mcr_total_time () подсчитывает и выводит значение общего времени для конкретного рецепта. Она принимает только один параметр: $format, аналогичный параметру $format в функции mcr_time ().
И последняя вспомогательная функция отображает перечень компонентов или инструкция для рецепта, согласно параметру $type:
function mcr_list_items($type = 'ingredients') {
global $post;
if (get_post_meta($post->ID, 'mcr_'. $type, true)) {
$get_items = get_post_meta($post->ID, 'mcr_'. $type, true);
$items = explode("\r", $get_items);
$list = '';
}
else {
return;
}
if ($type=='ingredients') {
$list .= '<ul>';
foreach ($items as $item) {
$list .= '<li><span itemprop="ingredients">' . trim($item) . '</span></li>';
}
$list .= '</ul>';
}
elseif ($type=='instructions') {
$list .= '<ol itemprop="recipeInstructions">';
foreach ($items as $item) {
$list .= '<li>' . trim($item) . '</li>';
}
$list .= '</ol>';
}
else {
$list .= 'Invalid list type.';
}
return $list;
}
Теперь настало время добавить контент. Перейдите к секции «Рецепты» в зоне работы администратора и добавьте новый рецепт. Выводу данных понадобится некоторая настройка стилей, но если вы посмотрите на пост, то увидите рецепт под остальным обычным контентом на вашем сайте:

Вот и все! Единственное, что осталось, — это проверка корректности разметки с помощью инструмента тестирования рич-сниппетов от Google.
Вот как выглядит блок рич-сниппета в режиме предварительного просмотра, сгенерированный на базе нашей HTML-разметки:

Вы можете протестировать свою разметку путем подстановки URL или кода сниппета в инструмент тестирования.
Как только вы добавите разметку рич-сниппетов, подождите какое-то время, чтобы Google обнаружил внесенные вами измнения. Когда Google обнаружит новую разметку, поисковик начнет показывать рич-сниппеты для вашего сайта в поисковых результатах. Вы также можете подать заявку через форму, чтобы оповестить Google о появлении рич-сниппетов на вашем сайте, но сперва стоит дать немного времени поисковику.
Заключение
В этом пошаговом руководстве я показал вам, как интегрировать формат микро-данных согласно словарю от schema.org для отображения кулинарных рецептов. Данный пример может послужить вам практическим шаблоном, который вы можете использовать в качестве «дорожной карты» при настройке рич-сниппетов для других типов контента. Использовали ли вы Google Rich Snippets для каких-либо типов контента в ваших проектах? Расскажите нам об этом в комментариях.
Источник: WP.tutsplus.com
Как правильно интегрировать сниппеты Google в тему WordPress,








