Условная загрузка JavaScript / CSS для шорткодов

37

Я выпустил плагин, который создает шорткод и требует файл JavaScript и файл CSS для загрузки на любую страницу, которая содержит этот шорткод. Я мог бы просто загрузить скрипт / стиль на все страницы, но это не лучшая практика. Я только хочу загрузить файлы на страницах, которые вызывают шорткод. Я нашел два способа сделать это, но у обоих есть проблемы.

Метод 1 устанавливает значение true в функцию-обработчик шорткода, а затем проверяет это значение в wp_footerобратном вызове. Если это правда, он использует wp_print_scripts()для загрузки JavaScript. Проблема в том, что он работает только для JavaScript, а не CSS, потому что CSS должен быть объявлен внутри <head>, что вы можете сделать только во время раннего хука, например initили wp_head.

Метод 2 запускается рано и «заглядывает вперед», чтобы увидеть, существует ли шорткод в содержимом текущей страницы. Мне нравится этот метод намного лучше, чем первый, но проблема в том, что он не обнаружит, вызывает ли шаблон do_shortcode().

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

Обновление: я интегрировал решение в свой плагин. Если кому-то интересно увидеть его в живом окружении, вы можете скачать его или просмотреть .

Обновление 2: Начиная с WordPress 3.3, теперь можно вызывать wp_enqueue_script()непосредственно внутри обратного вызова с коротким кодом , и файл JavaScript будет вызываться в нижнем колонтитуле документа. Это технически возможно и для файлов CSS, но это следует считать плохой практикой, поскольку вывод CSS за пределы <head>тега нарушает спецификации W3C, может привести к FOUC и может заставить браузер повторно выполнить рендеринг страницы.

Ян Данн
источник
Я использовал вариант Метода 1 в прошлом. Загрузите JS для шорткода в нижний колонтитул, загрузите CSS для шорткода в строке. Это работает, но взломано. Ждем лучшего решения.
EAMann
Основные проблемы встроенного CSS в том, что его трудно переопределить другими стилями, и он не использует метод wp_enqueue_styles ().
Ян Данн
Сколько у вас есть CSS?
mfields
Сколько строк в JavaScript?
mfields
2
Javascript является легкой частью. Лучшее, что можно сделать, это под "Джедаем" здесь: scribu.net/wordpress/optimal-script-loading.html
mfields

Ответы:

11

Основываясь на своем собственном опыте, я использовал комбинацию методов 1 и 2 - архитектуру и сценарии нижнего колонтитула 1 и технику прогнозирования 2.

Для прогнозирования я использую регулярное выражение вместо stripos; личные предпочтения, быстрее, и может проверить «неправильный» шорткод;

preg_match( '#\[ *shortcode([^\]])*\]#i', $content );

Если вы обеспокоены тем, что авторы используют do_shortcodeвручную, я бы рекомендовал им использовать вызов действия, чтобы поставить в очередь ваш предварительно зарегистрированный стиль вручную.

ОБНОВЛЕНИЕ : Для ленивого автора, который никогда не RTFM, выведите сообщение, чтобы выдвинуть на первый план ошибку их путей;)

function my_shortcode()
{
    static $enqueued;
    if ( ! isset( $enqueued ) )
        $enqueued = wp_style_is( 'my_style', 'done' ); // cache it so we don't repeat if called over and over

    // do shortcode
    $output = '';

    if ( ! $enqueued )
        // you can output the message on first occurence only by wrapping it in the previous if
        $output .= <<<HTML
<p>Attention! You must enqueue the shortcode stylesheet yourself if calling <code>do_shortcode()</code> directly!</p>
<p>Use <code>wp_enqueue_style( 'my_style' );</code> before your <code>get_header()</code> call inside your template.</p>
HTML;

    return $output;
}
TheDeadMedic
источник
Неплохо подмечено. В идеале я бы хотел, чтобы это работало без необходимости делать что-то дополнительно - потому что половину времени они, вероятно, не будут сначала читать FAQ, поэтому они просто предположят, что он сломан - но я мог бы в конечном итоге сделать это. Я мог бы зарегистрировать сценарии на каждой странице, но ставить их в очередь только в случае обнаружения короткого кода. Затем пользователи могут подключиться к init и вызывать функции enqueue в определенных шаблонах, где это необходимо, при условии, что в этот момент выполнение еще не слишком поздно. Кроме того, WP имеет встроенную функцию get_shortcode_regex ().
Ян Данн
3
Если пользователи имеют большой опыт реализации do_shortcode(), не разумно ли предположить, что они также обладают хорошими навыками следования инструкциям для постановки в очередь стиля шорткода?
Чип Беннетт
1
Правда, но он получит регулярное выражение для всех шорткодов, а не только для вашего;) «Я мог бы зарегистрировать скрипты на каждой странице» Возможно, это лучший способ! Обратите внимание, что они не должны подключаться init, просто где-то раньше wp_head. Для ленивого разработчика, проверьте wp_style_is( 'my_style_handle', 'done' )внутри своего шорткода. Если значение равно false, распечатайте видимую ошибку, которая указывает им, что делать.
TheDeadMedic
@Chip - Меня не волнует, что они не способны следовать инструкциям, просто они не знают, что должны, поскольку в 99% случаев вам не нужно делать ничего лишнего.
Ян Данн
1
@ Я думал, что добавление do_shortcode()в шаблон уже «делает что-то лишнее» - и пользователи, которые будут делать что-то дополнительное, либо уже знают о необходимости поставить в очередь стиль, либо будут более охотно / вероятно следовать специальным инструкциям.
Чип Беннетт
8

Я опаздываю с ответом на этот вопрос, но с тех пор, как Ян начал эту тему в списке wp-хакеров, я подумал, что стоит ответить, особенно учитывая, что я планировал добавить такую ​​функцию в некоторые плагины, над которыми я работал.

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

Пошаговая инструкция

  1. Установите $shortcode_usedфлаг в 'no'.
  2. В самой функции шорткода установите $shortcode_usedфлаг в 'yes'.
  3. Установите 'the_content'приоритет перехвата, 12который происходит после того, как WordPress обработал шорткоды, и проверьте метаданные публикации на предмет ''использования ключа "_has_{$shortcode_name}_shortcode". (Значение ''возвращается, когда мета-ключ сообщения не существует для идентификатора сообщения.)
  4. Используйте 'save_post'ловушку для удаления мета-сообщения, удаляя постоянный флаг для этого сообщения в случае, если пользователь меняет использование шорткода.
  5. Также в 'save_post'ловушке используется wp_remote_request()для отправки неблокирующего HTTP GET на собственную постоянную ссылку поста для запуска загрузки первой страницы и установки постоянного флага.
  6. Наконец, установите 'wp_print_styles'и проверьте мета сообщения для значения 'yes', 'no'или ''используя ключ "_has_{$shortcode_name}_shortcode". Если значение 'no'не служит внешнему. Если значение 'yes'или ''идти вперед и служить внешним.

И это должно сделать это. Я написал и протестировал пример плагина, чтобы показать, как все это работает.

Пример кода плагина

Плагин [trigger-css]активирует шорткод, который устанавливает <h2>элементы на странице белым по красному, чтобы вы могли легко увидеть, как он работает. Предполагается, что cssподкаталог содержит style.cssфайл с этим CSS:

/*
 * Filename: css/style.css
 */
h2 {
  color: white;
  background: red;
}

И ниже код в рабочем плагине:

<?php
/**
 * Plugin Name: CSS on Shortcode
 * Description: Shows how to conditionally load a shortcode
 * Author: Mike Schinkel <mike@newclarity.net>
 */
class CSS_On_Shortcode {

  /**
   * @var CSS_On_Shortcode
   */
  private static $_this;

  /**
   * @var string 'yes'/'no' vs. true/false as get_post_meta() returns '' for false and not found.
   */
  var $shortcode_used = 'no';

  /**
   * @var string
   */
  var $HAS_SHORTCODE_KEY = '_has_trigger-css_shortcode';
  /**
   *
   */
  function __construct() {
    self::$_this = $this;
    add_shortcode( 'trigger-css', array( $this, 'do_shortcode' ) );
    add_filter( 'the_content', array( $this, 'the_content' ), 12 ); // AFTER WordPress' do_shortcode()
    add_action( 'save_post', array( $this, 'save_post' ) );
    add_action( 'wp_print_styles', array( $this, 'wp_print_styles' ) );
  }

  /**
   * @return CSS_On_Shortcode
   */
  function this() {
    return self::$_this;
  }

  /**
   * @param array $arguments
   * @param string $content
   * @return string
   */
  function do_shortcode( $arguments, $content ) {
    /**
     * If this shortcode is being used, capture the value so we can save to post_meta in the 'the_content' filter.
     */
    $this->shortcode_used = 'yes';
    return '<h2>THIS POST WILL ADD CSS TO MAKE H2 TAGS WHITE ON RED</h2>';
  }

  /**
   * Delete the 'has_shortcode' meta value so that it can be regenerated
   * on first page load in case shortcode use has changed.
   *
   * @param int $post_id
   */
  function save_post( $post_id ) {
    delete_post_meta( $post_id, $this->HAS_SHORTCODE_KEY );
    /**
     * Now load the post asynchronously via HTTP to pre-set the meta value for $this->HAS_SHORTCODE_KEY.
     */
    wp_remote_request( get_permalink( $post_id ), array( 'blocking' => false ) );
  }

  /**
   * @param array $args
   *
   * @return array
   */
  function wp_print_styles( $args ) {
    global $post;
    if ( 'no' != get_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, true ) ) {
      /**
       * Only bypass if set to 'no' as '' is unknown.
       */
      wp_enqueue_style( 'css-on-shortcode', plugins_url( 'css/style.css', __FILE__ ) );
    }
   }

  /**
   * @param string $content
   * @return string
   */
  function the_content( $content ) {
    global $post;
    if ( '' === get_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, true ) ) {
      /**
       * This is the first time the shortcode has ever been seen for this post.
       * Save a post_meta key so that next time we'll know this post uses this shortcode
       */
      update_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, $this->shortcode_used );
    }
    /**
     * Remove this filter now. We don't need it for this post again.
     */
    remove_filter( 'the_content', array( $this, 'the_content' ), 12 );
    return $content;
  }

}
new CSS_On_Shortcode();

Примеры скриншотов

Вот серия скриншотов

Основной редактор сообщений, без содержимого

Опубликовать дисплей, нет контента

Базовый редактор сообщений с [trigger-css]шорткодом

Опубликовать дисплей с [trigger-css]шорткодом

Не уверен, что это 100%

Я считаю, что вышеупомянутое должно работать почти во всех случаях, но поскольку я только что написал этот код, я не могу быть уверен на 100%. Если вы можете найти ситуации, когда это не работает, мне бы очень хотелось знать, чтобы я мог исправить код в некоторых плагинах, к которым я только что добавил это. Заранее спасибо.

MikeSchinkel
источник
Итак, пять плагинов, использующих ваш подход, будут вызывать пять удаленных запросов при каждом сохранении сообщения? Я бы предпочел использовать регулярные выражения post_content. А как насчет шорткодов в виджетах?
fuxia
@toscho Запуск пост-загрузки на самом деле не обязателен; это только для того, чтобы пользователь не видел загрузку первой страницы с загруженными внешними объектами. Это также неблокирующий вызов, поэтому теоретически вы не должны это замечать. Для нашего кода мы делаем в базовом классе, поэтому базовый класс может обрабатывать это только один раз. Мы могли бы подключить 'pre_http_request'ловушку и отключить несколько вызовов на один и тот же URL, пока 'save_post'ловушка активна, но я бы хотел подождать, пока мы действительно увидим необходимость в этом, нет? Что касается виджетов, они могут быть улучшены для обработки, но это не тот вариант использования, на который я смотрел.
MikeSchinkel
1
@toscho - Также вы не можете быть уверены, что шорткод есть, поскольку другой хук может его очистить. Вы можете быть уверены, что функция шорткода действительно срабатывает. Таким образом, подход регулярных выражений не на 100% надежен.
MikeSchinkel
Я знаю. Не существует пуленепробиваемого способа ввода CSS для коротких кодов (кроме использования <style>).
fuxia
Я работаю с реализацией, основанной на этом решении, и я просто хотел отметить, что этот метод не ставит в очередь предварительный просмотр поста / страницы.
mor7ifer
5

Поиск в Google нашел мне потенциальный ответ . Я говорю «потенциал», так как он выглядит хорошо, должен работать, но я не уверен на 100%, что это лучший способ сделать это:

add_action( 'wp_print_styles', 'yourplugin_include_css' );
function yourplugin_include_css() {
    // Check if shortcode exists in page or post content
    global $post;

    // I removed the end ' ] '... so it can accept args.
    if ( strstr( $post->post_content, '[yourshortcode ' ) ) {
        echo $csslink;
    }
}

Это должно быть в состоянии проверить, использует ли текущее сообщение шорткод, и соответствующим образом добавить таблицу стилей в <head>элемент. Но я не думаю, что это будет работать для индексной (то есть, нескольких записей в цикле) страницы ... Это также из 2-летней записи в блоге, поэтому я даже не уверен, что она будет работать с WP 3.1.X ,

EAMann
источник
Это не будет работать, если у шорткода есть аргументы. Если вы действительно хотите идти по этому пути, который идет медленно, используйте WP get_shortcode_regex()для поиска.
onetrickpony
Как я уже сказал, «потенциальный» ответ, который я еще не проверял ... :-)
EAMann
Это в основном то же самое, что и метод 2, но он по-прежнему не проверяет шаблон для вызовов do_shortcode ().
Ян Данн
Зачем это нужно делать? Если вы позвоните do_shortcode()вручную в шаблоне, то вы уже знаете, что вы будете использовать
шорткод
Я не тот, кто вызывает шорткод, а пользователь. Это для распределенного плагина, а не частного.
Ян Данн
2

Используя комбинацию ответа TheDeadMedic и документации get_shortcode_regex () (которая фактически не нашла мои шорткоды), я создал простую функцию, используемую для постановки сценариев в очередь для нескольких шорткодов. Поскольку wp_enqueue_script () в шорткодах только добавляет в нижний колонтитул, это может быть полезно, поскольку оно может обрабатывать как сценарии верхнего, так и нижнего колонтитула.


function add_shortcode_scripts() {
    global $wp_query;   
    $posts = $wp_query->posts;
    $scripts = array(
        array(
            'handle' => 'map',
            'src' => 'http://maps.googleapis.com/maps/api/js?sensor=false',
            'deps' => '',
            'ver' => '3.0',
            'footer' => false
        ),
        array(
            'handle' => 'contact-form',
            'src' => get_template_directory_uri() . '/library/js/jquery.validate.min.js',
            'deps' => array( 'jquery' ),
            'ver' => '1.11.1',
            'footer' => true
        )   
    );

    foreach ( $posts as $post ) {
        foreach ( $scripts as $script ) {
            if ( preg_match( '#\[ *' . $script['handle'] . '([^\]])*\]#i', $post->post_content ) ) {
                // enqueue css and/or js
                if ( wp_script_is( $script['handle'], 'registered' ) ) {
                    return;
                } else {
                    wp_register_script( $script['handle'], $script['src'], $script['deps'], $script['ver'], $script['footer'] );
                    wp_enqueue_script( $script['handle'] );
                }
            }
        }
    }
}
add_action( 'wp', 'add_shortcode_scripts' );
Шон Мишо
источник
1

Наконец, я также нашел решение для условной загрузки CSS, которое работает для моего плагина www.mapsmarker.com, и я хотел бы поделиться с вами. Он проверяет, используется ли мой шорткод в текущем файле шаблона и header / footer.php, и, если да, помещает в таблицу необходимую таблицу стилей в заголовке:

  function prefix_template_check_shortcode( $template ) {
    $searchterm = '[mapsmarker';
    $files = array( $template, get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'header.php', get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'footer.php' );
    foreach( $files as $file ) {
        if( file_exists($file) ) {
            $contents = file_get_contents($file);
            if( strpos( $contents, $searchterm )  ) {
                wp_enqueue_style('
leafletmapsmarker', LEAFLET_PLUGIN_URL . 'leaflet-dist/leaflet.css');
                  break; 
            }
        }
    }
  return $template;
  }  
  add_action('template_include','prefix_template_check_shortcode' );
robertharm
источник
немного в стороне, но это не значит, что люди используют header.php и footer.php. Как насчет методов переноса тем, описанных в scribu.net/wordpress/theme-wrappers.html ? или такие темы, как корни, которые хранят свои части шаблона в другом месте?
orionrush
1

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

function abcd_load_my_shorcode_resources() {
       global $post, $wpdb;

       // determine whether this page contains "my_shortcode" 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' );
zdenekca
источник
0

потому что CSS должен быть объявлен внутри <head>

Для файлов CSS вы можете загрузить их в свой вывод шорткода:

<style type="text/css">
  @import "path/to/your.css"; 
</style>

Установите константу или что-то после этого, например MY_CSS_LOADED(включайте CSS, только если константа не установлена).

Оба ваших метода медленнее, чем идти по этому пути.

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

onetrickpony
источник
3
Загрузка CSS вне <head>элемента не является правильной разметкой. Правда, валидация - это всего лишь руководство, но если мы пытаемся придерживаться этого правила, это делает загрузку таблицы стилей в выводе шорткода плохой идеей.
EAMann
Встроенные блоки CSS являются допустимой разметкой, даже в том, что я помню, в XHTML. Нет причин не использовать их, когда у вас нет другой приемлемой альтернативы
onetrickpony
1
Согласно инструмент проверки W3C: <style type="text/css"> The element named above was found in a context where it is not allowed. This could mean that you have incorrectly nested elements -- such as a "style" element in the "body" section instead of inside "head". Таким образом, встроенные стили ( <element style="..."></element>) допустимы, а встроенные <style>элементы - нет.
EAMann
1
сделать его фильтруемым, и любой другой плагин или тема может делать с ним все что угодно. Если они настроят фильтр на возврат пустой строки - ничего не будет напечатано.
mfields
1
Вы не упомянули никаких объективных причин против этой практики. Во всяком случае, это не имеет значения; Здесь я вижу только два варианта: всегда загружать CSS / скрипты (оптимизировать их по размеру) или условные встроенные стили
onetrickpony
0

Script Logic - это плагин WordPress, который дает вам полный контроль над всеми файлами JavaScript и CSS. Используя этот плагин, вы можете условно загружать файлы CSS и JS только на те страницы, где это необходимо.

http://wordpress.org/plugins/script-logic/

Тахир Ясин
источник
Это не совсем относится к этому вопросу, который касается управления постановкой в ​​очередь из самого плагина. Ваш подход потребует от пользователя установить другой плагин, а затем понять, как правильно его настроить.
Ян Данн