array( 'name' => 'Animales', 'singular_name' => 'Animal', 'add_new' => 'Añadir animal', 'add_new_item' => 'Añadir nuevo animal', 'edit_item' => 'Editar animal', 'new_item' => 'Nuevo animal', 'view_item' => 'Ver ficha', 'search_items' => 'Buscar animales', 'not_found' => 'No hay animales', 'not_found_in_trash' => 'No hay animales en la papelera', 'all_items' => 'Todos los animales', 'featured_image' => 'Foto principal', 'set_featured_image' => 'Elegir foto principal', 'remove_featured_image' => 'Quitar foto principal', 'use_featured_image' => 'Usar como foto principal', 'excerpt' => 'Frase corta', 'menu_name' => 'Animales', ), 'public' => true, 'has_archive' => 'adopta', 'rewrite' => array( 'slug' => 'adopta', 'with_front' => false ), 'menu_icon' => 'dashicons-pets', 'menu_position' => 5, 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ), 'show_in_rest' => true, 'rest_base' => 'animales', 'show_in_graphql' => true, 'graphql_single_name' => 'animal', 'graphql_plural_name' => 'animales', ) ); register_taxonomy( 'especie', 'animal', array( 'labels' => array( 'name' => 'Especies', 'singular_name' => 'Especie', 'add_new_item' => 'Añadir especie', ), 'hierarchical' => true, 'show_admin_column' => true, 'show_in_rest' => true, 'rest_base' => 'especies', 'show_in_graphql' => true, 'graphql_single_name' => 'especie', 'graphql_plural_name' => 'especies', 'rewrite' => array( 'slug' => 'especie', 'with_front' => false ), ) ); register_post_type( 'ga_mensaje', array( 'labels' => array( 'name' => 'Mensajes', 'singular_name' => 'Mensaje', 'edit_item' => 'Mensaje', 'all_items' => 'Mensajes recibidos', 'not_found' => 'Aún no hay mensajes', ), 'public' => false, 'show_ui' => true, 'menu_icon' => 'dashicons-email-alt', 'menu_position' => 6, 'supports' => array( 'title', 'editor' ), 'capability_type' => 'post', 'capabilities' => array( 'create_posts' => 'do_not_allow' ), 'map_meta_cap' => true, ) ); foreach ( array_keys( ga_fields() ) as $key ) { register_post_meta( 'animal', "_ga_$key", array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'auth_callback' => fn() => current_user_can( 'edit_posts' ), ) ); } } ); /* ------------------------------------------------------------------------- * Campos de la ficha * * 'shared' => true: el dato es común a la versión en castellano y en euskera. * Los textos (carácter, necesidades…) se escriben en cada idioma. * ---------------------------------------------------------------------- */ function ga_fields() { return array( 'estado' => array( 'shared' => true, 'label' => ga__( 'Estado', 'Egoera' ), 'type' => 'select', 'options' => array( 'disponible' => ga__( 'Disponible', 'Eskuragarri' ), 'proceso' => ga__( 'En proceso', 'Prozesuan' ), 'adoptado' => ga__( 'Adoptado', 'Adoptatuta' ), ), 'help' => 'Los adoptados salen solos del listado.' ), 'destacado' => array( 'shared' => true, 'label' => ga__( 'Destacado', 'Nabarmendua' ), 'type' => 'checkbox', 'help' => 'Aparece en la portada. Útil para quien lleva más tiempo esperando o necesita un hogar especial.' ), 'sexo' => array( 'shared' => true, 'label' => ga__( 'Sexo', 'Sexua' ), 'type' => 'select', 'options' => array( 'macho' => ga__( 'Macho', 'Arra' ), 'hembra' => ga__( 'Hembra', 'Emea' ), ) ), 'nacimiento' => array( 'shared' => true, 'label' => ga__( 'Fecha de nacimiento', 'Jaiotze-data' ), 'type' => 'date', 'help' => 'Si no se conoce, deja la fecha vacía y rellena la edad aproximada.' ), 'edad' => array( 'shared' => true, 'label' => ga__( 'Edad aproximada', 'Gutxi gorabeherako adina' ), 'type' => 'number' ), 'edad_unidad' => array( 'shared' => true, 'label' => ga__( 'Edad en', 'Adina' ), 'type' => 'select', 'options' => array( 'anos' => ga__( 'Años', 'Urteak' ), 'meses' => ga__( 'Meses', 'Hilabeteak' ), ) ), 'tamano' => array( 'shared' => true, 'label' => ga__( 'Tamaño', 'Tamaina' ), 'type' => 'select', 'options' => array( 'pequeno' => ga__( 'Pequeño', 'Txikia' ), 'mediano' => ga__( 'Mediano', 'Ertaina' ), 'grande' => ga__( 'Grande', 'Handia' ), ), 'help' => 'Solo en perros.' ), 'llegada' => array( 'shared' => true, 'label' => ga__( 'En Bilagun desde', 'Bilagunen noiztik' ), 'type' => 'date', 'help' => 'Fecha de entrada, solo si está confirmada.' ), 'galeria' => array( 'shared' => true, 'label' => ga__( 'Más fotos', 'Argazki gehiago' ), 'type' => 'gallery' ), 'video' => array( 'shared' => true, 'label' => ga__( 'Vídeo', 'Bideoa' ), 'type' => 'url', 'help' => 'Enlace de YouTube, Vimeo o Instagram, o un vídeo subido a la biblioteca.' ), 'caracter' => array( 'label' => ga__( 'Cómo es', 'Nolakoa den' ), 'type' => 'textarea', 'help' => 'Su carácter, contado como en redes.' ), 'necesidades' => array( 'label' => ga__( 'Qué necesita', 'Zer behar duen' ), 'type' => 'textarea' ), 'convivencia' => array( 'label' => ga__( 'Convivencia con niños y otros animales', 'Haurrekin eta beste animaliekin bizikidetza' ), 'type' => 'textarea', 'help' => 'Solo lo observado, con contexto. Déjalo vacío si no lo sabemos.' ), 'hogar' => array( 'label' => ga__( 'El hogar que buscamos', 'Bilatzen dugun etxea' ), 'type' => 'textarea' ), ); } function ga_get( $post_id, $key ) { return (string) get_post_meta( $post_id, "_ga_$key", true ); } /** Estado normalizado (acepta valores antiguos). */ function ga_status( $post_id ) { $estado = ga_get( $post_id, 'estado' ); return match ( $estado ) { 'adoptado' => 'adoptado', 'proceso', 'reservado' => 'proceso', default => 'disponible', }; } /** Etiqueta legible de un campo de opciones. */ function ga_label( $post_id, $key ) { $fields = ga_fields(); $value = 'estado' === $key ? ga_status( $post_id ) : ga_get( $post_id, $key ); if ( '' === $value || ! isset( $fields[ $key ] ) ) { return ''; } $field = $fields[ $key ]; if ( 'select' === $field['type'] ) { return $field['options'][ $value ] ?? ''; } if ( 'date' === $field['type'] ) { return date_i18n( get_option( 'date_format' ), strtotime( $value ) ); } return $value; } /** Edad legible: calculada desde la fecha de nacimiento o la aproximada. */ function ga_age( $post_id ) { $birth = ga_get( $post_id, 'nacimiento' ); if ( $birth && strtotime( $birth ) ) { $diff = date_diff( date_create( $birth ), date_create( 'today' ) ); $n = $diff->y ?: $diff->m; $months = ! $diff->y; if ( ! $n ) { return ga__( 'Menos de un mes', 'Hilabete bat baino gutxiago' ); } } else { $n = ga_get( $post_id, 'edad' ); if ( '' === $n || ! is_numeric( $n ) ) { return ''; } $n = (float) $n + 0; $months = 'meses' === ga_get( $post_id, 'edad_unidad' ); } if ( 'eu' === ga_lang() ) { return $n . ( $months ? ' hilabete' : ' urte' ); } if ( $months ) { return $n . ( 1 == $n ? ' mes' : ' meses' ); } return $n . ( 1 == $n ? ' año' : ' años' ); } /** ¿Es un perro? (el tamaño solo se muestra en perros) */ function ga_is_dog( $post_id ) { $terms = get_the_terms( $post_id, 'especie' ) ?: array(); foreach ( $terms as $term ) { if ( in_array( $term->slug, array( 'perro', 'txakurra' ), true ) ) { return true; } } return false; } /** IDs de las fotos: la principal primero y después la galería. */ function ga_gallery_ids( $post_id ) { $ids = array_filter( array_map( 'absint', explode( ',', ga_get( $post_id, 'galeria' ) ) ) ); if ( has_post_thumbnail( $post_id ) ) { array_unshift( $ids, (int) get_post_thumbnail_id( $post_id ) ); } return array_values( array_unique( $ids ) ); } /* ------------------------------------------------------------------------- * Ficha en el panel * ---------------------------------------------------------------------- */ /* Editor clásico para los animales: la historia arriba y la ficha justo debajo, siempre a la vista. */ add_filter( 'use_block_editor_for_post_type', fn( $use, $type ) => 'animal' === $type ? false : $use, 10, 2 ); /* El cuadro de Yoast SEO va al final, debajo de la ficha. */ add_filter( 'wpseo_metabox_prio', fn() => 'low' ); add_action( 'add_meta_boxes_animal', function () { add_meta_box( 'ga_ficha', 'Ficha del animal', 'ga_render_metabox', 'animal', 'normal', 'high' ); } ); function ga_render_metabox( $post ) { wp_nonce_field( 'ga_ficha', 'ga_ficha_nonce' ); $fields = ga_fields(); $groups = array( 'Datos (comunes a castellano y euskera)' => array_filter( $fields, fn( $f ) => ! empty( $f['shared'] ) ), 'Textos de la ficha (en el idioma de esta página)' => array_filter( $fields, fn( $f ) => empty( $f['shared'] ) ), ); echo '

La historia se escribe en el editor principal. La «frase corta» (extracto) es opcional.

'; foreach ( $groups as $title => $group ) { echo '

' . esc_html( $title ) . '

'; foreach ( $group as $key => $f ) { $name = "ga_$key"; $value = 'estado' === $key ? ga_status( $post->ID ) : ga_get( $post->ID, $key ); echo ''; } echo ''; } } /* Selector de fotos de la galería */ add_action( 'admin_enqueue_scripts', function ( $hook ) { if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) || 'animal' !== get_current_screen()->post_type ) { return; } wp_enqueue_media(); wp_add_inline_style( 'wp-admin', '.ga-gallery-admin{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}.ga-gallery-admin__item{position:relative;cursor:move}.ga-gallery-admin__item img{display:block;width:80px;height:80px;object-fit:cover;border-radius:6px}.ga-gallery-admin__item button{position:absolute;top:-6px;right:-6px;width:22px;height:22px;border-radius:50%;border:0;background:#b32d2e;color:#fff;cursor:pointer;line-height:1}' ); wp_add_inline_script( 'media-editor', <<<'JS' document.addEventListener('DOMContentLoaded', function () { document.querySelectorAll('.ga-gallery-admin').forEach(function (box) { var input = document.getElementById(box.dataset.input); var sync = function () { input.value = Array.from(box.querySelectorAll('[data-id]')).map(function (el) { return el.dataset.id; }).join(','); }; box.addEventListener('click', function (e) { if (e.target.tagName === 'BUTTON') { e.target.parentNode.remove(); sync(); } }); box.nextElementSibling.addEventListener('click', function () { var frame = wp.media({ title: 'Fotos del animal', multiple: 'add', library: { type: 'image' }, button: { text: 'Añadir a la ficha' } }); frame.on('select', function () { frame.state().get('selection').each(function (att) { if (box.querySelector('[data-id="' + att.id + '"]')) return; var src = (att.get('sizes') && att.get('sizes').thumbnail ? att.get('sizes').thumbnail.url : att.get('url')); var item = document.createElement('span'); item.className = 'ga-gallery-admin__item'; item.dataset.id = att.id; item.innerHTML = ''; box.appendChild(item); }); sync(); }); frame.open(); }); }); }); JS ); } ); add_action( 'save_post_animal', function ( $post_id ) { if ( ! isset( $_POST['ga_ficha_nonce'] ) || ! wp_verify_nonce( $_POST['ga_ficha_nonce'], 'ga_ficha' ) ) { return; } if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) ) { return; } foreach ( ga_fields() as $key => $f ) { $raw = wp_unslash( $_POST[ "ga_$key" ] ?? '' ); $value = match ( $f['type'] ) { 'select' => isset( $f['options'][ $raw ] ) ? $raw : '', 'checkbox' => $raw ? '1' : '', 'textarea' => sanitize_textarea_field( $raw ), 'gallery' => implode( ',', array_filter( array_map( 'absint', explode( ',', $raw ) ) ) ), 'url' => esc_url_raw( $raw ), 'number' => '' === $raw ? '' : (string) absint( $raw ), 'date' => preg_match( '/^\d{4}-\d{2}-\d{2}$/', $raw ) ? $raw : '', default => sanitize_text_field( $raw ), }; if ( ! empty( $f['shared'] ) ) { ga_update_meta_all_languages( $post_id, "_ga_$key", $value ); } else { update_post_meta( $post_id, "_ga_$key", $value ); } } } ); /** Guarda un campo común en el animal y en todas sus traducciones. */ function ga_update_meta_all_languages( $post_id, $meta_key, $value ) { $ids = array( $post_id ); if ( function_exists( 'pll_get_post_translations' ) ) { $ids = array_unique( array_merge( $ids, array_values( pll_get_post_translations( $post_id ) ) ) ); } foreach ( $ids as $id ) { update_post_meta( $id, $meta_key, $value ); } } /* Polylang: al crear o sincronizar traducciones, no copiar los textos (se escriben en cada idioma). */ add_filter( 'pll_copy_post_metas', function ( $metas, $sync ) { $texts = array_map( fn( $k ) => "_ga_$k", array_keys( array_filter( ga_fields(), fn( $f ) => empty( $f['shared'] ) ) ) ); return $sync ? array_values( array_diff( $metas, $texts ) ) : $metas; }, 10, 2 ); /* Columnas del listado del panel */ add_filter( 'manage_animal_posts_columns', function ( $cols ) { $new = array(); foreach ( $cols as $k => $v ) { if ( 'title' === $k ) { $new['ga_foto'] = 'Foto'; } $new[ $k ] = $v; if ( 'title' === $k ) { $new['ga_estado'] = 'Estado'; $new['ga_destacado'] = '★'; } } return $new; } ); add_action( 'manage_animal_posts_custom_column', function ( $col, $post_id ) { if ( 'ga_foto' === $col ) { echo get_the_post_thumbnail( $post_id, array( 60, 60 ) ); } elseif ( 'ga_estado' === $col ) { echo esc_html( ga_label( $post_id, 'estado' ) ); } elseif ( 'ga_destacado' === $col ) { echo ga_get( $post_id, 'destacado' ) ? '★' : ''; } }, 10, 2 ); /* Título de la pestaña del listado en el idioma actual */ add_filter( 'document_title_parts', function ( $parts ) { if ( is_post_type_archive( 'animal' ) ) { $parts['title'] = ga__( 'Adopta', 'Adoptatu' ); } return $parts; } ); /* ------------------------------------------------------------------------- * Consultas * ---------------------------------------------------------------------- */ /** meta_query de los estados visibles en los listados. */ function ga_visible_meta_query() { $hidden = array( 'adoptado' ); if ( ! ga_setting( 'adopcion', 'show_in_process', true ) ) { $hidden = array_merge( $hidden, array( 'proceso', 'reservado' ) ); } return array( 'relation' => 'OR', array( 'key' => '_ga_estado', 'value' => $hidden, 'compare' => 'NOT IN' ), array( 'key' => '_ga_estado', 'compare' => 'NOT EXISTS' ), ); } /** Orden de los listados según el ajuste «Orden». */ function ga_order_args() { return 'antiguos' === ga_setting( 'adopcion', 'order', 'recientes' ) ? array( 'orderby' => 'date', 'order' => 'ASC' ) : array( 'orderby' => 'date', 'order' => 'DESC' ); } add_action( 'pre_get_posts', function ( $q ) { if ( is_admin() || ! $q->is_main_query() || ! ( $q->is_post_type_archive( 'animal' ) || $q->is_tax( 'especie' ) ) ) { return; } $q->set( 'meta_query', array( ga_visible_meta_query() ) ); $q->set( 'posts_per_page', 24 ); foreach ( ga_order_args() as $k => $v ) { $q->set( $k, $v ); } } ); /** * Consulta reutilizable de animales (Polylang filtra por el idioma actual). * * @param array $args numero, especie (slugs), estado (lista o 'adoptado'), destacados (bool). */ function ga_query_animals( $args = array() ) { $args = wp_parse_args( $args, array( 'numero' => 6, 'especie' => '', 'estado' => '', 'destacados' => false ) ); $query = array( 'post_type' => 'animal', 'posts_per_page' => (int) $args['numero'], 'no_found_rows' => true, ) + ga_order_args(); if ( $args['especie'] ) { $query['tax_query'] = array( array( 'taxonomy' => 'especie', 'field' => 'slug', 'terms' => array_map( 'trim', explode( ',', $args['especie'] ) ), ) ); } $meta = array(); if ( $args['estado'] ) { $meta[] = array( 'key' => '_ga_estado', 'value' => array_map( 'trim', explode( ',', $args['estado'] ) ), 'compare' => 'IN' ); } else { $meta[] = ga_visible_meta_query(); } if ( $args['destacados'] ) { $meta[] = array( 'key' => '_ga_destacado', 'value' => '1' ); } $query['meta_query'] = $meta; $q = new WP_Query( $query ); // Si no hay destacados marcados, se muestran los que llevan más tiempo esperando. if ( $args['destacados'] && ! $q->have_posts() ) { unset( $query['meta_query'][1] ); $query['orderby'] = 'date'; $query['order'] = 'ASC'; $q = new WP_Query( $query ); } return $q; } /* [ga_animales numero="6" especie="perro" destacados="1" vista="carrusel"] */ add_shortcode( 'ga_animales', function ( $atts ) { $atts = shortcode_atts( array( 'numero' => 6, 'especie' => '', 'estado' => '', 'destacados' => '', 'vista' => 'rejilla' ), $atts, 'ga_animales' ); $atts['destacados'] = in_array( $atts['destacados'], array( '1', 'si', 'true' ), true ); $q = ga_query_animals( $atts ); ob_start(); if ( $q->have_posts() ) { $carousel = 'carrusel' === $atts['vista']; $carousel ? ga_carousel_open() : print( '
' ); while ( $q->have_posts() ) { $q->the_post(); ga_animal_card(); } $carousel ? ga_carousel_close() : print( '
' ); } else { echo '

' . esc_html( ga__( 'Ahora mismo no hay animales en esta sección. ¡Vuelve pronto!', 'Une honetan ez dago animaliarik atal honetan. Itzuli laster!' ) ) . '

'; } wp_reset_postdata(); return ob_get_clean(); } ); /** Apertura y cierre de un carrusel accesible (el tema aporta el CSS y el JS). */ function ga_carousel_open( $label = '' ) { printf( '', esc_attr( ga__( 'Anteriores', 'Aurrekoak' ) ), esc_attr( ga__( 'Siguientes', 'Hurrengoak' ) ) ); } /** Tarjeta de un animal (dentro del loop). El tema la define en template-parts/animal-card.php. */ function ga_animal_card() { if ( locate_template( 'template-parts/animal-card.php' ) ) { get_template_part( 'template-parts/animal-card' ); return; } printf( '
%s
', esc_url( get_permalink() ), esc_html( get_the_title() ) ); }