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>
This commit is contained in:
@@ -13,3 +13,4 @@ require_once __DIR__ . '/gure-ametsa/animals.php';
|
||||
require_once __DIR__ . '/gure-ametsa/forms.php';
|
||||
require_once __DIR__ . '/gure-ametsa/api.php';
|
||||
require_once __DIR__ . '/gure-ametsa/admin.php';
|
||||
require_once __DIR__ . '/gure-ametsa/share.php';
|
||||
|
||||
BIN
wp-content/mu-plugins/gure-ametsa/assets/fraunces-800.ttf
Normal file
BIN
wp-content/mu-plugins/gure-ametsa/assets/fraunces-800.ttf
Normal file
Binary file not shown.
BIN
wp-content/mu-plugins/gure-ametsa/assets/karla-600.ttf
Normal file
BIN
wp-content/mu-plugins/gure-ametsa/assets/karla-600.ttf
Normal file
Binary file not shown.
BIN
wp-content/mu-plugins/gure-ametsa/assets/logo-mark.png
Normal file
BIN
wp-content/mu-plugins/gure-ametsa/assets/logo-mark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
317
wp-content/mu-plugins/gure-ametsa/share.php
Normal file
317
wp-content/mu-plugins/gure-ametsa/share.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?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;
|
||||
} );
|
||||
Reference in New Issue
Block a user