Created
September 21, 2025 09:11
-
-
Save paperplanefactory/c864bb8896fcf4226e96612f5071982c to your computer and use it in GitHub Desktop.
Sistema di gestione transient per WordPress - Cache automatico per campi ACF con supporto multilingua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Sistema di gestione transient per tema PaperPlane | |
| * Ottimizza le performance cachando i dati ACF usando transient WordPress | |
| */ | |
| global $use_transients_fields; | |
| $use_transients_fields = get_field( 'use_transients_fields', 'option' ); | |
| /** | |
| * Configura il supporto multilingua per il sistema di transient | |
| * | |
| * @return array Array degli slug delle lingue disponibili o 'any-lang' per siti monolingua | |
| */ | |
| function paperplane_multilang_setup() { | |
| // Verifica se Polylang è attivo e restituisce array degli slug delle lingue disponibili | |
| if ( function_exists( 'PLL' ) ) { | |
| $langs_parameters = array( | |
| 'hide_if_empty' => 0, | |
| 'fields' => 'slug' | |
| ); | |
| $languages = pll_languages_list( $langs_parameters ); | |
| } | |
| // Imposta parametro di fallback per siti monolingua | |
| else { | |
| $languages = array( 'any-lang' ); | |
| } | |
| return $languages; | |
| } | |
| /** | |
| * Recupera i campi ACF di un contenuto usando transient cache | |
| * | |
| * @param int $content_id ID del post/pagina | |
| * @return array|false Campi ACF del contenuto | |
| */ | |
| function paperplane_content_transients( $content_id ) { | |
| global $use_transients_fields; | |
| if ( $use_transients_fields == 1 ) { | |
| // Tentativo di recupero dal cache transient | |
| $content_fields_transient = get_transient( 'paperplane_transient_content_fields_' . $content_id ); | |
| if ( empty( $content_fields_transient ) ) { | |
| // Cache miss: recupera dal database e salva in transient | |
| $content_fields = get_fields( $content_id ); | |
| set_transient( 'paperplane_transient_content_fields_' . $content_id, $content_fields, DAY_IN_SECONDS * 4 ); | |
| } else { | |
| // Cache hit: usa dati dal transient | |
| $content_fields = $content_fields_transient; | |
| } | |
| } else { | |
| // Transient disabilitati: recupera sempre dal database | |
| $content_fields = get_fields( $content_id ); | |
| } | |
| return $content_fields; | |
| } | |
| /** | |
| * Pre-genera transient per i campi ACF durante il salvataggio del post | |
| * Evita il carico della prima richiesta dopo il salvataggio | |
| * | |
| * @param int $content_id ID del post salvato | |
| */ | |
| function paperplane_content_transients_generation( $content_id ) { | |
| global $use_transients_fields; | |
| if ( $use_transients_fields == 1 ) { | |
| // Evita l'esecuzione durante il salvataggio automatico per prestazioni | |
| if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { | |
| return; | |
| } | |
| // Crea transient solo se non esiste già | |
| $content_fields_transient = get_transient( 'paperplane_transient_content_fields_' . $content_id ); | |
| if ( empty( $content_fields_transient ) ) { | |
| $content_fields = get_fields( $content_id ); | |
| set_transient( 'paperplane_transient_content_fields_' . $content_id, $content_fields, DAY_IN_SECONDS * 4 ); | |
| } | |
| } | |
| } | |
| add_action( 'save_post', 'paperplane_content_transients_generation', 20, 3 ); | |
| /** | |
| * Recupera le opzioni ACF globali usando transient cache | |
| * | |
| * @return array|false Campi delle opzioni ACF | |
| */ | |
| function paperplane_options_transients() { | |
| global $use_transients_fields; | |
| if ( $use_transients_fields == 1 ) { | |
| // Tentativo di recupero dal cache | |
| $paperplane_transient_options_fields_ = get_transient( 'paperplane_transient_options_fields_' ); | |
| if ( empty( $paperplane_transient_options_fields_ ) ) { | |
| // Cache miss: recupera e salva | |
| $options_fields = get_fields( 'options' ); | |
| set_transient( 'paperplane_transient_options_fields_', $options_fields, DAY_IN_SECONDS * 4 ); | |
| } else { | |
| // Cache hit: usa transient | |
| $options_fields = $paperplane_transient_options_fields_; | |
| } | |
| return $options_fields; | |
| } else { | |
| // Bypass cache: accesso diretto al database | |
| $options_fields = get_fields( 'options' ); | |
| return $options_fields; | |
| } | |
| } | |
| /** | |
| * Recupera opzioni ACF specifiche per lingua usando transient cache | |
| * Utilizzato con plugin multilingua come Polylang | |
| * | |
| * @param string $acf_options_parameter Parametro linguaggio (es. 'options_en', 'options_it') | |
| * @return array|false Campi delle opzioni per la lingua specifica | |
| */ | |
| function paperplane_options_transients_multilanguage( $acf_options_parameter ) { | |
| global $use_transients_fields; | |
| if ( $use_transients_fields == 1 ) { | |
| // Cache key specifico per lingua | |
| $options_fields_multilang_transient = get_transient( 'paperplane_transient_options_fields_multilang_' . $acf_options_parameter ); | |
| if ( empty( $options_fields_multilang_transient ) ) { | |
| // Recupera dati specifici lingua dal database | |
| $options_fields_multilang = get_fields( $acf_options_parameter ); | |
| set_transient( 'paperplane_transient_options_fields_multilang_' . $acf_options_parameter, $options_fields_multilang, DAY_IN_SECONDS * 4 ); | |
| } else { | |
| $options_fields_multilang = $options_fields_multilang_transient; | |
| } | |
| return $options_fields_multilang; | |
| } else { | |
| $options_fields_multilang = get_fields( $acf_options_parameter ); | |
| return $options_fields_multilang; | |
| } | |
| } | |
| /** | |
| * Invalida tutti i transient PaperPlane quando un post viene salvato | |
| * Strategia aggressiva per garantire coerenza dei dati | |
| * | |
| * @param int $post_id ID del post | |
| * @param object $post Oggetto post | |
| * @param bool $update True se è un aggiornamento, false se nuovo post | |
| */ | |
| function paperplane_delete_content_transients( $post_id, $post, $update ) { | |
| // Invalida tutti i tipi di transient per sicurezza | |
| delete_transients_with_prefix( 'paperplane_transient_content_fields_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_query_modals_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_query_mega_menus' ); | |
| delete_transients_with_prefix( 'paperplane_transient_options_fields_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_options_fields_multilang_' ); | |
| } | |
| add_action( 'save_post', 'paperplane_delete_content_transients', 10, 3 ); | |
| add_action( 'wp_trash_post', 'paperplane_delete_content_transients_on_post_delete', 10 ); | |
| add_action( 'delete_post', 'paperplane_delete_content_transients_on_post_delete', 10 ); | |
| /** | |
| * Invalida transient quando un post viene eliminato definitivamente | |
| * Versione semplificata senza parametri del post | |
| */ | |
| function paperplane_delete_content_transients_on_post_delete() { | |
| delete_transients_with_prefix( 'paperplane_transient_content_fields_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_query_modals_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_query_mega_menus' ); | |
| delete_transients_with_prefix( 'paperplane_transient_options_fields_' ); | |
| delete_transients_with_prefix( 'paperplane_transient_options_fields_multilang_' ); | |
| } | |
| /** | |
| * Invalida transient delle opzioni quando vengono salvate le pagine opzioni ACF | |
| * Hook specifico per ACF che si attiva solo al salvataggio opzioni | |
| */ | |
| function paperplane_delete_option_pages_transients() { | |
| // Rimuove transient opzioni generali | |
| delete_transient( 'paperplane_transient_options_fields_' ); | |
| // Rimuove transient per tutte le lingue disponibili | |
| $languages = paperplane_multilang_setup(); | |
| foreach ( $languages as $language ) { | |
| delete_transient( 'paperplane_transient_options_fields_multilang_' . $language ); | |
| } | |
| } | |
| add_action( 'acf/save_post', 'paperplane_delete_option_pages_transients', 20 ); | |
| /** | |
| * Cancella tutti i transient che iniziano con un prefisso specifico | |
| * | |
| * @param string $prefix Prefisso dei transient da eliminare | |
| */ | |
| function delete_transients_with_prefix( $prefix ) { | |
| foreach ( get_transient_keys_with_prefix( $prefix ) as $key ) { | |
| delete_transient( $key ); | |
| } | |
| } | |
| /** | |
| * Trova tutte le chiavi transient nel database che iniziano con un prefisso | |
| * | |
| * @param string $prefix Prefisso da cercare | |
| * @return array Array delle chiavi transient trovate (senza prefisso _transient_) | |
| */ | |
| function get_transient_keys_with_prefix( $prefix ) { | |
| global $wpdb; | |
| $prefix = $wpdb->esc_like( '_transient_' . $prefix ); | |
| $sql = "SELECT `option_name` FROM $wpdb->options WHERE `option_name` LIKE '%s'"; | |
| $keys = $wpdb->get_results( $wpdb->prepare( $sql, $prefix . '%' ), ARRAY_A ); | |
| if ( is_wp_error( $keys ) ) { | |
| return []; | |
| } | |
| return array_map( function ( $key ) { | |
| // Rimuove il prefisso '_transient_' dal nome dell'opzione per uso con delete_transient() | |
| return ltrim( $key['option_name'], '_transient_' ); | |
| }, $keys ); | |
| } | |
| /** | |
| * Programma l'evento cron giornaliero per la pulizia automatica dei transient | |
| * Esecuzione fissata alle 05:00 per minimizzare l'impatto sulle prestazioni | |
| */ | |
| function paperplane_schedule_transient_cleanup() { | |
| if ( ! wp_next_scheduled( 'paperplane_cleanup_transients' ) ) { | |
| // Calcola il prossimo timestamp per le 05:00 | |
| $next_5am = strtotime( 'today 05:00' ); | |
| if ( $next_5am <= time() ) { | |
| $next_5am = strtotime( 'tomorrow 05:00' ); | |
| } | |
| wp_schedule_event( $next_5am, 'daily', 'paperplane_cleanup_transients' ); | |
| } | |
| } | |
| add_action( 'wp', 'paperplane_schedule_transient_cleanup' ); | |
| /** | |
| * Esegue la pulizia automatica dei transient scaduti e orfani | |
| * Chiamata dal cron job giornaliero alle 05:00 | |
| */ | |
| function paperplane_auto_cleanup_transients() { | |
| global $wpdb; | |
| // Log di inizio pulizia (solo se WP_DEBUG attivo) | |
| if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | |
| error_log( 'PaperPlane: Avvio pulizia automatica transient' ); | |
| } | |
| // Query per trovare transient scaduti confrontando timestamp timeout con ora corrente | |
| $expired_transients = $wpdb->get_col( $wpdb->prepare( " | |
| SELECT REPLACE(option_name, '_transient_timeout_', '') | |
| FROM {$wpdb->options} | |
| WHERE option_name LIKE %s | |
| AND option_value < %d | |
| ", '_transient_timeout_paperplane_transient_%', time() ) ); | |
| $cleaned_count = 0; | |
| foreach ( $expired_transients as $transient_key ) { | |
| if ( delete_transient( $transient_key ) ) { | |
| $cleaned_count++; | |
| } | |
| } | |
| // Trova e rimuove transient "orfani" (senza record di timeout corrispondente) | |
| // Questo può accadere quando i timeout vengono eliminati ma non i dati transient | |
| $orphaned_transients = $wpdb->get_col( $wpdb->prepare( " | |
| SELECT REPLACE(option_name, '_transient_', '') | |
| FROM {$wpdb->options} t1 | |
| WHERE option_name LIKE %s | |
| AND NOT EXISTS ( | |
| SELECT 1 FROM {$wpdb->options} t2 | |
| WHERE t2.option_name = CONCAT('_transient_timeout_', REPLACE(t1.option_name, '_transient_', '')) | |
| ) | |
| ", '_transient_paperplane_transient_%' ) ); | |
| foreach ( $orphaned_transients as $transient_key ) { | |
| if ( delete_transient( $transient_key ) ) { | |
| $cleaned_count++; | |
| } | |
| } | |
| // Log del risultato della pulizia (solo se elementi rimossi e debug attivo) | |
| if ( defined( 'WP_DEBUG' ) && WP_DEBUG && $cleaned_count > 0 ) { | |
| error_log( "PaperPlane: Rimosse {$cleaned_count} transient scadute/orfane" ); | |
| } | |
| // Ottimizzazione tabella database se rimosse molte transient (migliora prestazioni) | |
| if ( $cleaned_count > 50 ) { | |
| $wpdb->query( "OPTIMIZE TABLE {$wpdb->options}" ); | |
| } | |
| } | |
| add_action( 'paperplane_cleanup_transients', 'paperplane_auto_cleanup_transients' ); | |
| /** | |
| * Pulisce tutti gli eventi cron quando il tema/plugin viene disattivato | |
| * Evita l'accumulo di cron jobs orfani nel sistema | |
| */ | |
| function paperplane_deactivate_cleanup() { | |
| wp_clear_scheduled_hook( 'paperplane_cleanup_transients' ); | |
| } | |
| // NOTA: Decommentare e collegare all'hook di disattivazione del tema/plugin quando necessario | |
| // register_deactivation_hook( __FILE__, 'paperplane_deactivate_cleanup' ); | |
| /* | |
| ====================================================================== | |
| ESEMPI DI UTILIZZO DELLE FUNZIONI TRANSIENT | |
| ====================================================================== | |
| */ | |
| /** | |
| * ESEMPIO 1: paperplane_content_transients() | |
| * Recupera campi ACF di un post/pagina con cache automatico | |
| */ | |
| /* | |
| // In single.php, page.php o nei template | |
| global $post; | |
| // Recupera tutti i campi ACF del post corrente | |
| $campi_post = paperplane_content_transients( $post->ID ); | |
| // Uso dei campi recuperati | |
| if ( $campi_post ) { | |
| $titolo_hero = $campi_post['titolo_hero'] ?? ''; | |
| $immagine_featured = $campi_post['immagine_featured'] ?? ''; | |
| $contenuto_extra = $campi_post['contenuto_extra'] ?? ''; | |
| // Output | |
| if ( $titolo_hero ) { | |
| echo '<h2>' . esc_html( $titolo_hero ) . '</h2>'; | |
| } | |
| } | |
| // Per un post specifico (es. homepage ID 15) | |
| $campi_homepage = paperplane_content_transients( 15 ); | |
| // Per la pagina corrente in un template | |
| $campi_corrente = paperplane_content_transients( get_the_ID() ); | |
| // Per un post in un loop | |
| while ( have_posts() ) { | |
| the_post(); | |
| $campi_loop = paperplane_content_transients( get_the_ID() ); | |
| // ... usa i campi | |
| } | |
| */ | |
| /** | |
| * ESEMPIO 2: paperplane_options_transients() | |
| * Recupera opzioni globali ACF con cache | |
| */ | |
| /* | |
| // In header.php | |
| $opzioni = paperplane_options_transients(); | |
| if ( $opzioni ) { | |
| $logo_sito = $opzioni['logo_sito'] ?? ''; | |
| $colore_tema = $opzioni['colore_tema'] ?? '#000000'; | |
| $menu_principale = $opzioni['menu_principale'] ?? ''; | |
| // Uso nel template | |
| if ( $logo_sito ) { | |
| echo '<img src="' . esc_url( $logo_sito['url'] ) . '" alt="Logo">'; | |
| } | |
| // CSS dinamico | |
| echo '<style>:root { --colore-tema: ' . esc_attr( $colore_tema ) . '; }</style>'; | |
| } | |
| // In footer.php | |
| $opzioni = paperplane_options_transients(); | |
| $copyright = $opzioni['testo_copyright'] ?? ''; | |
| $social_links = $opzioni['social_links'] ?? array(); | |
| $indirizzo = $opzioni['indirizzo_azienda'] ?? ''; | |
| echo '<footer>'; | |
| if ( $copyright ) { | |
| echo '<p>' . esc_html( $copyright ) . '</p>'; | |
| } | |
| echo '</footer>'; | |
| */ | |
| /** | |
| * ESEMPIO 3: paperplane_options_transients_multilanguage() | |
| * Recupera opzioni specifiche per lingua (con Polylang) | |
| */ | |
| /* | |
| // In functions.php per siti multilingua | |
| if ( function_exists( 'pll_current_language' ) ) { | |
| $lingua_corrente = pll_current_language(); | |
| // Recupera opzioni per la lingua corrente | |
| $opzioni_lingua = paperplane_options_transients_multilanguage( 'options_' . $lingua_corrente ); | |
| if ( $opzioni_lingua ) { | |
| $titolo_sito = $opzioni_lingua['titolo_sito'] ?? ''; | |
| $descrizione = $opzioni_lingua['descrizione_sito'] ?? ''; | |
| $contatti = $opzioni_lingua['info_contatti'] ?? ''; | |
| // Uso nel template | |
| echo '<h1>' . esc_html( $titolo_sito ) . '</h1>'; | |
| echo '<p>' . esc_html( $descrizione ) . '</p>'; | |
| } | |
| } | |
| // Esempio con gestione fallback | |
| $lingua = pll_current_language() ?? 'it'; // Default italiano | |
| // Prova con lingua specifica | |
| $opzioni = paperplane_options_transients_multilanguage( 'options_' . $lingua ); | |
| // Se non trova nulla, usa opzioni generali | |
| if ( empty( $opzioni ) ) { | |
| $opzioni = paperplane_options_transients(); | |
| } | |
| */ | |
| /** | |
| * ESEMPIO 4: Uso in query personalizzate e loop complessi | |
| */ | |
| /* | |
| // Loop personalizzato con cache ACF | |
| $posts_query = new WP_Query( array( | |
| 'post_type' => 'prodotto', | |
| 'posts_per_page' => 6, | |
| 'meta_key' => 'in_evidenza', | |
| 'meta_value' => '1' | |
| ) ); | |
| if ( $posts_query->have_posts() ) { | |
| echo '<div class="prodotti-evidenza">'; | |
| while ( $posts_query->have_posts() ) { | |
| $posts_query->the_post(); | |
| // Recupera campi ACF con cache | |
| $campi_prodotto = paperplane_content_transients( get_the_ID() ); | |
| $prezzo = $campi_prodotto['prezzo'] ?? ''; | |
| $immagine_gallery = $campi_prodotto['gallery_prodotto'] ?? array(); | |
| $caratteristiche = $campi_prodotto['caratteristiche'] ?? array(); | |
| echo '<div class="prodotto-card">'; | |
| echo '<h3>' . get_the_title() . '</h3>'; | |
| if ( $prezzo ) { | |
| echo '<p class="prezzo">€ ' . esc_html( $prezzo ) . '</p>'; | |
| } | |
| echo '</div>'; | |
| } | |
| echo '</div>'; | |
| wp_reset_postdata(); | |
| } | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment