Хуки и фильтры в WordPress для начинающих

  1. Главная
  2. Блог
  3. WordPress
  4. Хуки и фильтры в WordPress для начинающих

Введение

WordPress — мощная платформа для разработки сайтов и веб-приложений. И, наверное, в 99% проектов необходимо модифицировать админку: добавить свои поля ввода, свои разделы, страницы, или скорректировать выводимые данные.

Но редактировать исходники админки нельзя. Поэтому разработчики позаботились о нас и внедрили в систему экшены (actions), хуки (hooks) и фильтры (filters).

Что такое экшены (actions) и хуки (hooks)

Экшен (action) — это место в коде, в которое можно прикрепить функцию при помощи хука.
Хук (hook) — это функция, которая прикрепляется к экшену.

Экшен обозначается функцией do_action(); В нем указывают следующие аргументы:

  • $hook_name — название экшена.
  • …$args — переменные, которые будут передаваться в хук (функцию). Их может быть бесконечное множество.

Простой пример

Допустим, у нас есть файл index.php, и в нем есть такой код:

<?php
get_header();
the_post();

do_action( "before_content_editor", $post );

get_footer();

В место, где указано do_action( "before_content_editor", $post ); мы можем прикрепить функцию (хук) PHP, не меняя исходники файла index.php. И эта функция будет исполняться каждый раз, когда будет срабатывать файл index.php.

Чтобы добавить хук, нужно использовать функцию add_action() в файле functions.php. У этой функции есть следующие параметры:

  • $tag — название экшена, к которому мы хотим прикрепить функцию
  • $callback — название функции, которая будет выполняться с этим экшеном
  • $priority — приоритет выполнения функции. Это необязательно, но важный параметр, потому что на экшен можно добавить сколько угодно хуков и, как правило, нужно чтобы они исполнялись в строго определенном порядке
  • $accepted_args — количество агрументов, которое принимает функция $callback. Эти аргументы должны передаваться в do_action();

Давайте добавим в экшен «before_content_editor» функцию, которая будет выводить секцию «О компании». Для этого в functions.php добавим такой код:

<?php
add_action( 
    "before_content_editor", //Название хука, к которому цепляемся
    "feodoraxis_show_about", //Название функции, которая будет выполнена
    10, //Приоритет. Если этому-же экшену добавлены функции с цифрой меньше - они будут выполнены раньше этого.
    1 // Мы передали один дополнительный аргумент - объект $post
);

/**
 * В функции указываем аргумент $post, 
 * потому что он передается в функции do_action() в index.php
 **/
function feodoraxis_show_about( $post ) {
    echo "<section class='about'>
              <div class='row'>
                  <div class='col-md-6'>
                      <h2>О компании</h2>
                      <p>Некоторый текст о компании</p>
                  </div>
                  <div class='col-md-6'>
                      <img src='img/about.jpg'>
                  </div>
              </div>
          </section>";
}

Таким образом, не вмешиваясь в исходники файла index.php мы добавили работу простой функции.

Что такое фильтры (filters)

Фильтры (filter) — это место в коде, которое принимает какую-либо информацию (строки, числа, массивы — любые типы данных PHP), «пропускает» её через прикрепленные функции и возвращает её.

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

Фильтр создается при помощи функции apply_filter(), которая имеет следующие параметры:

  • $hook_name — название фильтра
  • $value — значение, которое передается в фильтр и которое, после обработки всеми прикрепленными функциями, будет возвращено
  • …$args — дополнительные параметры. Они не фильтруются, но могут использоваться в прикрепленных фильтрах. Можно передавать бесконечное множество.

Обычно фильтры имеют такой вид:

$content = apply_filters( "the_content", $content );

Чтобы прикрепить функцию к фильтру, нужно в functions.php использовать функцию add_filter();

Пример:

<?php
add_filter( "the_content", "feodoraxis_modify_content", 10 );
function feodoraxis_modify_content( $content ) {
    $content = nl2br( $content );
    $content = str_replace( "фрукты", "овощи", $content );

    return $content;
}

Именованные экшены (actions)

Выше мы рассмотрели экшены, у которых есть строго определенное название и оно не меняется. Но в WP есть и хуки, часть названия которых может меняться. Вот некоторые из них:

  • save_post_{$post_type} — экшен, который срабатывает когда запись определенного типа создаётся или обновляется. $post_type — это строка, в которой передается название типа записи. Например, если мы сохраняем простую страницу, то экшен будет называться так: save_post_page; Если сохраняем товар WooCommerce — то название будет save_post_product
  • edit_post_{$post_type} — экшен, который срабатывает когда запись определенного типа обновляется. Смысл такой-же, как с save_post_{$post_type}.
  • wp_ajax_{$action_name} — экшен, который срабатывает, когда авторизованный пользователь делает ajax-запрос. Например, если это отправка сообщения из формы обратной связи, то $action_name может иметь значение по типу recall_form. Тогда полное имя экшена будет wp_ajax_recall_form
  • wp_ajax_nopriv_{$action_name} — экшен, который срабатывает, когда неавторизованный пользователь делает ajax-запрос. Отличие от wp_ajax только в том, это этот экшен будет срабатывать только для неавторизованных пользователей. В остальном принцип идентичен.

Подробнее про Ajax в WordPress я писал в этой статье.

Пример: обработка количества товаров на складе у товаров при сохранении записи товара. Если вдруг товаров будет меньше одной штуки — то автоматически поставим количество 10. Код указываем в functions.php

<?php
add_action( "save_post_" . "product", "feodoraxis_check_product_stock", 10, 2 );

/**
 * @param int $post_ID
 * @param WP_Post $post
 **/
function feodoraxis_check_product_stock( $post_ID, $post ) {
    $stock = get_post_meta( $post_ID, "feodoraxis_product_stock", true );
    
    if ( $stock < 1 ) {
        update_post_meta( $post_ID, "feodoraxis_product_stock", 10 );
    }
} 

Пример исключительно академический. Вряд-ли вам в реальности потребуется сделать что-то такое. Показал просто для наглядности 🙂

Хуки в админке WordPress

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

Менять исходники нельзя. Поэтому нужно использовать хуки. Для решения конкретно этой задачи нам потребуется хук edit_form_after_editor. Он находится в файле /wp-admin/edit-form-advanced.php:649.

Чтобы вывести статистику, укажем такой код в functions.php:

<?php
add_action( "edit_form_after_editor", "feodoraxis_show_views", 10, 1 );
function feodoraxis_show_views( $post ) {
    if ( get_post_type( $post->ID ) != 'post' ) {
        return;
    }

    echo "Количество просмотров: " . get_post_meta( $post->ID, "feodoraxis_views", true );
}

Отлично! Но выводится не очень красиво — просто посреди серого фона админки. А хочется как-то оформить. Оформим вывод той-же информации по-красивей:

<?php
add_action( "edit_form_after_editor", "feodoraxis_show_views", 10, 1 );
function feodoraxis_show_views( $post ) {
    if ( get_post_type( $post->ID ) != 'post' ) {
        return;
    }
    ?>
    <div style="padding: 10px 15px; margin-top: 30px; color: #FFF; background: linear-gradient(to bottom right, #4358d0, #6443d0);">
        <p>Количество просмотров: <?php echo get_post_meta( $post->ID, "feodoraxis_views", true ); ?></p>
    </div>
    <?php
}

В результате получится такая красивая штука 🙂

Хуки и фильтры в плагине на примере Contact Form 7

Подробно о Contact Form 7 я написал в этой статье

Тут смысл точно такой-же, как и в ядре WordPress. Более того, вы можете сами создавать свои хуки и фильтры в своих решениях. Здесь я лишь покажу на примере, как можно работать с хуками в плагине:

<?php
//Исходники из этой статьи - https://feodoraxis.ru/wordpress/contact-form-7-kak-polzovatsya.html
add_filter( "wpcf7_posted_data", "feodoraxis_change_cf7_data", 10 );
function feodoraxis_change_cf7_data( $posted_data ) {

    if ( isset( $posted_data["product-id"] ) && intval( $posted_data["product-id"] ) > 0 ) {

        $post = get_post( $posted_data["product-id"] );

        if ( isset( $post->post_title ) ) {
            $posted_data["product-id"] = $post->post_title . " | " . get_permalink( $post );
        } else {
            $posted_data["product-id"] = "Товар не найден.";
        }
    }

    return $posted_data;
}

Полезные советы

Не используйте анонимные функции

Это плохая практика, потому что такой хук нельзя удалить через remove_action(), а именованные — можно. Иногда это бывает необходимо.

Кроме того, одну и ту же именованную функцию можно прикрепить к разным хукам. Так нам не придется несколько раз описывать одно и тоже.

Комментирйте экшены

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

<?php
get_header();
the_post();

/**
 * Hook: feodoraxis_front_page
 *
 * @hooked front_page_open  - 10
 * @hooked section_first    - 20
 * @hooked section_about    - 30
 * @hooked section_recall   - 40
 * @hooked front_page_close - 50
 **/
do_action( "feodoraxis_front_page", $post );

Также обязательно пишите в комментариях к функциям и методам о том, к каким экшенам вы их прикрепили. Выглядеть это может так:

<?php

/**
 * Hook: feodoraxis_front_page - 10
 */
function front_page_open() {
    //Some code...
}

Комментировать экшены из ядра не нужно 🙂