Все мы знакомы с тем, как поисковик 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

Вам понравился материал?

Добавить комментарий

Такой e-mail уже зарегистрирован. Воспользуйтесь формой входа или введите другой.

Вы ввели некорректные логин или пароль

Извините, для комментирования необходимо войти.

14 комментариев

сначала новые
по рейтингу сначала новые по хронологии

Не подскажите как с этим бороться? https://developers.google.com/structured-data/testing-tool?url=http%253A%252F%252Ffriendcook.ru%252Fsirijskaya-mahammara%252F Или хотя бы кратко объясните что эти ошибки значат.
Спасибо)

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

Регина

Здравствуйте! Я немного изменила код в INGREDIENTS - TEXTAREA ( 'type' => 'textarea' заменила на 'type' => 'repeatable') и теперь не могу отобразить записи на сайте. Не подскажите как это сделать?

Регина

Здравствуйте! А возможно ли вывести мета-блоки не создавая нового шаблона записи, то есть в обычном шаблоне записей?

Только через CustomPostType

Регина

Получилось! Просто вставила код метаблоков в функции!

Регина

Здравствуйте! Помогите пожалуйста! Уже все перепробовала! Как вставить код include('recipe-config.php'); в функции????

Регина

Я все это уже делала, но у меня 'recipe-config.php' в админке сверху в виде кода выходит, как простой текст.

Регина

Странное дело, делала все тоже самое и все получилось. Так и не поняла в чем дело. Только теперь у меня другой вопрос, я создаю новый рецепт, сохраняю, публикую, перехожу на сайт, но запись не выводится, как будто ее и нет. В чем может быть дело?

Виталий

Попробовал создать новый файл recipe-config.php, в начале надо ставить
Если так, то чтото весь сайт скривился и в админке не появилась вкладка рецепт,а просто надпись с кодами сверху админки, что не так сделал?

Виталий

Вот е вас форма комментариев интересная даже элементы php не пропишешь. Получился вопрос не корректный. Я спрашивал Надо ли код заключать в php с характерными

Просто скачайте исходники по ссылке в начале урока, и посмотрите на весь код целиком. Думаю, вы разберетесь, что за чем следует.

Статья бомба! Спасибо, возьму на заметку.