piazza-in-festa-wp/includes/render.php
2026-09-29 16:11:42 +02:00

222 lines
6.1 KiB
PHP

<?php
/**
* Builds the widget URL and renders the iframe. Shared by the shortcode and
* the block: both end up here, so there is a single place where parameters are
* sanitised and a single place where markup is produced.
*
* @package PiazzaInFesta
*/
defined( 'ABSPATH' ) || exit;
/**
* Default values for every supported parameter.
*
* @return array<string, string>
*/
function pinfesta_defaults() {
return array(
'comuni' => '',
'regione' => '',
'categoria' => '',
'limite' => '5',
'formato' => 'lista',
'tema' => 'chiaro',
'colore' => '',
'titolo' => '',
'altezza' => '320',
'dal' => '',
'al' => '',
'cerca' => '',
'font' => '',
);
}
/**
* Categories accepted by the service. Anything else is dropped instead of
* being forwarded, so a typo shows every event rather than none.
*
* @return string[]
*/
function pinfesta_categorie() {
return array(
'religioso',
'sagra_enogastronomica',
'musica_concerto',
'culturale_mostra',
'incontro_conferenza',
'fiera_mercato',
'mercatini',
'sportivo',
'natura_escursione',
'spettacolo',
'teatro',
'cinema',
'bambini_famiglie',
'istituzionale',
'altro',
);
}
/**
* Cleans a comma separated list of town identifiers (the slug you see in the
* address of a town page, for instance "la-morra").
*
* The hard limit is the service's, not ours: twelve towns on the free plan,
* fifty with a key. Here we only cut at the higher of the two, so a site with
* a key is not trimmed by the plugin; the extra towns are simply ignored by
* the service when the key is missing.
*
* @param string $raw Raw attribute value.
* @return string Normalised list, empty when nothing survives.
*/
function pinfesta_clean_comuni( $raw ) {
$out = array();
foreach ( explode( ',', (string) $raw ) as $item ) {
$slug = sanitize_title( trim( $item ) );
if ( '' !== $slug ) {
$out[] = $slug;
}
}
return implode( ',', array_slice( array_unique( $out ), 0, 50 ) );
}
/**
* Turns the parameters into the URL of the widget page on the service.
*
* @param array $args Parameters, already merged with the defaults.
* @return string
*/
function pinfesta_build_url( $args ) {
$query = array();
$comuni = pinfesta_clean_comuni( $args['comuni'] );
if ( '' !== $comuni ) {
$query['comune'] = $comuni;
} elseif ( '' !== $args['regione'] ) {
$query['regione'] = sanitize_text_field( $args['regione'] );
}
// One category or several, comma separated. Unknown ones are dropped, so a
// typo widens the widget instead of emptying it.
$categorie = array();
foreach ( explode( ',', (string) $args['categoria'] ) as $item ) {
$item = trim( $item );
if ( in_array( $item, pinfesta_categorie(), true ) ) {
$categorie[] = $item;
}
}
if ( $categorie ) {
$query['categoria'] = implode( ',', array_unique( $categorie ) );
}
// Period: only events that start between these two dates (YYYY-MM-DD), for
// a page about Christmas or the summer. Anything that is not a date is
// ignored.
foreach ( array( 'dal', 'al' ) as $chiave_data ) {
$data = trim( (string) $args[ $chiave_data ] );
if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $data ) ) {
$query[ $chiave_data ] = $data;
}
}
// A word in the title, for instance "natale" inside the markets.
$cerca = sanitize_text_field( $args['cerca'] );
if ( mb_strlen( $cerca ) >= 3 ) {
$query['cerca'] = mb_substr( $cerca, 0, 40 );
}
// The widget lives in an iframe and cannot inherit the site's font: the
// service offers a few safe stacks to pick from.
if ( in_array( $args['font'], array( 'helvetica', 'serif' ), true ) ) {
$query['font'] = $args['font'];
}
$limite = absint( $args['limite'] );
if ( $limite > 0 ) {
$query['limite'] = min( $limite, 20 );
}
// Elenco, schede con la locandina, o carosello che scorre da solo.
if ( in_array( $args['formato'], array( 'schede', 'carosello' ), true ) ) {
$query['formato'] = $args['formato'];
}
if ( 'scuro' === $args['tema'] ) {
$query['tema'] = 'scuro';
}
$colore = sanitize_hex_color( $args['colore'] );
if ( $colore ) {
// Without the hash: add_query_arg() leaves "#" as it is, and a bare "#"
// in a URL opens the fragment, which would swallow this parameter and
// every parameter after it. The service accepts the six digits alone.
$query['accent'] = ltrim( $colore, '#' );
}
$titolo = sanitize_text_field( $args['titolo'] );
if ( '' !== $titolo ) {
$query['titolo'] = $titolo;
}
// Service key: optional. It only tells the service that this site may ask
// for the version without the Piazza in Festa credit line; every feature of
// this plugin works exactly the same with or without it.
$chiave = pinfesta_get_option( 'chiave' );
if ( '' !== $chiave ) {
$query['chiave'] = $chiave;
$query['marchio'] = '0';
}
return add_query_arg( $query, pinfesta_base_url() . '/embed' );
}
/**
* Renders the widget.
*
* An iframe, on purpose: the CSS of the host site cannot break the widget and
* the widget cannot break the host site. The height arrives from the framed
* page via postMessage (assets/js/resize.js), so nobody has to guess it; the
* `altezza` parameter is only the starting height, and what visitors see when
* JavaScript is off.
*
* @param array $args Parameters, already merged with the defaults.
* @return string HTML.
*/
function pinfesta_render_iframe( $args ) {
$args = wp_parse_args( $args, pinfesta_defaults() );
if ( '' === pinfesta_clean_comuni( $args['comuni'] ) && '' === trim( (string) $args['regione'] ) ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return '';
}
return '<p class="pinfesta-empty">' . esc_html__( 'Piazza in Festa: set at least one town or a region.', 'piazza-in-festa' ) . '</p>';
}
wp_enqueue_style( 'pinfesta-widget' );
wp_enqueue_script( 'pinfesta-resize' );
$altezza = absint( $args['altezza'] );
$altezza = ( $altezza >= 120 && $altezza <= 4000 ) ? $altezza : 320;
return sprintf(
'<iframe class="pinfesta-embed" src="%1$s" title="%2$s" height="%3$d" loading="lazy" scrolling="no" referrerpolicy="no-referrer-when-downgrade"></iframe>',
esc_url( pinfesta_build_url( $args ) ),
esc_attr__( 'Upcoming local events', 'piazza-in-festa' ),
$altezza
);
}