Files
gureametsabilagun/wp-content/mu-plugins/gure-ametsa/share.php
Josep Piguillem 130c29bd61 Compartir: imagen automática para redes y carteles con QR
- Imagen 1200×630 por animal (foto, nombre, estado, logo), en su idioma y con los colores
  del tema; se regenera sola si cambia la foto o el nombre. Se usa en og:image/twitter
  (con Yoast o sin él) y se puede descargar desde la ficha del panel.
- Cartel A4 imprimible con QR a la ficha (acción de fila y acción en bloque).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:21:32 +02:00

318 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Compartir: imagen automática para redes (1200×630) y carteles imprimibles con QR.
*/
defined( 'ABSPATH' ) || exit;
define( 'GA_SHARE_VERSION', 2 );
define( 'GA_QR_JS', 'https://cdn.jsdelivr.net/npm/qrcode-generator@2.0.4/dist/qrcode.js' );
define( 'GA_QR_SRI', 'sha384-e9EFD6BGC90bkW9aDV5xbbBfzwN7G8YImHao2lfLVKV/hPB0E0go+H3I64h7oHtA' );
/* -------------------------------------------------------------------------
* Imagen para redes
* ---------------------------------------------------------------------- */
/** Ruta y URL de la imagen de un animal (el nombre cambia si cambia la foto, el nombre o el diseño). */
function ga_share_image_paths( $post_id ) {
$uploads = wp_upload_dir();
$lang = function_exists( 'pll_get_post_language' ) ? pll_get_post_language( $post_id ) : 'es';
$hash = substr( md5( implode( '|', array(
GA_SHARE_VERSION, get_the_title( $post_id ), get_post_thumbnail_id( $post_id ), $lang, ga_status( $post_id ),
function_exists( 'ga_color' ) ? ga_color( 'primary' ) . ga_color( 'accent' ) : '',
) ) ), 0, 10 );
$name = "animal-$post_id-$hash.jpg";
return array(
'dir' => trailingslashit( $uploads['basedir'] ) . 'ga-share',
'file' => trailingslashit( $uploads['basedir'] ) . "ga-share/$name",
'url' => trailingslashit( $uploads['baseurl'] ) . "ga-share/$name",
'glob' => trailingslashit( $uploads['basedir'] ) . "ga-share/animal-$post_id-*.jpg",
);
}
/** Devuelve la URL de la imagen para redes, generándola si hace falta. */
function ga_share_image_url( $post_id ) {
$p = ga_share_image_paths( $post_id );
if ( ! file_exists( $p['file'] ) && ! ga_generate_share_image( $post_id ) ) {
return '';
}
return $p['url'];
}
function ga_hex_color( $img, $hex ) {
$hex = ltrim( $hex, '#' );
return imagecolorallocate( $img, hexdec( substr( $hex, 0, 2 ) ), hexdec( substr( $hex, 2, 2 ) ), hexdec( substr( $hex, 4, 2 ) ) );
}
/** Carga una imagen de la biblioteca (JPEG, PNG o WebP) como recurso GD. */
function ga_gd_load( $path ) {
$type = wp_check_filetype( $path )['type'] ?? '';
return match ( $type ) {
'image/jpeg' => @imagecreatefromjpeg( $path ),
'image/png' => @imagecreatefrompng( $path ),
'image/webp' => function_exists( 'imagecreatefromwebp' ) ? @imagecreatefromwebp( $path ) : false,
default => false,
};
}
/** Parte un texto en líneas que quepan en $width con la fuente y tamaño dados. */
function ga_gd_wrap( $text, $font, $size, $width ) {
$lines = array( '' );
foreach ( preg_split( '/\s+/u', $text ) as $word ) {
$try = trim( end( $lines ) . ' ' . $word );
$box = imagettfbbox( $size, 0, $font, $try );
if ( $box[2] - $box[0] > $width && '' !== end( $lines ) ) {
$lines[] = $word;
} else {
$lines[ count( $lines ) - 1 ] = $try;
}
}
return $lines;
}
function ga_generate_share_image( $post_id ) {
if ( ! function_exists( 'imagettftext' ) ) {
return false;
}
$p = ga_share_image_paths( $post_id );
wp_mkdir_p( $p['dir'] );
foreach ( glob( $p['glob'] ) ?: array() as $old ) {
@unlink( $old ); // Versiones anteriores de este animal.
}
$w = 1200;
$h = 630;
$split = 630; // Ancho de la foto (cuadrada, a la izquierda).
$img = imagecreatetruecolor( $w, $h );
$primary = function_exists( 'ga_color' ) ? ga_color( 'primary' ) : '#401f0c';
$accent = function_exists( 'ga_color' ) ? ga_color( 'accent' ) : '#bd8537';
$surface = function_exists( 'ga_color' ) ? ga_color( 'surface' ) : '#f3ecdc';
$bg = ga_hex_color( $img, $primary );
imagefilledrectangle( $img, 0, 0, $w, $h, $bg );
// Foto (recorte cuadrado centrado) o, si no hay, el símbolo del logo sobre fondo claro.
$photo = get_post_thumbnail_id( $post_id ) ? get_attached_file( get_post_thumbnail_id( $post_id ) ) : '';
$src = $photo ? ga_gd_load( $photo ) : false;
if ( $src ) {
$sw = imagesx( $src );
$sh = imagesy( $src );
$side = min( $sw, $sh );
imagecopyresampled( $img, $src, 0, 0, (int) ( ( $sw - $side ) / 2 ), (int) ( ( $sh - $side ) / 3 ), $split, $h, $side, $side );
imagedestroy( $src );
} else {
imagefilledrectangle( $img, 0, 0, $split, $h, ga_hex_color( $img, $surface ) );
$mark = @imagecreatefrompng( __DIR__ . '/assets/logo-mark.png' );
if ( $mark ) {
$mw = 380;
$mh = (int) ( imagesy( $mark ) * $mw / imagesx( $mark ) );
imagecopyresampled( $img, $mark, (int) ( ( $split - $mw ) / 2 ), (int) ( ( $h - $mh ) / 2 ), 0, 0, $mw, $mh, imagesx( $mark ), imagesy( $mark ) );
imagedestroy( $mark );
}
}
// Texto a la derecha.
$serif = __DIR__ . '/assets/fraunces-800.ttf';
$sans = __DIR__ . '/assets/karla-600.ttf';
$white = imagecolorallocate( $img, 255, 255, 255 );
$gold = ga_hex_color( $img, $accent );
$soft = ga_hex_color( $img, $surface );
$x = $split + 56;
$max = $w - $x - 50;
$lang = function_exists( 'pll_get_post_language' ) ? pll_get_post_language( $post_id ) : 'es';
$eu = 'eu' === $lang;
imagettftext( $img, 17, 0, $x, 92, $gold, $sans, $eu ? 'BILAGUN · ADOPZIOA' : 'BILAGUN · ADOPCIÓN' );
$name = get_the_title( $post_id );
$size = 64;
do {
$lines = ga_gd_wrap( $name, $serif, $size, $max );
$size -= 4;
} while ( ( count( $lines ) > 2 ) && $size > 30 );
$size += 4;
$y = 190;
foreach ( array_slice( $lines, 0, 3 ) as $line ) {
imagettftext( $img, $size, 0, $x, $y, $white, $serif, $line );
$y += (int) ( $size * 1.25 );
}
$status = ga_status( $post_id );
$tag = 'proceso' === $status ? ( $eu ? 'Prozesuan' : 'En proceso de adopción' ) : ( $eu ? 'Familia bila' : 'Busca familia' );
imagettftext( $img, 28, 0, $x, $y + 20, $soft, $sans, $tag );
$host = wp_parse_url( home_url(), PHP_URL_HOST );
imagettftext( $img, 18, 0, $x, $h - 60, $gold, $sans, $host );
$mark = @imagecreatefrompng( __DIR__ . '/assets/logo-mark.png' );
if ( $mark ) {
$mw = 96;
$mh = (int) ( imagesy( $mark ) * $mw / imagesx( $mark ) );
imagecopyresampled( $img, $mark, $w - $mw - 44, $h - $mh - 44, 0, 0, $mw, $mh, imagesx( $mark ), imagesy( $mark ) );
imagedestroy( $mark );
}
$ok = imagejpeg( $img, $p['file'], 86 );
imagedestroy( $img );
return $ok;
}
/* Se regenera al guardar (si cambió la foto o el nombre, cambia el nombre de archivo). */
add_action( 'save_post_animal', function ( $post_id ) {
if ( ! wp_is_post_revision( $post_id ) && 'publish' === get_post_status( $post_id ) ) {
ga_share_image_url( $post_id );
}
}, 30 );
/* Etiquetas Open Graph / Twitter con la imagen generada */
add_action( 'wpseo_add_opengraph_images', function ( $container ) {
if ( is_singular( 'animal' ) && ( $url = ga_share_image_url( get_queried_object_id() ) ) ) {
$container->add_image( array( 'url' => $url, 'width' => 1200, 'height' => 630, 'type' => 'image/jpeg' ) );
}
} );
add_filter( 'wpseo_opengraph_image', fn( $url ) => is_singular( 'animal' ) ? ( ga_share_image_url( get_queried_object_id() ) ?: $url ) : $url );
add_filter( 'wpseo_twitter_image', fn( $url ) => is_singular( 'animal' ) ? ( ga_share_image_url( get_queried_object_id() ) ?: $url ) : $url );
add_filter( 'wpseo_opengraph_image_width', fn( $v ) => is_singular( 'animal' ) ? 1200 : $v );
add_filter( 'wpseo_opengraph_image_height', fn( $v ) => is_singular( 'animal' ) ? 630 : $v );
add_filter( 'wpseo_twitter_card_type', fn( $v ) => is_singular( 'animal' ) ? 'summary_large_image' : $v );
/* Sin Yoast: etiquetas mínimas propias */
add_action( 'wp_head', function () {
if ( defined( 'WPSEO_VERSION' ) || ! is_singular( 'animal' ) ) {
return;
}
$id = get_queried_object_id();
printf( '<meta property="og:title" content="%s">' . "\n", esc_attr( get_the_title( $id ) ) );
printf( '<meta property="og:url" content="%s">' . "\n", esc_url( get_permalink( $id ) ) );
printf( '<meta property="og:image" content="%s">' . "\n", esc_url( ga_share_image_url( $id ) ) );
echo '<meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta name="twitter:card" content="summary_large_image">' . "\n";
}, 5 );
/* En la ficha del panel: vista previa y descarga (para publicarla en Instagram, etc.) */
add_action( 'add_meta_boxes_animal', function ( $post ) {
add_meta_box( 'ga_share', 'Compartir', function ( $post ) {
if ( 'publish' !== $post->post_status ) {
echo '<p class="description">Disponible cuando el animal esté publicado.</p>';
return;
}
$url = ga_share_image_url( $post->ID );
if ( $url ) {
printf( '<p><img src="%1$s" alt="" style="width:100%%;height:auto;border-radius:4px"></p><p><a class="button" href="%1$s" download>Descargar imagen para redes</a></p>', esc_url( $url ) );
}
printf(
'<p><a class="button" href="%s" target="_blank">Imprimir cartel con QR</a></p><p class="description">La imagen se crea sola con la foto principal y el nombre, y es la que aparece al compartir el enlace en WhatsApp o redes.</p>',
esc_url( ga_poster_url( array( $post->ID ) ) )
);
}, 'animal', 'side', 'default' );
} );
/* -------------------------------------------------------------------------
* Carteles con QR (para las jaulas, jornadas de adopción, etc.)
* ---------------------------------------------------------------------- */
function ga_poster_url( array $ids ) {
return wp_nonce_url( admin_url( 'admin-post.php?action=ga_poster&ids=' . implode( ',', array_map( 'absint', $ids ) ) ), 'ga_poster' );
}
add_filter( 'post_row_actions', function ( $actions, $post ) {
if ( 'animal' === $post->post_type && 'publish' === $post->post_status ) {
$actions['ga_poster'] = sprintf( '<a href="%s" target="_blank">Cartel QR</a>', esc_url( ga_poster_url( array( $post->ID ) ) ) );
}
return $actions;
}, 10, 2 );
add_filter( 'bulk_actions-edit-animal', function ( $actions ) {
$actions['ga_posters'] = 'Imprimir carteles con QR';
return $actions;
} );
add_filter( 'handle_bulk_actions-edit-animal', function ( $redirect, $action, $ids ) {
return 'ga_posters' === $action ? ga_poster_url( $ids ) : $redirect;
}, 10, 3 );
add_action( 'admin_post_ga_poster', function () {
check_admin_referer( 'ga_poster' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( 'Sin permiso.' );
}
$ids = array_filter( array_map( 'absint', explode( ',', (string) ( $_GET['ids'] ?? '' ) ) ) );
$ids = array_filter( $ids, fn( $id ) => 'animal' === get_post_type( $id ) && 'publish' === get_post_status( $id ) );
if ( ! $ids ) {
wp_die( 'No hay animales publicados seleccionados.' );
}
$primary = function_exists( 'ga_color' ) ? ga_color( 'primary' ) : '#401f0c';
$accent = function_exists( 'ga_color' ) ? ga_color( 'accent' ) : '#bd8537';
$logo = function_exists( 'ga_logo_svg' ) ? ga_logo_svg( 'full' ) : '';
$fonts = content_url( 'themes/gure-ametsa/assets/fonts/' );
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Carteles · Bilagun</title>
<style>
@font-face { font-family: Fraunces; src: url("<?php echo esc_url( $fonts . 'fraunces.woff2' ); ?>") format("woff2"); font-weight: 100 900; }
@font-face { font-family: Karla; src: url("<?php echo esc_url( $fonts . 'karla.woff2' ); ?>") format("woff2"); font-weight: 100 900; }
@page { size: A4; margin: 0; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Karla, sans-serif; color: <?php echo esc_html( $primary ); ?>; background: #eee; }
.bar { padding: 12px 20px; background: <?php echo esc_html( $primary ); ?>; color: #fff; display: flex; gap: 16px; align-items: center; }
.bar button { font: 700 15px Karla, sans-serif; padding: 8px 18px; border-radius: 999px; border: 0; background: <?php echo esc_html( $accent ); ?>; cursor: pointer; }
.poster { width: 210mm; height: 297mm; margin: 10mm auto; background: #fff; padding: 16mm; display: flex; flex-direction: column; page-break-after: always; }
.photo { flex: 1; min-height: 0; border-radius: 6mm; overflow: hidden; background: #f3ecdc; display: grid; place-items: center; }
.photo img { width: 100%; height: 100%; object-fit: cover; }
.photo svg { width: 60%; height: auto; }
h1 { font: 800 30mm/1 Fraunces, serif; margin: 9mm 0 3mm; letter-spacing: -0.02em; }
.lead { font-size: 6mm; margin: 0 0 6mm; }
.foot { display: flex; align-items: center; gap: 8mm; border-top: 0.6mm solid #e6d9b7; padding-top: 7mm; }
.qr svg { width: 48mm; height: 48mm; display: block; }
.cta { flex: 1; }
.cta strong { display: block; font: 800 9mm/1.1 Fraunces, serif; }
.cta span { display: block; font-size: 5mm; margin-top: 2mm; color: <?php echo esc_html( $accent ); ?>; word-break: break-all; }
.logo svg { width: 34mm; height: auto; }
@media print { body { background: #fff; } .bar { display: none; } .poster { margin: 0; } }
</style>
</head>
<body>
<div class="bar"><button onclick="window.print()">Imprimir</button> <?php echo count( $ids ); ?> cartel(es) · A4</div>
<?php foreach ( $ids as $id ) : ?>
<?php
$photo = get_the_post_thumbnail_url( $id, 'large' );
$lead = has_excerpt( $id ) ? get_the_excerpt( $id ) : '';
?>
<section class="poster">
<div class="photo">
<?php if ( $photo ) : ?>
<img src="<?php echo esc_url( $photo ); ?>" alt="">
<?php else : ?>
<?php echo function_exists( 'ga_logo_svg' ) ? ga_logo_svg( 'mark' ) : ''; ?>
<?php endif; ?>
</div>
<h1><?php echo esc_html( get_the_title( $id ) ); ?></h1>
<?php if ( $lead ) : ?>
<p class="lead"><?php echo esc_html( $lead ); ?></p>
<?php endif; ?>
<div class="foot">
<div class="qr" data-qr="<?php echo esc_url( get_permalink( $id ) ); ?>"></div>
<div class="cta">
<strong>¿Quieres conocerme?<br>Ezagutu nahi nauzu?</strong>
<span><?php echo esc_html( preg_replace( '#^https?://#', '', get_permalink( $id ) ) ); ?></span>
</div>
<div class="logo"><?php echo $logo; // SVG del tema. ?></div>
</div>
</section>
<?php endforeach; ?>
<script src="<?php echo esc_url( GA_QR_JS ); ?>" integrity="<?php echo esc_attr( GA_QR_SRI ); ?>" crossorigin="anonymous"></script>
<script>
document.querySelectorAll( '[data-qr]' ).forEach( function ( el ) {
var qr = qrcode( 0, 'M' );
qr.addData( el.dataset.qr );
qr.make();
el.innerHTML = qr.createSvgTag( { cellSize: 4, margin: 0, scalable: true } );
} );
</script>
</body>
</html>
<?php
exit;
} );