Cargar Scripts / Estilos cuando el shortcode está presente
¿Cuál es la mejor manera de registrar/cargar scripts y/o estilos para usar en plugins?
Recientemente hice un plugin simple para añadir el avatar/gravatar del usuario con un shortcode. Tengo diferentes opciones de estilo para mostrar el avatar (cuadrado, redondo, etc.) y decidí poner el CSS directamente en el shortcode mismo.
Sin embargo, ahora me doy cuenta de que este no es un buen enfoque ya que repetirá el CSS cada vez que se use el shortcode en una página. He visto varios otros enfoques en este sitio y el codex de WordPress incluso tiene dos ejemplos propios, por lo que es difícil saber qué enfoque es más consistente y rápido.
Aquí están los métodos que conozco actualmente:
Método 1: Incluir directamente en el shortcode - Esto es lo que estoy haciendo actualmente en el plugin, pero no parece bueno ya que repite código.
class My_Shortcode {
function handle_shortcode( $atts, $content="" ) {
/* simplemente cargar o imprimir los scripts/estilos en el shortcode mismo */
?>
<style type="text/css">
</style>
<?php
return "$content";
}
}
add_shortcode( 'myshortcode', array( 'My_Shortcode', 'handle_shortcode' ) );
Método 2: Usar clase para cargar scripts o estilos condicionalmente
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;
// manejo del shortcode aquí
}
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();
Método 3: Usando 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' );
Método 4: Usando 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');

Encontré otra forma que funciona bien para mí:
Al inicializar el plugin, no encoles tus scripts y estilos, sino regístralos con
wp_register_style
ywp_register_script
.Luego puedes cargar el script/estilo bajo demanda. Por ejemplo, cuando renderizas un shortcode con
wp_enqueue_style("your_style")
ywp_enqueue_script("your_script")
.
Aquí tienes un ejemplo de plugin que usa este método y te permite usar get_avatar en un shortcode. La hoja de estilos solo se encola cuando el shortcode está presente.
Uso (el id por defecto es el usuario actual):
[get_avatar id="" size="32" default="mystery" alt="Foto de perfil" 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' );
}

Olvidé que hice esta pregunta el año pasado, pero de hecho también había comenzado a hacerlo de esta manera en muchos casos. Es como combinar el método 1 y 2.

Este método carga el CSS en el pie de página, lo cual seguramente tiene limitaciones, ¿no?

Esta es absolutamente la forma correcta de hacerlo. Usa este método cada vez que necesites cargar condicionalmente un script o hoja de estilo cuando el hook wp_enqueue_scripts
ya haya ocurrido. (Los shortcodes se analizan durante the_content
que es parte del hook the_post
, lo que significa que encolarlos cuando se analizan es demasiado tarde a menos que ya hayas registrado el script).

Antes de comenzar, debo decir que en este tema CSS y JS no son lo mismo.
La razón es simple: mientras que agregar JS al cuerpo de la página (en el footer) es una forma común y válida, el CSS debe colocarse en la sección <head>
de la página: aunque la mayoría de los navegadores pueden renderizar correctamente el CSS en el cuerpo de la página, eso no es código HTML válido.
Cuando se renderiza un shortcode, la sección <head>
ya se ha impreso, lo que significa que JS se puede agregar sin problemas en el footer, pero el CSS debe agregarse antes de que se renderice el shortcode.
Scripts
Si tu shortcode solo necesita JS, tienes suerte y puedes usar wp_enqueue_script
en el cuerpo de la función del shortcode:
add_shortcode( 'myshortcode', 'my_handle_shortcode' );
function my_handle_shortcode() {
wp_enqueue_script( 'myshortcodejs', '/path/to/js/file.js' );
// resto del código aquí...
}
De esta manera, tu script se agrega al footer y solo una vez, incluso si el shortcode se usa más de una vez en la página.
Estilos
Si tu código necesita estilos, entonces debes actuar antes de que el shortcode se renderice.
Existen diferentes formas de hacer esto:
Revisar todas las entradas en la consulta actual y agregar los estilos del shortcode si es necesario. Esto es lo que haces en los métodos #3 y #4 del OP. De hecho, ambos métodos hacen lo mismo, pero
has_shortcode
se agregó en WP 3.6, mientras queget_shortcode_regex
está disponible desde la versión 2.5, así que usaget_shortcode_regex
solo si deseas que tu plugin sea compatible con versiones antiguas.Agregar siempre los estilos del shortcode, en todas las páginas
Problemas
El problema con #1 es el rendimiento. Las expresiones regulares (regex) son operaciones bastante lentas y ejecutarlas en un bucle de todas las entradas puede ralentizar considerablemente la página. Además, es una tarea común en los temas mostrar solo el extracto de las entradas en los archivos y mostrar el contenido completo con shortcodes solo en las vistas individuales. Si eso ocurre, cuando se muestra un archivo, tu plugin ejecutará una coincidencia de regex en un bucle con el objetivo de agregar un estilo que nunca se usará: un impacto doble e innecesario en el rendimiento: ralentización de la generación de la página + solicitud HTTP adicional innecesaria.
El problema con #2 también es el rendimiento. Agregar estilos a todas las páginas significa agregar una solicitud HTTP adicional para todas las páginas, incluso cuando no es necesario. Aunque el tiempo de generación de la página en el servidor no se ve afectado, el tiempo total de renderizado de la página sí lo hará, y para todas las páginas del sitio.
Entonces, ¿qué debe hacer un desarrollador de plugins?
Creo que lo mejor es agregar una página de opciones al plugin, donde los usuarios puedan elegir si el shortcode debe manejarse solo en vistas individuales o también en archivos. En ambos casos, es mejor proporcionar otra opción para elegir para qué tipos de contenido habilitar el shortcode.
De esta manera, es posible enlazar "template_redirect"
para verificar si la consulta actual cumple con los requisitos y, en ese caso, agregar el estilo.
Si el usuario elige usar el shortcode solo en vistas individuales de entradas, es una buena idea verificar si la entrada tiene el shortcode o no: como solo se requiere una expresión regular, no debería ralentizar demasiado la página.
Si el usuario elige usar el shortcode incluso en archivos, entonces evitaría ejecutar regex para todas las entradas si el número de entradas es alto y simplemente encolar el estilo si la consulta cumple con los requisitos.
Lo que se considera "alto" en este sentido debe determinarse mediante algunas pruebas de rendimiento o, como alternativa, agregar otra opción y darle la elección a los usuarios.

Vale la pena mencionar aquí que las etiquetas <style>
en el cuerpo del documento ahora son válidas según la especificación W3C. https://www.w3.org/TR/html52/document-metadata.html#the-style-element

@IsaacLubow Actualmente (2023): Contextos en los que este elemento puede usarse: En un elemento noscript que sea hijo de un elemento head. Validé <style>
dentro de <body>
en https://validator.w3.org/nu/#textarea pero el resultado es Error: Elemento style
no permitido como hijo del elemento body
en este contexto.

Para mi plugin, descubrí que a veces los usuarios tienen un constructor de temas que almacena shortcodes en los metadatos del post. Esto es lo que uso para detectar si el shortcode de mi plugin está presente en el post actual o en sus metadatos:
function abcd_load_my_shorcode_resources() {
global $post, $wpdb;
// Determina si esta página contiene el shortcode "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 tiene una función incorporada para realizar acciones basadas en el estado de presentación de un Shortcode específico. El nombre de la función es has_shortcode()
. Puedes usar el siguiente código para encolar tus estilos y scripts.
Aquí utilicé is_a( $post, 'WP_Post' )
para verificar si el objeto $post
es de la clase WP_Post
y usé $post->post_content
para verificar el contenido del post.
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' );
}

Yo lo hago así:
class My_Shortcode {
function __construct() {
do_action('my_start_shortcode'); // llamada
....
y capturo el hook en otras funciones (u otros plugins):
function wpse_3232_load_script(){
wp_enqueue_script('js-myjs');
}
add_action('my_start_shortcode','wpse_3232_load_script',10);

Como alguien mencionó, usar 'has_shortcode' puede resolverlo. Aquí hay un ejemplo que encontré muy útil (Fuente: 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');

Podemos cargar condicionalmente archivos CSS y JS mediante shortcodes sin necesidad de usar funciones excesivamente complejas:
Primero, asumamos que hemos registrado todos nuestros archivos CSS/JS previamente (quizás cuando se ejecutó 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 );
A continuación, examinemos nuestra función de shortcode:
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'); }
...
}
Sencillo. Listo. Ergo: podemos "cargar condicionalmente" archivos css/js (solo cuando se ejecuta el [shortcode]).
Consideremos la siguiente ideología:
- Queremos cargar ciertos archivos CSS/JS (pero solo cuando se active el shortcode)
- Quizás no queremos que esos archivos se carguen en todas las páginas, o que no se carguen si el shortcode no se usa en una página/entrada. (ligero)
- Podemos lograrlo asegurándonos de que WP registre TODOS los archivos css/js antes de llamar al shortcode y antes de encolarlos.
- Ejemplo: Quizás durante mi función
prep_css_and_js()
cargo TODOS los archivos .css/.js que están en ciertas carpetas...
Ahora que WP los ve como scripts registrados, podemos llamarlos condicionalmente, basándonos en diversas situaciones, incluyendo pero no limitado a my_shortcode_func()
Beneficios: (sin MySQL, sin funciones avanzadas y sin necesidad de filtros)
- esto es "ortodoxo en WP", elegante, de carga rápida, fácil y simple
- permite que los compresores/minificadores detecten dichos scripts
- usa las funciones de WP (wp_register_style/wp_register_script)
- compatible con más temas/plugins que podrían querer desregistrar esos scripts por diversas razones.
- no se activará múltiples veces
- muy poco procesamiento o código involucrado
Referencias:

Descubrí para mi propio plugin cómo verificar si el shortcode está presente en el widget de texto.
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] ) )
{
// El shortcode 'myshortcode' está siendo usado
// Cargar mi CSS y JS
/****** Cargar RFNB ******/
wp_enqueue_style('css-mycss');
wp_enqueue_script('js-myjs');
// OPCIONAL: PERMITIR QUE EL SHORTCODE FUNCIONE EN WIDGETS DE TEXTO
add_filter( 'widget_text', 'shortcode_unautop');
add_filter( 'widget_text', 'do_shortcode');
}
return $text;
}
add_filter('widget_text', 'check_shortcode');

Hice una combinación del código de ejemplo de la página de Wordpress para has_shortcode() y la respuesta que zndencka proporcionó. Noté que la función has_shortcode se agregó en Wordpress 3.6, por eso primero verifico si la función existe. Tal vez esa verificación sea un poco obsoleta, ya que no hay muchos usuarios con versiones inferiores a WordPress 3.6 según sus propias estadísticas.
// Esto asegura que el estilo ya esté encolado en el encabezado, antes de que el shortcode se cargue en la página/publicación
function enqueue_shortcode_header_script() {
global $post;
if ( function_exists( 'has_shortcode' ) ){ // se agregó en wordpress 3.6
// Fuente: 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) ) { // Un pequeño porcentaje de usuarios de WordPress está por debajo de 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');

Utilizo WordPress versión 5.4 con un estilo de código OOP, no sé si esto afecta por qué ninguna de las soluciones anteriores funcionó para mí, así que llegué a esta solución:
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, '[TU_SHORTCODE]'),true));
if ( is_a( $post, 'WP_Post' ) && strpos($post->post_content, '[TU_SHORTCODE]') )
{
wp_register_style('my-css', $_css_url);
wp_register_script('my-js', $_js_url);
}
}
Espero que esto ayude a alguien.
