MenuFacile2.0

Shortcode WordPressDEV

Mostra il menu MenuFacile su una pagina WordPress con shortcode + cache.

Obiettivo: una pagina WordPress che mostra il menu del ristorante leggendolo dall'API MenuFacile, con cache server-side per ridurre il numero di chiamate e tenere i tempi di risposta sotto i 100ms.

Caso d'uso

Il cliente ha già un sito WordPress (template Astra, Divi, Avada, qualsiasi). Vuole una pagina "Il nostro menu" che mostri il menu live, senza duplicare i piatti su WordPress (così quando cambia un piatto sul pannello MenuFacile, il sito si aggiorna in 5 minuti senza copia-incolla).

Codice

Aggiungi al functions.php del tema (o meglio in un plugin custom):

<?php
/**
 * MenuFacile shortcode for WordPress.
 * Usage: [menufacile tenant="ducatarocco" locale="it"]
 */

function menufacile_render_menu($atts) {
    $atts = shortcode_atts([
        'tenant' => '',
        'locale' => 'it',
        'cache_minutes' => 5,
    ], $atts, 'menufacile');

    if (empty($atts['tenant'])) {
        return '<!-- MenuFacile: tenant attribute mancante -->';
    }

    $tenant = sanitize_key($atts['tenant']);
    $locale = sanitize_key($atts['locale']);
    $cache_key = "mf_menu_{$tenant}_{$locale}";

    $data = get_transient($cache_key);

    if (false === $data) {
        $url = sprintf(
            'https://%s.menufacile.it/api/v1/menu/%s',
            $tenant,
            $locale
        );
        $resp = wp_remote_get($url, [
            'timeout' => 5,
            'headers' => ['Accept' => 'application/json'],
        ]);

        if (is_wp_error($resp) || 200 !== wp_remote_retrieve_response_code($resp)) {
            return '<p class="mf-error">Menu temporaneamente non disponibile.</p>';
        }

        $body = json_decode(wp_remote_retrieve_body($resp), true);
        $data = $body['data'] ?? null;

        if ($data) {
            set_transient(
                $cache_key,
                $data,
                absint($atts['cache_minutes']) * MINUTE_IN_SECONDS
            );
        }
    }

    if (!$data) {
        return '<p class="mf-error">Menu non disponibile.</p>';
    }

    return menufacile_format_html($data);
}

function menufacile_format_html($data) {
    $html = '<div class="mf-menu">';

    if (!empty($data['settings']['name'])) {
        $html .= '<h1 class="mf-restaurant-name">' . esc_html($data['settings']['name']) . '</h1>';
    }

    foreach ($data['sections'] as $section) {
        if (empty($section['is_active'])) {
            continue;
        }

        $html .= '<section class="mf-section mf-section--' . esc_attr($section['menu_type']) . '">';
        $html .= '<h2>' . esc_html($section['name']) . '</h2>';

        if (!empty($section['notes'])) {
            $html .= '<p class="mf-section-notes">' . wp_kses_post($section['notes']) . '</p>';
        }

        $html .= '<ul class="mf-items">';
        foreach ($section['items'] as $item) {
            $html .= '<li class="mf-item">';
            $html .= '<div class="mf-item-header">';
            $html .= '<strong class="mf-item-name">' . esc_html($item['name']) . '</strong>';
            $html .= '<span class="mf-item-price">€ ' . number_format((float) $item['price'], 2, ',', '') . '</span>';
            $html .= '</div>';

            if (!empty($item['description'])) {
                $html .= '<p class="mf-item-description">' . esc_html($item['description']) . '</p>';
            }

            if (!empty($item['allergens'])) {
                $html .= '<p class="mf-item-allergens">Allergeni: ' . esc_html(implode(', ', $item['allergens'])) . '</p>';
            }

            $html .= '</li>';
        }
        $html .= '</ul></section>';
    }

    return $html . '</div>';
}

add_shortcode('menufacile', 'menufacile_render_menu');

/**
 * Stile minimale: aggiungilo nel CSS del tema o estendilo a piacere.
 */
function menufacile_inline_styles() {
    echo '<style>
        .mf-menu { max-width: 800px; margin: 0 auto; }
        .mf-section { margin-bottom: 2rem; }
        .mf-items { list-style: none; padding: 0; }
        .mf-item { padding: 0.75rem 0; border-bottom: 1px solid #eee; }
        .mf-item-header { display: flex; justify-content: space-between; gap: 1rem; }
        .mf-item-price { font-weight: 600; white-space: nowrap; }
        .mf-item-allergens { font-size: 0.85em; color: #999; margin: 0.25rem 0 0; }
    </style>';
}
add_action('wp_head', 'menufacile_inline_styles');

Uso nella pagina

Crea una pagina "Il nostro menu", modalità classica (non Gutenberg, oppure usa il blocco "Shortcode"):

[menufacile tenant="ducatarocco" locale="it"]

Versione inglese:

[menufacile tenant="ducatarocco" locale="en"]

Cache più aggressiva (10 minuti):

[menufacile tenant="ducatarocco" locale="it" cache_minutes="10"]

Considerazioni

Cache

Il transient API di WordPress salva su DB (o Redis se hai object-cache). Con cache_minutes=5 il sito chiama MenuFacile una volta ogni 5 minuti indipendentemente dal traffico. Anche con 10.000 visite/giorno fai ~288 richieste/giorno: carico minimo lato server, esperienza utente immediata.

Invalidazione manuale

Se il ristoratore vuole vedere subito una modifica senza aspettare i 5 minuti, può cancellare il transient. Comodo da WP-CLI:

wp transient delete mf_menu_ducatarocco_it

Multilingua (Polylang/WPML)

Se il sito WP è multilingua, in ogni versione di pagina passa il locale corretto:

[menufacile tenant="ducatarocco" locale="it"]
[menufacile tenant="ducatarocco" locale="en"]

Performance lato server

wp_remote_get con timeout=5: se MenuFacile dovesse essere lento, la pagina WordPress non si blocca oltre 5 secondi. Per siti high-traffic considera object cache (Redis) invece dei transient su DB.

SEO

Il menu è renderizzato server-side (HTML statico nella response): Googlebot lo vede senza problemi. Per arricchirlo ulteriormente con dati strutturati, vedi Schema.org JSON-LD.

Hai bisogno di aiuto?

Scrivi a info@menufacile.it.