Статьи

Интеграция Google Rich Snippets в тему WordPress

Мы все знакомы с тем, как Google представляет результаты поиска — с заголовком страницы и небольшим фрагментом текста для каждого результата. С помощью Google Rich Snippets мы можем добавить полезную информацию во фрагмент результатов веб-поиска, чтобы выделить его среди других результатов и в конечном итоге привлечь больше посетителей. Хотя в WordPress уже есть плагины, которые предоставляют такую ​​функциональность, бывают ситуации, когда ретрансляция на сторонний плагин не рекомендуется. В этом уроке мы собираемся интегрировать формат микроданных в разметку темы WordPress, чтобы отобразить кулинарный рецепт и сделать его совместимым с требованиями Google Rich Snippets.


Давайте посмотрим на пример богатого фрагмента:

Я выделил для вас фрагменты дополнительной информации, которую Google «читает» со страницы. Как видите, богатые фрагменты добавляют действительно полезную информацию к результатам поиска. В случае рецептов эта информация включает в себя фотографию, оценку рецепта, калории и общее время, необходимое для приготовления блюда. Вся эта дополнительная информация дает пользователям гораздо лучшее представление о содержимом страницы и повышает вероятность того, что пользователи перейдут по ссылке и посетят ваш веб-сайт.

Фрагменты для каждого типа контента выглядят немного по-разному и предоставляют информацию, относящуюся к конкретному типу контента.

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

  • Отзывы
  • люди
  • Товары
  • Предприятия и организации
  • События
  • Музыка

Для получения дополнительной информации о богатых фрагментах и ​​типах контента посетите Справочный центр Google .

Когда дело доходит до разметки вашего контента, есть три формата разметки на выбор:

  • микроданных
  • микроформатами
  • RDFa

В этом руководстве мы будем интегрировать разметку микроданных со свойствами schema.org, как рекомендовано в документации к фрагментам Google. Стоит отметить, что словарь schema.org распознается не только Google, но и другими крупными поисковыми провайдерами — Yahoo! и Microsoft.

Посетите Schema.org для получения дополнительной информации и примеров того, как реализовать это в своем коде.


Так как мы будем писать довольно много кода, мы создадим отдельный файл с именем recipe-config.php , чтобы хранить все наши фрагменты и включать его с помощью функции PHP include . Для этого откройте файл functions.php в текущей директории темы и вставьте следующий фрагмент кода в конец:

1
include(‘recipe-config.php’);

Теперь создайте новый файл с именем recipe-config.php . Весь следующий код должен быть добавлен в этот файл.

Давайте начнем с создания нового пользовательского типа записи под названием «Рецепт».

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
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 );
}

Теперь, если вы зайдете в админку, в меню должна появиться новая опция под названием «Рецепты». Пока не добавляйте никаких рецептов, потому что сначала нам нужно добавить несколько пользовательских мета-блоков.


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

Wptuts + имеет отличный учебник по теме многоразовых пользовательских мета-боксов

Для начала нам нужно скачать библиотеку с GitHub . Как предполагает автор, мы будем хранить все файлы скриптов в папке « lib / metabox ». Итак, начните с создания папки ‘ lib ‘ в вашей теме или дочерней теме, затем добавьте папку ‘ metabox ‘ внутри ‘ lib ‘. Распакуйте и загрузите все загруженные файлы в ‘ / wp-content / themes / my-theme / lib / metabox ‘.

Наконец, нам нужно включить файл init.php . Обычно вы включаете его в свой файл functions.php, но мы сделаем это в нашем recipe-config.php , так как именно там мы храним все специфические для рецепта функции.

1
2
3
4
5
6
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 );

Как только это будет сделано, мы можем начать определять мета-блоки.

Чтобы претендовать на Google Rich Snippets, нам не нужно предоставлять все свойства, включенные в спецификацию, хотя у каждого типа контента есть необходимый минимум. В этом уроке мы собираемся включить следующие свойства:

  • name
  • recipeCategory
  • image
  • description
  • ingredients
  • instructions
  • recipeYield
  • prepTime
  • cookTime
  • totalTime
  • datePublished
  • author

Обратите внимание, что нам не нужно создавать собственные мета-блоки для всех свойств. Например, totalTime будет рассчитываться на основе prepTime и cookTime .

Давайте добавим несколько пользовательских мета-блоков?

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
$prefix = ‘mcr_’;
 
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 .
                ‘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’ => ‘text_medium’,
            ),
            // IMAGE UPLOAD
            array(
                ‘name’ => ‘Recipe Image’,
                ‘desc’ => ‘Image of the dish being prepared.’,
                ‘id’ => $prefix .
                ‘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 .
                ‘type’ => ‘text’,
            ),
            //INGREDIENTS — TEXTAREA
            array(
                ‘name’ => __( ‘Ingredients’, ‘culinary_recipes’ ),
                ‘desc’ => __( ‘Put each ingredient in seaprate line.’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘type’ => ‘textarea’,
            ),
            //DIRECTIONS — TEXTAREA
            array(
                ‘name’ => __( ‘Instructions’, ‘culinary_recipes’ ),
                ‘desc’ => __( ‘Put each instruction in seaprate line.’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘type’ => ‘textarea’,
            ),
            //YIELD — TEXT
            array(
                ‘name’ => __( ‘Yield’, ‘culinary_recipes’ ),
                ‘desc’ => __( ‘Enter the number of servings or number of people served’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘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 TIME HOURS — NUMBER
            array(
                ‘name’ => __( ‘Hours’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘type’ => ‘number’,
                ‘std’ => ‘0’,
            ),
            //PREP TIME MINUTES- NUMBER
            array(
                ‘name’ => __( ‘Minutes’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘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 .
            ),
            //COOKING TIME — TEXT
            array(
                ‘name’ => __( ‘Hours’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘type’ => ‘number’,
                ‘std’ => ‘0’,
            ),
            //COOKING TIME — TEXT
            array(
                ‘name’ => __( ‘Minutes’, ‘culinary_recipes’ ),
                ‘id’ => $prefix .
                ‘type’ => ‘number’,
                ‘std’ => ‘0’,
            )
        )
    );
 
    return $meta_boxes;
}
 
add_filter( ‘cmb_meta_boxes’ , ‘mcr_create_metaboxes’ );

С помощью этого фрагмента кода мы создали мета-блок под названием «Кулинарный рецепт», который будет отображаться только на экране редактирования типа записи «Рецепты».

Фактические определения полей хранятся в виде массива в свойстве fields . Давайте внимательнее посмотрим:

1
2
3
4
5
6
array(
    ‘name’ => __(‘Summary’, ‘culinary_recipes’),
    ‘desc’ => __(‘A short summary describing the dish.’, ‘culinary_recipes’),
    ‘id’ => $prefix .’summary’,
    ‘type’ => ‘text’,
),

Добавить новое поле так же просто, как скопировать один из элементов массива (показанный выше) и изменить значения для « name », « id », « desc » и « type ». Библиотека пользовательских метабоксов и полей предлагает несколько предопределенных типов полей и удобный метод для определения своих собственных.

Чтобы облегчить отдельный ввод часов и минут для времени приготовления и приготовления, я определил наш собственный тип поля, называемый « number ». Я использовал один из новых типов ввода HTML5 — число и создал простую функцию проверки, приводящую целочисленный тип к значению, предоставленному пользователем.

1
2
3
4
5
6
7
8
9
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;
}

Теперь мы наконец готовы написать некоторую разметку. Мы могли бы создать отдельный файл шаблона для нашего пользовательского типа записи и поместить разметку непосредственно в этот шаблон. Вместо этого мы поместим всю разметку внутри функции и добавим ее к содержимому the_content() с the_content() фильтра the_content() . Это важно, потому что есть много плагинов, которые добавляют какой-то контент, например кнопки социальных сетей, в конец поста. Таким образом, мы гарантируем, что весь вывод плагина отображается под рецептом.

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
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»>’.
                $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»>’.
                $recipe .= ‘<span class=»mcr_meta»><b>Yield:</b> <span itemprop=»recipeYield»>’.
                $recipe .= ‘<span class=»mcr_meta»><b>Prep time:</b> <time content=»‘. mcr_time(‘prep’,’iso’) .'» itemprop=»prepTime»>’.
                $recipe .= ‘<span class=»mcr_meta»><b>Cook time:</b> <time content=»‘. mcr_time(‘cook’,’iso’) .'» itemprop=»cookTime»>’.
                $recipe .= ‘<span class=»mcr_meta»><b>Total time:</b> <time content=»‘. mcr_total_time(‘iso’) .'» itemprop=»totalTime»>’.
                $recipe .= ‘</br>’;
                $recipe .= ‘<hr />’;
                $recipe .= ‘<span itemprop=»description»>’.
                $recipe .= ‘<h3>Ingredients:</h3> ‘.
                $recipe .= ‘<h3>Directions:</h3> ‘.
                $recipe .= ‘<span class=»mcr_meta»>Published on <time itemprop=»datePublished» content=»‘. get_the_date(‘Ym-d’) .'»>’.
                $recipe .= ‘<span class=»mcr_meta»>by <span itemprop=»author»>’.
            $recipe .= ‘</div>’;
        $recipe .= ‘</div>’;
    }
 
    return $content .
}
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() и get_post_meta() их в разметку, структурированную в соответствии с форматом микроданных.

Вы можете заметить, что я использовал некоторые дополнительные функции — mcr_time() , mcr__total_time() и mcr_list_items() для извлечения и подготовки данных для отображения. Давайте взглянем!

Свойства, связанные со временем ( prepTime , cookTime и totalTime ), предполагают значения в формате продолжительности ISO 8601 . Чтобы учесть это, обе наши функции, связанные со временем, примут формат в качестве параметра и подготовят выходные данные соответствующим образом.

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
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.’
            }
            else {
                $time = $hours.’
            }
            if ($minutes > 0) {
                $time .= $minutes.’
            }
        }
        else {
            $time = $minutes.’
        }
    }
    return $time;
}

Функция mcr_time() подготавливает выходные данные для времени приготовления и приготовления и принимает два параметра:

  • $type (обязательно) — это тип времени, которое мы хотим отобразить. Принимает два значения — « prep » (по умолчанию) или « cook »
  • $format (необязательно) — указывает, что вывод должен быть отформатирован в соответствии с форматом продолжительности ISO 8601. Принимает только одно значение — « iso ».
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
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.’
            }
            else {
                $total_time = $hours.’
            }
            if ($minutes > 0) {
                $total_time .= $minutes.’
            }
        }
        else {
            $total_time = $minutes.’
        }
    }
    return $total_time;
}

Функция mcr_total_time() рассчитывает и подготавливает вывод для общего времени рецепта. Принимает только один параметр — $format , аналогичный параметру $format в функции mcr_time() .

Последняя вспомогательная функция отображает списки элементов — ингредиентов или инструкций в соответствии с параметром $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
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»>’ .
        }
        $list .= ‘</ul>’;
    }
    elseif ($type==’instructions’) {
        $list .= ‘<ol itemprop=»recipeInstructions»>’;
        foreach ($items as $item) {
            $list .= ‘<li>’ .
        }
        $list .= ‘</ol>’;
    }
    else {
        $list .= ‘Invalid list type.’;
    }
    return $list;
}

Теперь пришло время добавить контент. Перейдите в раздел «Рецепты» в области администрирования и добавьте рецепт. Вывод может потребовать некоторой стилизации, но если вы просматриваете сообщение, вы должны увидеть рецепт ниже обычного содержания.

Это оно! Осталось только проверить правильность нашей разметки с помощью инструмента тестирования расширенного фрагмента от Google.

Это расширенный предварительный просмотр фрагмента, созданный из нашей HTML-разметки:

Вы можете проверить свою разметку, указав URL-адрес или фрагмент кода для инструмента тестирования.

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


В этом уроке я показал вам, как интегрировать формат микроданных со словарем schema.org для отображения кулинарных рецептов. Этот пример должен служить вам планом, который вы можете использовать, чтобы включить расширенные фрагменты для других типов контента. Вы использовали Google Rich Snippets для чего-либо в своих проектах? Дайте нам знать в комментариях ниже.