- Adopción sin filtros ni etiquetas: acceso a Perros y Gatos, tarjetas de foto y nombre. - Ficha nueva: galería, vídeo, fecha de nacimiento o edad aproximada, carácter, necesidades, convivencia y hogar que buscamos (textos por idioma, datos compartidos). - Estados disponible / en proceso / adoptado y animales destacados. - Botón «Quiero conocerlo/la» con formulario breve (animal incluido) y confirmación. - Botones para compartir cada ficha (WhatsApp, Facebook, copiar enlace). - Portada corta según la propuesta del cliente; donaciones, cifras, etc. ocultas. - Páginas Acoge y Preguntas frecuentes, menú con submenús, sin hotel. - Aviso de cita previa y lista de ayuda material editables desde el panel. - Plugin núcleo dividido en módulos (i18n, animales, formularios, APIs). - Migración 5 idempotente: respeta las páginas editadas por el cliente. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
304 lines
14 KiB
PHP
304 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Tema Gure Ametsa.
|
|
*
|
|
* - inc/customizer.php Opciones de Apariencia → Personalizar (colores, tipografía, textos…).
|
|
* - inc/dynamic-css.php CSS generado a partir de esas opciones.
|
|
* - inc/patterns.php Patrones y estilos de bloque para el editor.
|
|
*/
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
define( 'GA_THEME_VERSION', '2.2.0' );
|
|
|
|
require_once __DIR__ . '/inc/customizer.php';
|
|
require_once __DIR__ . '/inc/dynamic-css.php';
|
|
require_once __DIR__ . '/inc/patterns.php';
|
|
|
|
add_action( 'after_setup_theme', function () {
|
|
add_theme_support( 'title-tag' );
|
|
add_theme_support( 'post-thumbnails' );
|
|
add_theme_support( 'custom-logo', array( 'height' => 120, 'width' => 120, 'flex-width' => true, 'flex-height' => true ) );
|
|
add_theme_support( 'html5', array( 'search-form', 'gallery', 'caption', 'style', 'script' ) );
|
|
add_theme_support( 'align-wide' );
|
|
add_theme_support( 'responsive-embeds' );
|
|
add_theme_support( 'editor-styles' );
|
|
add_theme_support( 'wp-block-styles' );
|
|
add_theme_support( 'customize-selective-refresh-widgets' );
|
|
add_editor_style( 'style.css' );
|
|
|
|
add_image_size( 'ga-card', 640, 544, true );
|
|
add_image_size( 'ga-square', 1000, 1000, true );
|
|
|
|
register_nav_menus( array(
|
|
'primary' => 'Menú principal',
|
|
'footer' => 'Menú legal (pie)',
|
|
) );
|
|
} );
|
|
|
|
add_action( 'wp_enqueue_scripts', function () {
|
|
wp_enqueue_style( 'gure-ametsa', get_stylesheet_uri(), array(), GA_THEME_VERSION );
|
|
wp_enqueue_script( 'gure-ametsa', get_theme_file_uri( 'assets/js/main.js' ), array(), GA_THEME_VERSION, array( 'strategy' => 'defer', 'in_footer' => true ) );
|
|
} );
|
|
|
|
add_action( 'wp_head', function () {
|
|
echo '<meta name="theme-color" content="' . esc_attr( ga_color( 'primary' ) ) . '">' . "\n";
|
|
if ( ! has_site_icon() ) {
|
|
echo '<link rel="icon" href="' . esc_url( get_theme_file_uri( 'assets/img/favicon.svg' ) ) . '" type="image/svg+xml">' . "\n";
|
|
}
|
|
}, 1 );
|
|
|
|
/** Portada en el idioma actual. */
|
|
function ga_home_url() {
|
|
return function_exists( 'pll_home_url' ) ? pll_home_url() : home_url( '/' );
|
|
}
|
|
|
|
/* ---------- Selector de idioma ---------- */
|
|
function ga_language_switcher( $class = 'ga-lang' ) {
|
|
if ( ! function_exists( 'pll_the_languages' ) || ! ga_opt( 'header', 'switcher' ) ) {
|
|
return;
|
|
}
|
|
$langs = pll_the_languages( array( 'raw' => 1, 'hide_if_empty' => 0, 'hide_if_no_translation' => 0 ) );
|
|
if ( ! $langs ) {
|
|
return;
|
|
}
|
|
echo '<ul class="' . esc_attr( $class ) . '" aria-label="' . esc_attr( ga__( 'Idioma', 'Hizkuntza' ) ) . '">';
|
|
foreach ( $langs as $lang ) {
|
|
printf(
|
|
'<li><a href="%s" hreflang="%s" lang="%s"%s title="%s">%s</a></li>',
|
|
esc_url( $lang['url'] ),
|
|
esc_attr( $lang['slug'] ),
|
|
esc_attr( $lang['slug'] ),
|
|
$lang['current_lang'] ? ' aria-current="true" class="is-current"' : '',
|
|
esc_attr( $lang['name'] ),
|
|
esc_html( strtoupper( $lang['slug'] ) )
|
|
);
|
|
}
|
|
echo '</ul>';
|
|
}
|
|
|
|
/* ---------- Iconos SVG ---------- */
|
|
function ga_icon( $name ) {
|
|
$icons = array(
|
|
'paw' => '<svg viewBox="0 0 64 64" fill="currentColor" aria-hidden="true"><ellipse cx="17" cy="24" rx="6.5" ry="8.5" transform="rotate(-18 17 24)"/><ellipse cx="47" cy="24" rx="6.5" ry="8.5" transform="rotate(18 47 24)"/><ellipse cx="26" cy="12" rx="6" ry="8"/><ellipse cx="38" cy="12" rx="6" ry="8"/><path d="M32 30c-9 0-18 11-18 19 0 6 5 8 9 7 4-1 6-3 9-3s5 2 9 3c4 1 9-1 9-7 0-8-9-19-18-19z"/></svg>',
|
|
'mark' => '<svg viewBox="0 0 64 64" aria-hidden="true"><circle cx="32" cy="32" r="32" style="fill:var(--ga-green)"/><g style="fill:var(--ga-cream)"><ellipse cx="21" cy="28" rx="4" ry="5.2" transform="rotate(-18 21 28)"/><ellipse cx="27" cy="20.5" rx="3.6" ry="4.8"/><ellipse cx="35" cy="20.5" rx="3.6" ry="4.8"/><ellipse cx="41" cy="28" rx="4" ry="5.2" transform="rotate(18 41 28)"/></g><path style="fill:var(--ga-red)" d="M31 48.5s-12-7.2-12-14.6c0-3.6 2.8-6.1 6-6.1 2.6 0 4.6 1.5 6 3.6 1.4-2.1 3.4-3.6 6-3.6 3.2 0 6 2.5 6 6.1 0 7.4-12 14.6-12 14.6z"/></svg>',
|
|
'instagram' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1" fill="currentColor"/></svg>',
|
|
'facebook' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M14 8h3V4h-3c-2.8 0-4.5 1.8-4.5 4.6V11H7v4h2.5v9h4v-9h3l.5-4h-3.5V8.8c0-.5.3-.8.5-.8z"/></svg>',
|
|
'tiktok' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M16.5 3c.4 2.2 1.8 3.7 4 4v3.2c-1.5 0-2.9-.4-4-1.2v6.2c0 3.4-2.7 5.8-5.8 5.8S5 18.6 5 15.3s2.8-5.9 6.2-5.7v3.3c-1.4-.3-2.9.8-2.9 2.4 0 1.4 1.1 2.5 2.5 2.5s2.5-1.1 2.5-2.5V3h3.2z"/></svg>',
|
|
'youtube' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M22 8.2c-.2-1.6-1-2.6-2.6-2.8C17 5 12 5 12 5s-5 0-7.4.4C3 5.6 2.2 6.6 2 8.2 1.8 9.5 1.8 12 1.8 12s0 2.5.2 3.8c.2 1.6 1 2.6 2.6 2.8C7 19 12 19 12 19s5 0 7.4-.4c1.6-.2 2.4-1.2 2.6-2.8.2-1.3.2-3.8.2-3.8s0-2.5-.2-3.8zM10 15V9l5.2 3L10 15z"/></svg>',
|
|
'whatsapp' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M12 2a10 10 0 0 0-8.6 15.1L2 22l5-1.3A10 10 0 1 0 12 2zm5.8 14.1c-.2.7-1.4 1.3-2 1.4-.5.1-1.2.1-1.9-.1-.4-.1-1-.3-1.7-.6-3-1.3-4.9-4.3-5.1-4.5-.1-.2-1.2-1.6-1.2-3.1s.8-2.2 1-2.5c.3-.3.6-.4.8-.4h.6c.2 0 .4 0 .6.5l.9 2.1c.1.1.1.3 0 .5l-.3.5-.4.5c-.1.1-.3.3-.1.6.2.3.7 1.2 1.6 1.9 1.1 1 2 1.3 2.3 1.4.3.1.4.1.6-.1l.9-1c.2-.3.4-.2.6-.1l2 1c.3.1.4.2.5.3.1.2.1.8-.2 1.4z"/></svg>',
|
|
'phone' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2c.3-.3.7-.4 1-.2 1.1.4 2.3.6 3.6.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1A17 17 0 0 1 3 4c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.3.2 2.5.6 3.6.1.3 0 .7-.2 1l-2.3 2.2z"/></svg>',
|
|
'mail' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>',
|
|
'pin' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 21s-7-6.2-7-11.5A7 7 0 0 1 19 9.5C19 14.8 12 21 12 21z"/><circle cx="12" cy="9.5" r="2.5"/></svg>',
|
|
'clock' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>',
|
|
'arrow' => '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>',
|
|
'check' => '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>',
|
|
'menu' => '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"/></svg>',
|
|
);
|
|
return $icons[ $name ] ?? '';
|
|
}
|
|
|
|
/** Enlaces a redes sociales configuradas en el personalizador. */
|
|
function ga_social_links( $with_labels = false ) {
|
|
$networks = array(
|
|
'instagram' => 'Instagram',
|
|
'facebook' => 'Facebook',
|
|
'tiktok' => 'TikTok',
|
|
'youtube' => 'YouTube',
|
|
'whatsapp' => 'WhatsApp',
|
|
);
|
|
$out = '';
|
|
foreach ( $networks as $key => $label ) {
|
|
$value = ga_info( $key );
|
|
if ( ! $value ) {
|
|
continue;
|
|
}
|
|
$href = 'whatsapp' === $key ? 'https://wa.me/' . preg_replace( '/\D/', '', $value ) : $value;
|
|
$out .= sprintf(
|
|
'<a href="%s" target="_blank" rel="noopener" aria-label="%s">%s%s</a>',
|
|
esc_url( $href ),
|
|
esc_attr( $label ),
|
|
ga_icon( $key ),
|
|
$with_labels ? ' ' . esc_html( $label ) : ''
|
|
);
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function ga_tel_link() {
|
|
return 'tel:+34' . preg_replace( '/\D/', '', ga_info( 'telefono' ) );
|
|
}
|
|
|
|
/** SVG del logo (assets/img/logo.svg o logo-mark.svg), en línea para poder recolorearlo con CSS. */
|
|
function ga_logo_svg( $variant = 'full' ) {
|
|
static $cache = array();
|
|
if ( ! isset( $cache[ $variant ] ) ) {
|
|
$file = get_theme_file_path( 'assets/img/' . ( 'mark' === $variant ? 'logo-mark.svg' : 'logo.svg' ) );
|
|
$cache[ $variant ] = is_readable( $file ) ? trim( file_get_contents( $file ) ) : '';
|
|
}
|
|
return $cache[ $variant ];
|
|
}
|
|
|
|
/**
|
|
* Marca del sitio.
|
|
*
|
|
* @param string $context 'header' o 'footer'.
|
|
*/
|
|
function ga_brand( $context = 'header' ) {
|
|
if ( has_custom_logo() ) {
|
|
the_custom_logo();
|
|
return;
|
|
}
|
|
$style = 'footer' === $context ? 'full' : ga_opt( 'header', 'logo_style' );
|
|
$name = sprintf(
|
|
'<span class="ga-brand__name"><b>%s</b><b>%s</b><small>%s</small></span>',
|
|
esc_html( ga_opt( 'header', 'brand_line1' ) ),
|
|
esc_html( ga_opt( 'header', 'brand_line2' ) ),
|
|
esc_html( ga_opt( 'header', 'brand_line3' ) )
|
|
);
|
|
$inner = match ( $style ) {
|
|
'full' => '<span class="ga-brand__logo">' . ga_logo_svg( 'full' ) . '</span>',
|
|
'text' => $name,
|
|
default => '<span class="ga-brand__mark">' . ga_logo_svg( 'mark' ) . '</span>' . $name,
|
|
};
|
|
printf(
|
|
'<a class="ga-brand ga-brand--%s" href="%s" rel="home" aria-label="%s">%s</a>',
|
|
esc_attr( $style ),
|
|
esc_url( ga_home_url() ),
|
|
esc_attr( get_bloginfo( 'name' ) ),
|
|
$inner // SVG estático del tema y textos escapados.
|
|
);
|
|
}
|
|
|
|
/* ---------- Ayudantes de animales ---------- */
|
|
/** Distintivo de estado: solo se muestra «En proceso» o «Adoptado» (los disponibles no llevan etiqueta). */
|
|
function ga_status_badge( $post_id = null ) {
|
|
$post_id = $post_id ?: get_the_ID();
|
|
$estado = ga_status( $post_id );
|
|
if ( 'disponible' === $estado ) {
|
|
return;
|
|
}
|
|
printf( '<span class="ga-status ga-status--%s">%s</span>', esc_attr( $estado ), esc_html( ga_label( $post_id, 'estado' ) ) );
|
|
}
|
|
|
|
/** Foto principal o, si no hay, la silueta del logo. */
|
|
function ga_animal_photo( $size = 'ga-card', $post_id = null ) {
|
|
$post_id = $post_id ?: get_the_ID();
|
|
if ( has_post_thumbnail( $post_id ) ) {
|
|
echo get_the_post_thumbnail( $post_id, $size, array( 'loading' => 'lazy' ) );
|
|
} else {
|
|
echo '<div class="ga-card__placeholder">' . ga_placeholder_svg() . '</div>';
|
|
}
|
|
}
|
|
|
|
/** Silueta del logo para huecos sin foto. */
|
|
function ga_placeholder_svg() {
|
|
return ga_logo_svg( 'mark' ) ?: ga_icon( 'paw' );
|
|
}
|
|
|
|
/** Texto del botón «Quiero conocerlo / conocerla» según el sexo. */
|
|
function ga_meet_label( $post_id = null ) {
|
|
$post_id = $post_id ?: get_the_ID();
|
|
return 'hembra' === ga_get( $post_id, 'sexo' ) ? ga_opt( 'adopcion', 'btn_f' ) : ga_opt( 'adopcion', 'btn_m' );
|
|
}
|
|
|
|
/** Enlaces para compartir una ficha. */
|
|
function ga_share_links( $post_id = null ) {
|
|
$post_id = $post_id ?: get_the_ID();
|
|
$url = get_permalink( $post_id );
|
|
$text = get_the_title( $post_id ) . ' — ' . ga__( 'busca familia en Bilagun', 'familia bila Bilagunen' );
|
|
printf(
|
|
'<div class="ga-share"><span>%s</span><a class="ga-share__btn" href="%s" target="_blank" rel="noopener">WhatsApp</a><a class="ga-share__btn" href="%s" target="_blank" rel="noopener">Facebook</a><button type="button" class="ga-share__btn" data-copy="%s" data-done="%s">%s</button></div>',
|
|
esc_html( ga__( 'Comparte su historia:', 'Partekatu haren istorioa:' ) ),
|
|
esc_url( 'https://wa.me/?text=' . rawurlencode( $text . ' ' . $url ) ),
|
|
esc_url( 'https://www.facebook.com/sharer/sharer.php?u=' . rawurlencode( $url ) ),
|
|
esc_attr( $url ),
|
|
esc_attr( ga__( '¡Copiado!', 'Kopiatuta!' ) ),
|
|
esc_html( ga__( 'Copiar enlace', 'Kopiatu esteka' ) )
|
|
);
|
|
}
|
|
|
|
/** Vídeo de la ficha: oEmbed (YouTube, Vimeo…) o archivo de vídeo. */
|
|
function ga_video_html( $url ) {
|
|
if ( ! $url ) {
|
|
return '';
|
|
}
|
|
if ( preg_match( '/\.(mp4|webm|mov)(\?.*)?$/i', $url ) ) {
|
|
return '<video controls preload="metadata" playsinline src="' . esc_url( $url ) . '"></video>';
|
|
}
|
|
$embed = wp_oembed_get( $url, array( 'width' => 900 ) );
|
|
return $embed ?: '<a href="' . esc_url( $url ) . '" target="_blank" rel="noopener">' . esc_html( ga__( 'Ver vídeo', 'Ikusi bideoa' ) ) . '</a>';
|
|
}
|
|
|
|
function ga_species( $post_id = null ) {
|
|
$terms = get_the_terms( $post_id ?: get_the_ID(), 'especie' );
|
|
return $terms && ! is_wp_error( $terms ) ? $terms[0]->name : '';
|
|
}
|
|
|
|
function ga_plural( $name ) {
|
|
if ( 'eu' === ga_lang() ) {
|
|
return preg_match( '/k$/i', $name ) ? $name : $name . 'k'; // Txakurra → Txakurrak, Katua → Katuak.
|
|
}
|
|
return preg_match( '/s$/i', $name ) ? $name : $name . 's';
|
|
}
|
|
|
|
/** URL de la página de contacto con el animal preseleccionado. */
|
|
function ga_adopt_url( $post_id = null ) {
|
|
return add_query_arg( 'adoptar', $post_id ?: get_the_ID(), ga_page_url( 'contacto' ) ) . '#formulario';
|
|
}
|
|
|
|
/** Escapa un texto y convierte *entre asteriscos* en palabras resaltadas. */
|
|
function ga_highlight( $text ) {
|
|
return preg_replace( '/\*(.+?)\*/u', '<span class="ga-hl">$1</span>', esc_html( $text ) );
|
|
}
|
|
|
|
/**
|
|
* Encabezado de sección: rayita + antetítulo + título + texto, leídos del personalizador.
|
|
*
|
|
* @param string $section Sección del registro.
|
|
* @param array $args 'center' => bool, 'h' => nivel, 'extra' => HTML a la derecha (p. ej. un botón).
|
|
*/
|
|
function ga_section_head( $section, $args = array() ) {
|
|
$args = wp_parse_args( $args, array( 'center' => false, 'h' => 2, 'extra' => '' ) );
|
|
$eyebrow = ga_opt( $section, 'eyebrow' );
|
|
$title = ga_opt( $section, 'title' );
|
|
$text = ga_opt( $section, 'text' );
|
|
if ( ! $eyebrow && ! $title && ! $text ) {
|
|
return;
|
|
}
|
|
printf( '<div class="ga-head%s">', $args['center'] ? ' ga-head--center' : '' );
|
|
echo '<div class="ga-head__main">';
|
|
if ( $eyebrow ) {
|
|
echo '<span class="ga-eyebrow">' . esc_html( $eyebrow ) . '</span>';
|
|
}
|
|
if ( $title ) {
|
|
printf( '<h%1$d>%2$s</h%1$d>', (int) $args['h'], ga_highlight( $title ) );
|
|
}
|
|
if ( $text ) {
|
|
echo '<p class="ga-head__text">' . esc_html( $text ) . '</p>';
|
|
}
|
|
echo '</div>';
|
|
echo $args['extra']; // HTML ya escapado por quien llama.
|
|
echo '</div>';
|
|
}
|
|
|
|
/** Lista de líneas de un textarea del personalizador. */
|
|
function ga_lines( $text ) {
|
|
return array_values( array_filter( array_map( 'trim', explode( "\n", (string) $text ) ) ) );
|
|
}
|
|
|
|
/** URL configurable: vacía → valor por defecto; relativa → se respeta. */
|
|
function ga_link( $value, $fallback ) {
|
|
return '' !== trim( (string) $value ) ? $value : $fallback;
|
|
}
|
|
|
|
/* Título de archivo sin prefijo "Archivo:" */
|
|
add_filter( 'get_the_archive_title_prefix', '__return_empty_string' );
|
|
|
|
/* Extracto corto */
|
|
add_filter( 'excerpt_length', fn() => 24 );
|
|
add_filter( 'excerpt_more', fn() => '…' );
|
|
|
|
/* Formato de fecha según idioma */
|
|
add_filter( 'option_date_format', fn( $f ) => is_admin() ? $f : ga__( 'j \\d\\e F \\d\\e Y', 'Y\\(\\e\\)\\k\\o F\\r\\e\\n j\\a' ) );
|