Caricare Script / Stili quando è presente uno shortcode
Qual è il modo migliore per registrare/accodare script e/o stili nei plugin?
Recentemente ho creato un plugin semplice per aggiungere l'avatar/gravatar dell'utente con uno shortcode. Ho diverse opzioni di stile per visualizzare l'avatar (quadrato, rotondo, ecc.) e ho deciso di inserire il css direttamente nello shortcode stesso.
Tuttavia, ora mi rendo conto che questo non è un buon approccio poiché ripeterà il css ogni volta che lo shortcode viene utilizzato in una pagina. Ho visto diversi altri approcci su questo sito e il codex wp ha persino due esempi propri, quindi è difficile sapere quale approccio sia più coerente e veloce.
Ecco i metodi di cui sono attualmente a conoscenza:
Metodo 1: Inclusione diretta nello shortcode - Questo è quello che sto facendo attualmente nel plugin, ma non sembra buono poiché ripete il codice.
class My_Shortcode {
function handle_shortcode( $atts, $content="" ) {
/* semplicemente accoda o stampa gli script/stili direttamente nello shortcode */
?>
<style type="text/css">
</style>
<?php
return "$content";
}
}
add_shortcode( 'myshortcode', array( 'My_Shortcode', 'handle_shortcode' ) );
Metodo 2: Utilizzo della classe per accodare script o stili condizionalmente
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;
// gestione shortcode qui
}
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();
Metodo 3: Utilizzo di 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' );
Metodo 4: Utilizzo di 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');

Ho trovato un altro metodo che funziona bene per me:
Durante l'inizializzazione del plugin, invece di accodare gli script e gli stili, registrali con
wp_register_style
ewp_register_script
.Successivamente puoi caricare lo script/style on demand. Ad esempio quando renderizzi uno shortcode con
wp_enqueue_style("your_style")
ewp_enqueue_script("your_script")
.
Ecco un esempio di plugin che utilizza questo metodo e ti permette di usare get_avatar in uno shortcode. Il foglio di stile viene accodato solo quando è presente lo shortcode.
Utilizzo (l'id di default è l'utente corrente):
[get_avatar id="" size="32" default="mystery" alt="Foto Profilo" 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' );
}

Ho dimenticato di aver fatto questa domanda l'anno scorso, ma in realtà avevo già iniziato a fare così in molti casi. È un po' come combinare il metodo 1 e il 2.

Questo metodo carica il CSS nel footer, il che sicuramente ha delle limitazioni?

Questo è assolutamente il modo giusto per farlo. Usa questo metodo ogni volta che hai bisogno di caricare condizionalmente uno script o un foglio di stile quando l'hook wp_enqueue_scripts
è già avvenuto. (Gli shortcode vengono analizzati durante the_content
che fa parte dell'hook the_post
, il che significa che accodarli quando vengono analizzati è troppo tardi a meno che non hai già registrato lo script)

Prima di iniziare a rispondere, devo dire che riguardo a questo argomento CSS e JS non sono la stessa cosa.
Il motivo è semplice: mentre aggiungere JS nel corpo della pagina (nel footer) è un modo comune e valido di procedere, il CSS deve essere posizionato nella sezione <head>
della pagina: anche se la maggior parte dei browser può visualizzare correttamente il CSS nel corpo della pagina, non è un codice HTML valido.
Quando uno shortcode viene renderizzato, la sezione <head>
è già stata stampata, il che significa che JS può essere aggiunto senza problemi nel footer, ma il CSS deve essere aggiunto prima che lo shortcode venga renderizzato.
Script
Se il tuo shortcode necessita solo di JS, sei fortunato e puoi semplicemente usare wp_enqueue_script
nel corpo della callback dello shortcode:
add_shortcode( 'myshortcode', 'my_handle_shortcode' );
function my_handle_shortcode() {
wp_enqueue_script( 'myshortcodejs', '/percorso/al/file/js.js' );
// resto del codice qui...
}
In questo modo il tuo script viene aggiunto nel footer e solo una volta, anche se lo shortcode è utilizzato più volte nella pagina.
Stili
Se il tuo codice necessita di stili, allora devi agire prima che lo shortcode venga effettivamente renderizzato.
Ci sono diversi modi per farlo:
esamina tutti i post nella query corrente e aggiungi gli stili dello shortcode se necessario. Questo è ciò che fai sia nel metodo #3 che #4 nell'OP. In effetti, entrambi i metodi fanno la stessa cosa, ma
has_shortcode
è stato aggiunto in WP 3.6, mentreget_shortcode_regex
è disponibile dalla versione 2.5, quindi usaget_shortcode_regex
solo se vuoi rendere il tuo plugin compatibile con versioni più vecchie.aggiungi sempre lo stile dello shortcode, in tutte le pagine
Problemi
Il problema con #1 è la performance. Le regex sono operazioni piuttosto lente e lanciare regex in un loop di tutti i post può rallentare consistentemente la pagina. Inoltre, è un compito abbastanza comune nei temi mostrare solo l'estratto del post negli archivi e mostrare il contenuto completo con gli shortcode solo nelle visualizzazioni singole. Se ciò accade, quando viene mostrato un archivio, il tuo plugin lancerà un matching di regex in un loop con l'obiettivo di aggiungere uno stile che non verrà mai utilizzato: un doppio impatto sulla performance non necessario: rallentamento della generazione della pagina + richiesta HTTP aggiuntiva non necessaria.
Il problema con #2 è ancora la performance. Aggiungere stili a tutte le pagine significa aggiungere una richiesta HTTP aggiuntiva per tutte le pagine, anche quando non è necessario. Anche se il tempo di generazione della pagina lato server non è influenzato, il tempo totale di rendering della pagina lo sarà, e per tutte le pagine del sito.
Quindi, cosa dovrebbe fare uno sviluppatore di plugin?
Penso che la cosa migliore da fare sia aggiungere una pagina di opzioni al plugin, dove gli utenti possono scegliere se lo shortcode debba essere gestito solo nelle visualizzazioni singole o anche negli archivi. In entrambi i casi è meglio fornire un'altra opzione per scegliere per quali tipi di post abilitare lo shortcode.
In questo modo è possibile agganciarsi a "template_redirect"
per verificare se la query corrente soddisfa i requisiti e in quel caso aggiungere lo stile.
Se l'utente sceglie di usare lo shortcode solo nelle visualizzazioni singole dei post, è una buona idea verificare se il post ha lo shortcode o meno: visto che è richiesta solo una regex, non dovrebbe rallentare troppo la pagina.
Se l'utente sceglie di usare lo shortcode anche negli archivi, allora eviterei di eseguire regex per tutti i post se il numero di post è elevato e aggiungerei semplicemente lo stile se la query soddisfa i requisiti.
Cosa considerare "elevato" in questo caso dovrebbe essere determinato tramite alcuni test di performance o, in alternativa, aggiungere un'altra opzione e dare la scelta agli utenti.

Vale la pena notare qui che i tag <style>
nel body del documento sono ora validi secondo le specifiche W3C. https://www.w3.org/TR/html52/document-metadata.html#the-style-element

@IsaacLubow Attualmente (2023): Contesti in cui questo elemento può essere usato: In un elemento noscript che è figlio di un elemento head. Ho validato <style>
all'interno di <body>
su https://validator.w3.org/nu/#textarea ma il risultato è Errore: Elemento style
non consentito come figlio dell'elemento body
in questo contesto.

Per il mio plugin ho scoperto che a volte gli utenti hanno un tema builder che memorizza gli shortcode nei metadati del post. Ecco cosa utilizzo per rilevare se lo shortcode del mio plugin è presente nell'attuale post o nei metadati del post:
function abcd_load_my_shorcode_resources() {
global $post, $wpdb;
// determina se questa pagina contiene lo 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 ha una funzione integrata per eseguire azioni in base alla presenza di un determinato Shortcode. Il nome della funzione è has_shortcode()
. Puoi utilizzare il seguente codice per accodare i tuoi stili e script.
Qui ho usato is_a( $post, 'WP_Post' )
per verificare se l'oggetto $post
appartiene alla classe WP_Post
e ho utilizzato $post->post_content
per controllare il contenuto 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' );
}

Faccio così:
class My_Shortcode {
function __construct() {
do_action('my_start_shortcode'); // chiama l'azione
....
e intercetto l'hook in altre funzioni (o altri plugin):
function wpse_3232_load_script(){
wp_enqueue_script('js-myjs');
}
add_action('my_start_shortcode','wpse_3232_load_script',10);

Come qualcuno ha menzionato, usare 'has_shortcode' funzionerà. Ecco l'esempio che ho trovato molto utile (Fonte: 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');

Possiamo infatti caricare condizionalmente file CSS e JS tramite shortcode senza utilizzare funzioni eccessivamente elaborate:
Innanzitutto, supponiamo di aver registrato tutti i nostri file CSS/JS precedentemente (magari quando è stato eseguito wp_enqueue_scripts
)
function prep_css_e_js() {
wp_register_style('my-css', $_css_url);
wp_register_script('my-js', $_js_url);
}
add_action( 'wp_enqueue_scripts', 'prep_css_e_js', 5 );
Successivamente, esaminiamo la nostra funzione dello shortcode:
function mio_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'); }
...
}
Semplice. Fatto. Ergo: possiamo "caricare condizionalmente" file css/js, (solo quando viene eseguito [shortcode]).
Consideriamo la seguente ideologia:
- Vogliamo caricare determinati file CSS/JS (ma solo quando viene attivato lo shortcode)
- Forse non vogliamo che quei file vengano caricati su ogni pagina, o del tutto se lo shortcode non viene utilizzato in una pagina/post. (leggero)
- Possiamo ottenere questo risultato assicurandoci che WP registri TUTTI i file css/js prima di chiamare lo shortcode e prima dell'enqueue.
- IE: Magari durante la mia funzione
prep_css_e_js()
carico TUTTI i file .css/.js che si trovano in determinate cartelle...
Ora che WP li vede come script registrati, possiamo effettivamente chiamarli, condizionalmente, in base a una varietà di situazioni, tra cui ma non limitato a mio_shortcode_func()
Vantaggi: (nessun MySQL, nessuna funzione avanzata e nessun filtro necessario)
- è "ortodosso per WP", elegante, a caricamento veloce, facile e semplice
- consente ai compilatori/minifier di rilevare tali script
- utilizza le funzioni WP (wp_register_style/wp_register_script)
- compatibile con più temi/plugin che potrebbero voler deregistrare quegli script per una varietà di motivi.
- non si attiverà più volte
- viene coinvolto pochissimo codice o elaborazione
Riferimenti:

Ho scoperto per il mio plugin, se lo shortcode è presente nel widget di testo.
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] ) )
{
// myshortcode è in uso
// carica i miei file css e js
/****** Carica RFNB ******/
wp_enqueue_style('css-mycss');
wp_enqueue_script('js-myjs');
// OPZIONALE PERMETTI AGLI SHORTCODE DI FUNZIONARE NEL WIDGET DI TESTO
add_filter( 'widget_text', 'shortcode_unautop');
add_filter( 'widget_text', 'do_shortcode');
}
return $text;
}
add_filter('widget_text', 'check_shortcode');

Ho combinato il codice di esempio dalla pagina di Wordpress per has_shortcode() e la risposta data da zndencka. Ho notato che la funzione has_shortcode è stata aggiunta in Wordpress 3.6, motivo per cui controllo prima se la funzione esiste. Forse questo controllo è un po' obsoleto, dato che non ci sono molti utenti con versioni inferiori a Wordpress 3.6 secondo le statistiche ufficiali di Wordpress.
// Questo assicura che lo stile sia già accodato nell'header, prima che lo shortcode venga caricato nella pagina/post
function enqueue_shortcode_header_script() {
global $post;
if ( function_exists( 'has_shortcode' ) ){ // aggiunto in wordpress 3.6
// Fonte: 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) ) { // Piccola percentuale di utenti wordpress è sotto la 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');

Utilizzo WordPress versione 5.4 con uno stile di codice OOP, non so se questo influisce sul motivo per cui nessuna delle soluzioni sopra indicate ha funzionato per me, quindi ho trovato questa soluzione:
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);
}
}
Spero che questo possa aiutare qualcuno.
