Подключение скриптов и стилей при наличии шорткода
Какой оптимальный способ регистрации/подключения скриптов и/или стилей для использования в плагинах?
Недавно я создал простой плагин для добавления аватара пользователя/граватара с помощью шорткода. У меня есть различные варианты стилей для отображения аватара (квадратный, круглый и т.д.), и я решил поместить CSS непосредственно в сам шорткод.
Однако теперь я понимаю, что это не лучший подход, так как CSS будет повторяться каждый раз, когда шорткод используется на странице. Я видел несколько других подходов на этом сайте, и даже в документации WordPress есть два собственных примера, поэтому трудно понять, какой подход наиболее последовательный и быстрый.
Вот методы, о которых мне известно:
Метод 1: Включение напрямую в шорткод - Это то, что я сейчас использую в плагине, но кажется неоптимальным, так как код повторяется.
class My_Shortcode {
function handle_shortcode( $atts, $content="" ) {
/* просто подключаем или выводим скрипты/стили прямо в шорткоде */
?>
<style type="text/css">
</style>
<?php
return "$content";
}
}
add_shortcode( 'myshortcode', array( 'My_Shortcode', 'handle_shortcode' ) );
Метод 2: Использование класса для условного подключения скриптов или стилей
class My_Shortcode {
static $add_script;
static function init() {
add_shortcode('myshortcode', array(__CLASS__, 'handle_shortcode'));
add_action('init', array(__CLASS__, 'register_script'));
add_action('wp_footer', array(__CLASS__, 'print_script'));
}
static function handle_shortcode($atts) {
self::$add_script = true;
// обработка шорткода здесь
}
static function register_script() {
wp_register_script('my-script', plugins_url('my-script.js', __FILE__), array('jquery'), '1.0', true);
}
static function print_script() {
if ( ! self::$add_script )
return;
wp_print_scripts('my-script');
}
}
My_Shortcode::init();
Метод 3: Использование get_shortcode_regex();
function your_prefix_detect_shortcode() {
global $wp_query;
$posts = $wp_query->posts;
$pattern = get_shortcode_regex();
foreach ($posts as $post){
if ( preg_match_all( '/'. $pattern .'/s', $post->post_content, $matches )
&& array_key_exists( 2, $matches )
&& in_array( 'myshortcode', $matches[2] ) )
{
// css/js
break;
}
}
}
add_action( 'wp', 'your_prefix_detect_shortcode' );
Метод 4: Использование has_shortcode();
function custom_shortcode_scripts() {
global $post;
if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'myshortcode') ) {
wp_enqueue_script( 'my-script');
}
}
add_action( 'wp_enqueue_scripts', 'custom_shortcode_scripts');

Я нашел другой способ, который хорошо работает для меня:
При инициализации плагина не подключайте скрипты и стили сразу, а зарегистрируйте их с помощью
wp_register_style
иwp_register_script
.Затем вы можете загружать скрипт/стиль по требованию. Например, при выводе шорткода с помощью
wp_enqueue_style("your_style")
иwp_enqueue_script("your_script")
.
Вот пример плагина, использующего этот метод, который позволяет использовать get_avatar в шорткоде. Таблица стилей подключается только при наличии шорткода.
Использование (id по умолчанию - текущий пользователь):
[get_avatar id="" size="32" default="mystery" alt="Фото профиля" class="round"]
function wpse_165754_avatar_shortcode_wp_enqueue_scripts() {
wp_register_style( 'get-avatar-style', plugins_url( '/css/style.css', __FILE__ ), array(), '1.0.0', 'all' );
}
add_action( 'wp_enqueue_scripts', 'wpse_165754_avatar_shortcode_wp_enqueue_scripts' );
if ( function_exists( 'get_avatar' ) ) {
function wpse_165754_user_avatar_shortcode( $attributes ) {
global $current_user;
get_currentuserinfo();
extract( shortcode_atts(
array(
"id" => $current_user->ID,
"size" => 32,
"default" => 'mystery',
"alt" => '',
"class" => '',
), $attributes, 'get_avatar' ) );
$get_avatar = get_avatar( $id, $size, $default, $alt );
wp_enqueue_style( 'get-avatar-style' );
return '<span class="get_avatar ' . $class . '">' . $get_avatar . '</span>';
}
add_shortcode( 'get_avatar', 'wpse_165754_user_avatar_shortcode' );
}

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

Этот метод загружает CSS в подвале, что наверняка имеет ограничения?

Это абсолютно правильный способ. Используйте этот метод всякий раз, когда вам нужно условно загрузить скрипт или таблицу стилей после того, как хук wp_enqueue_scripts
уже произошел. (Шорткоды обрабатываются во время the_content
, который является частью хука the_post
, а значит, подключение во время их обработки будет слишком поздним, если вы уже не зарегистрировали скрипт заранее)

Прежде чем начать ответ, хочу сказать, что в данном вопросе CSS и JS — это не одно и то же.
Причина проста: хотя добавление JS в тело страницы (в подвал) является распространённым и допустимым подходом, CSS необходимо размещать в секции <head>
страницы. Даже если большинство браузеров корректно обрабатывают CSS в теле страницы, это не соответствует валидному HTML-коду.
Когда шорткод обрабатывается, секция <head>
уже выведена. Это означает, что JS можно без проблем добавить в подвал, но CSS должен быть добавлен до отображения шорткода.
Скрипты
Если ваш шорткод использует только JS, вам повезло — можно просто использовать wp_enqueue_script
в теле callback-функции шорткода:
add_shortcode( 'myshortcode', 'my_handle_shortcode' );
function my_handle_shortcode() {
wp_enqueue_script( 'myshortcodejs', '/path/to/js/file.js' );
// остальной код здесь...
}
Таким образом, ваш скрипт добавится в подвал и только один раз, даже если шорткод используется на странице несколько раз.
Стили
Если ваш код требует стилей, их нужно подключить до отображения шорткода.
Есть несколько способов сделать это:
Проверить все записи в текущем запросе и добавить стили шорткода, если это необходимо. Именно это делается в методах #3 и #4 из оригинального вопроса. Оба метода выполняют одно и то же, но
has_shortcode
появился в WordPress 3.6, аget_shortcode_regex
доступен с версии 2.5. Используйтеget_shortcode_regex
, только если нужно обеспечить совместимость с более старыми версиями.Всегда добавлять стили шорткода на всех страницах.
Проблемы
Проблема с первым подходом — производительность. Регулярные выражения довольно медленные, и их выполнение в цикле для всех записей может значительно замедлить загрузку страницы. Кроме того, в темах часто показывают только анонсы записей в архивах, а полный контент с шорткодами — только на отдельных страницах. В таком случае, при отображении архива, ваш плагин будет запускать проверку регулярных выражений в цикле, чтобы добавить стили, которые никогда не будут использованы. Это двойной удар по производительности: замедление генерации страницы + лишний HTTP-запрос.
Проблема второго подхода — снова производительность. Добавление стилей на все страницы означает лишний HTTP-запрос везде, даже там, где он не нужен. Хотя время генерации страницы на сервере не пострадает, общее время её отображения увеличится для всех страниц сайта.
Что же делать разработчику плагина?
Лучшее решение — добавить страницу настроек в плагин, где пользователи смогут выбрать, должен ли шорткод работать только на отдельных страницах или в архивах тоже. В обоих случаях хорошо предоставить ещё одну опцию для выбора типов записей, где шорткод будет активен.
Таким образом, можно использовать хук "template_redirect"
, чтобы проверить, соответствует ли текущий запрос требованиям, и, если да, добавить стили.
Если пользователь выбрал использование шорткода только на отдельных страницах, стоит проверить, есть ли в записи шорткод. Поскольку требуется всего одно регулярное выражение, это не должно сильно замедлить загрузку.
Если пользователь разрешил использование шорткода в архивах, я бы избегал проверки регулярных выражений для всех записей, если их количество велико, и просто подключал стили, если запрос соответствует требованиям.
Что считать "большим" количеством, можно определить с помощью тестов производительности или, как вариант, добавить ещё одну настройку и дать выбор пользователям.

Стоит отметить, что теги <style>
в теле документа теперь считаются валидными согласно спецификации W3C. https://www.w3.org/TR/html52/document-metadata.html#the-style-element

@IsaacLubow На текущий момент (2023): Контексты, в которых может использоваться этот элемент: Внутри элемента noscript, который является дочерним для элемента head. Проверил валидность <style>
внутри <body>
на https://validator.w3.org/nu/#textarea, но результат - Ошибка: Элемент style
не допускается как дочерний для элемента body
в данном контексте.

Для своего плагина я обнаружил, что иногда пользователи используют конструктор тем, который хранит шорткоды в метаданных записи. Вот что я использую для определения, присутствует ли шорткод моего плагина в текущей записи или её метаданных:
function abcd_load_my_shorcode_resources() {
global $post, $wpdb;
// определяем, содержит ли страница шорткод "my_shortcode"
$shortcode_found = false;
if ( has_shortcode($post->post_content, 'my_shortcode') ) {
$shortcode_found = true;
} else if ( isset($post->ID) ) {
$result = $wpdb->get_var( $wpdb->prepare(
"SELECT count(*) FROM $wpdb->postmeta " .
"WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'", $post->ID ) );
$shortcode_found = ! empty( $result );
}
if ( $shortcode_found ) {
wp_enqueue_script(...);
wp_enqueue_style(...);
}
}
add_action( 'wp_enqueue_scripts', 'abcd_load_my_shorcode_resources' );

WordPress имеет встроенную функцию для выполнения действий в зависимости от наличия определенного шорткода. Называется она has_shortcode()
. Вы можете использовать следующий код для подключения стилей и скриптов.
Здесь я использовал is_a( $post, 'WP_Post' )
для проверки, является ли объект $post
экземпляром класса WP_Post
, а также $post->post_content
для проверки содержимого записи.
if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'shortcode_tag') ) {
wp_enqueue_style( 'handle', get_template_directory_uri() . '/your_file_filename.css' );
}

Я делаю так:
class My_Shortcode {
function __construct() {
do_action('my_start_shortcode'); // вызываем хук
....
и ловлю хук в других функциях (или других плагинах):
function wpse_3232_load_script(){
wp_enqueue_script('js-myjs'); // подключаем скрипт
}
add_action('my_start_shortcode','wpse_3232_load_script',10); // вешаем функцию на хук

Как уже упоминалось, использование 'has_shortcode' решит эту задачу. Вот пример, который я нашел очень полезным (Источник: Wordpress.org)
function wpdocs_shortcode_scripts() {
global $post;
if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'wpdocs-shortcode') ) {
wp_enqueue_script( 'wpdocs-script');
}
}
add_action( 'wp_enqueue_scripts', 'wpdocs_shortcode_scripts');

Мы действительно можем условно загружать CSS и JS файлы через шорткод, не используя слишком сложные функции:
Сначала предположим, что мы зарегистрировали все наши CSS/JS файлы заранее (например, при выполнении wp_enqueue_scripts
):
function prep_css_and_js() {
wp_register_style('my-css', $_css_url);
wp_register_script('my-js', $_js_url);
}
add_action( 'wp_enqueue_scripts', 'prep_css_and_js', 5 );
Далее рассмотрим нашу функцию шорткода:
function my_shortcode_func( $atts, $content = null ) {
if( ! wp_style_is( "my-css", $list = 'enqueued' ) ) { wp_enqueue_style('my-css'); }
if( ! wp_script_is( "my-js", $list = 'enqueued' ) ) { wp_enqueue_script('my-js'); }
...
}
Просто. Готово. Таким образом: мы можем "условно загружать" CSS/JS файлы (только при выполнении [шорткода]).
Рассмотрим следующую идеологию:
- Мы хотим загружать определённые CSS/JS файлы (но только когда срабатывает шорткод)
- Возможно, мы не хотим, чтобы эти файлы загружались на каждой странице или вообще, если шорткод не используется на странице/записи. (лёгкость)
- Мы можем достичь этого, убедившись, что WP регистрирует ВСЕ CSS/JS файлы до вызова шорткода и до постановки в очередь.
- То есть: возможно, во время моей функции
prep_css_and_js()
я загружаю ВСЕ .css/.js файлы из определённых папок...
Теперь, когда WP видит их как зарегистрированные скрипты, мы действительно можем вызывать их условно, основываясь на различных ситуациях, включая, но не ограничиваясь my_shortcode_func()
Преимущества: (без MySQL, без сложных функций и без необходимости фильтров)
- это "ортодоксально для WP", элегантно, быстро загружается, легко и просто
- позволяет компиляторам/минификаторам обнаруживать такие скрипты
- использует WP функции (wp_register_style/wp_register_script)
- совместимо с большим количеством тем/плагинов, которые могут захотеть отменить регистрацию этих скриптов по разным причинам.
- не срабатывает несколько раз
- задействовано очень мало обработки или кода
Ссылки:

Я обнаружил, как проверить наличие шорткода в текстовом виджете для моего собственного плагина.
function check_shortcode($text) {
$pattern = get_shortcode_regex();
if ( preg_match_all( '/'. $pattern .'/s', $text, $matches )
&& array_key_exists( 2, $matches )
&& in_array( 'myshortcode', $matches[2] ) )
{
// шорткод используется
// подключаем мои CSS и JS
/****** Подключение RFNB ******/
wp_enqueue_style('css-mycss');
wp_enqueue_script('js-myjs');
// ОПЦИОНАЛЬНО: РАЗРЕШАЕМ РАБОТУ ШОРТКОДА В ТЕКСТОВОМ ВИДЖЕТЕ
add_filter( 'widget_text', 'shortcode_unautop');
add_filter( 'widget_text', 'do_shortcode');
}
return $text;
}
add_filter('widget_text', 'check_shortcode');

Я объединил пример кода со страницы WordPress для has_shortcode() и ответа пользователя zndencka. Я заметил, что функция has_shortcode была добавлена в WordPress 3.6, поэтому сначала проверяю её существование. Хотя, возможно, эта проверка уже устарела, так как, согласно статистике самого WordPress, пользователей с версией ниже 3.6 осталось очень мало.
// Это гарантирует, что стили будут подключены в заголовке до загрузки шорткода на странице/записи
function enqueue_shortcode_header_script() {
global $post;
if ( function_exists( 'has_shortcode' ) ){ // добавлено в WordPress 3.6
// Источник: https://codex.wordpress.org/Function_Reference/has_shortcode
if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'my_shortcode') ) {
wp_enqueue_style( 'shortcode-css' );
}
}
else if ( isset($post->ID) ) { // Небольшой процент пользователей WordPress ниже 3.6 https://wordpress.org/about/stats/
global $wpdb;
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT count(*) FROM $wpdb->postmeta " .
"WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'",
$post->ID )
);
if (!empty( $result )) { wp_enqueue_style( 'shortcode-css' ); }
}
}
add_action( 'wp_enqueue_scripts', 'enqueue_shortcode_header_script');

Я использую WordPress версии 5.4 с ООП стилем кода, не знаю, влияет ли это на то, почему ни одно из вышеперечисленных решений не сработало для меня, поэтому я придумал такое решение:
public function enqueue_scripts() {
global $post;
//error_log( print_r( $post, true ) );
//error_log( print_r( $post->post_content, true ) );
//error_log( print_r( strpos($post->post_content, '[YOUR_SHORTCODE]'),true));
if ( is_a( $post, 'WP_Post' ) && strpos($post->post_content, '[YOUR_SHORTCODE]') )
{
wp_register_style('my-css', $_css_url);
wp_register_script('my-js', $_js_url);
}
}
Надеюсь, это поможет кому-то.
