Panel: fichas por revisar, acciones en bloque, destacar con un clic y borrado de mensajes antiguos

- Escritorio: animales sin foto, sin euskera, sin «Cómo es», sin especie o en proceso
  desde hace más de N días (configurable).
- Listado: acciones en bloque de estado y destacado (en los dos idiomas) y estrella clicable.
- Se guarda la fecha del último cambio de estado.
- Borrado diario de mensajes del formulario más antiguos que N meses (12 por defecto).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 10:17:14 +02:00
parent c9011d4677
commit c1bbf78b5c
4 changed files with 204 additions and 2 deletions

View File

@@ -12,3 +12,4 @@ require_once __DIR__ . '/gure-ametsa/i18n.php';
require_once __DIR__ . '/gure-ametsa/animals.php'; require_once __DIR__ . '/gure-ametsa/animals.php';
require_once __DIR__ . '/gure-ametsa/forms.php'; require_once __DIR__ . '/gure-ametsa/forms.php';
require_once __DIR__ . '/gure-ametsa/api.php'; require_once __DIR__ . '/gure-ametsa/api.php';
require_once __DIR__ . '/gure-ametsa/admin.php';

View File

@@ -0,0 +1,201 @@
<?php
/**
* Herramientas del panel: fichas por revisar, acciones en bloque, destacar con un clic
* y borrado automático de mensajes antiguos.
*/
defined( 'ABSPATH' ) || exit;
/* -------------------------------------------------------------------------
* Fecha del último cambio de estado (para saber cuánto lleva «en proceso»)
* ---------------------------------------------------------------------- */
$ga_status_changed = function ( $meta_id, $post_id, $meta_key ) {
if ( '_ga_estado' === $meta_key ) {
update_post_meta( $post_id, '_ga_estado_desde', current_time( 'Y-m-d' ) );
}
};
add_action( 'added_post_meta', $ga_status_changed, 10, 3 );
add_action( 'updated_post_meta', $ga_status_changed, 10, 3 );
/* -------------------------------------------------------------------------
* Escritorio: fichas por revisar
* ---------------------------------------------------------------------- */
/** Animales (versión en castellano) con algo pendiente, agrupados por problema. */
function ga_review_issues() {
$animals = get_posts( array(
'post_type' => 'animal',
'post_status' => array( 'publish', 'draft', 'pending' ),
'numberposts' => -1,
'lang' => function_exists( 'pll_default_language' ) ? pll_default_language() : '',
) );
$days = max( 1, (int) ga_setting( 'adopcion', 'review_days', 30 ) );
$issues = array(
'photo' => array( 'Sin foto principal', array() ),
'eu' => array( 'Sin versión en euskera', array() ),
'text' => array( 'Sin texto «Cómo es» (castellano o euskera)', array() ),
'species' => array( 'Sin especie (perro o gato)', array() ),
'process' => array( sprintf( 'En proceso desde hace más de %d días', $days ), array() ),
);
foreach ( $animals as $animal ) {
if ( 'adoptado' === ga_status( $animal->ID ) ) {
continue;
}
$eu = function_exists( 'pll_get_post' ) ? pll_get_post( $animal->ID, 'eu' ) : 0;
if ( ! has_post_thumbnail( $animal ) ) {
$issues['photo'][1][] = $animal;
}
if ( function_exists( 'pll_get_post' ) && ! $eu ) {
$issues['eu'][1][] = $animal;
}
if ( '' === ga_get( $animal->ID, 'caracter' ) || ( $eu && '' === ga_get( $eu, 'caracter' ) ) ) {
$issues['text'][1][] = $animal;
}
if ( ! has_term( '', 'especie', $animal ) ) {
$issues['species'][1][] = $animal;
}
$since = ga_get( $animal->ID, 'estado_desde' );
if ( 'proceso' === ga_status( $animal->ID ) && $since && strtotime( $since ) < strtotime( "-$days days" ) ) {
$issues['process'][1][] = $animal;
}
}
return array_filter( $issues, fn( $i ) => ! empty( $i[1] ) );
}
add_action( 'wp_dashboard_setup', function () {
if ( ! current_user_can( 'edit_posts' ) ) {
return;
}
wp_add_dashboard_widget( 'ga_review', 'Bilagun · Fichas por revisar', function () {
$issues = ga_review_issues();
if ( ! $issues ) {
echo '<p>✓ Todas las fichas están completas.</p>';
}
foreach ( $issues as [ $title, $animals ] ) {
printf( '<h4 style="margin:14px 0 6px">%s <span class="count">(%d)</span></h4><ul style="margin:0 0 0 1em;list-style:disc">', esc_html( $title ), count( $animals ) );
foreach ( array_slice( $animals, 0, 8 ) as $animal ) {
printf( '<li><a href="%s">%s</a></li>', esc_url( get_edit_post_link( $animal ) ), esc_html( get_the_title( $animal ) ) );
}
if ( count( $animals ) > 8 ) {
echo '<li>…</li>';
}
echo '</ul>';
}
$messages = (int) wp_count_posts( 'ga_mensaje' )->private;
printf(
'<p style="margin-top:16px;border-top:1px solid #eee;padding-top:10px"><a class="button" href="%s">Añadir animal</a> <a class="button" href="%s">Mensajes (%d)</a></p>',
esc_url( admin_url( 'post-new.php?post_type=animal' ) ),
esc_url( admin_url( 'edit.php?post_type=ga_mensaje' ) ),
$messages
);
} );
// Arriba del todo.
global $wp_meta_boxes;
$widget = $wp_meta_boxes['dashboard']['normal']['core']['ga_review'] ?? null;
if ( $widget ) {
unset( $wp_meta_boxes['dashboard']['normal']['core']['ga_review'] );
$wp_meta_boxes['dashboard']['normal']['high']['ga_review'] = $widget;
}
} );
/* -------------------------------------------------------------------------
* Listado de animales: acciones en bloque y destacar con un clic
* ---------------------------------------------------------------------- */
function ga_bulk_actions() {
return array(
'ga_disponible' => array( 'Estado: Disponible', 'estado', 'disponible' ),
'ga_proceso' => array( 'Estado: En proceso', 'estado', 'proceso' ),
'ga_adoptado' => array( 'Estado: Adoptado', 'estado', 'adoptado' ),
'ga_destacar' => array( 'Destacar en portada', 'destacado', '1' ),
'ga_no_destacar' => array( 'Quitar de destacados', 'destacado', '' ),
);
}
add_filter( 'bulk_actions-edit-animal', function ( $actions ) {
foreach ( ga_bulk_actions() as $key => [ $label ] ) {
$actions[ $key ] = $label;
}
return $actions;
} );
add_filter( 'handle_bulk_actions-edit-animal', function ( $redirect, $action, $ids ) {
$actions = ga_bulk_actions();
if ( ! isset( $actions[ $action ] ) ) {
return $redirect;
}
[ , $field, $value ] = $actions[ $action ];
$done = 0;
foreach ( $ids as $id ) {
if ( current_user_can( 'edit_post', $id ) ) {
ga_update_meta_all_languages( $id, "_ga_$field", $value );
++$done;
}
}
return add_query_arg( 'ga_bulk', $done, $redirect );
}, 10, 3 );
add_action( 'admin_notices', function () {
if ( isset( $_GET['ga_bulk'] ) && 'edit-animal' === ( get_current_screen()->id ?? '' ) ) {
printf( '<div class="notice notice-success is-dismissible"><p>%d animales actualizados (también su versión en el otro idioma).</p></div>', absint( $_GET['ga_bulk'] ) );
}
} );
/* Estrella clicable en la columna «★» */
add_action( 'manage_animal_posts_custom_column', function ( $col, $post_id ) {
if ( 'ga_destacado' !== $col || ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$on = (bool) ga_get( $post_id, 'destacado' );
$url = wp_nonce_url( admin_url( 'admin-post.php?action=ga_toggle_featured&post=' . $post_id ), 'ga_toggle_featured_' . $post_id );
printf(
'<a href="%s" title="%s" style="font-size:18px;text-decoration:none;color:%s">%s</a>',
esc_url( $url ),
esc_attr( $on ? 'Quitar de destacados' : 'Destacar en portada' ),
$on ? '#bd8537' : '#c3c4c7',
$on ? '★' : '☆'
);
}, 20, 2 );
add_action( 'admin_post_ga_toggle_featured', function () {
$id = absint( $_GET['post'] ?? 0 );
check_admin_referer( 'ga_toggle_featured_' . $id );
if ( ! current_user_can( 'edit_post', $id ) ) {
wp_die( 'Sin permiso.' );
}
ga_update_meta_all_languages( $id, '_ga_destacado', ga_get( $id, 'destacado' ) ? '' : '1' );
wp_safe_redirect( wp_get_referer() ?: admin_url( 'edit.php?post_type=animal' ) );
exit;
} );
/* -------------------------------------------------------------------------
* Protección de datos: borrado automático de mensajes antiguos
* ---------------------------------------------------------------------- */
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'ga_purge_messages' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'ga_purge_messages' );
}
} );
add_action( 'ga_purge_messages', 'ga_purge_messages' );
/** Borra los mensajes del formulario más antiguos que el plazo configurado (0 = nunca). */
function ga_purge_messages() {
$months = (int) ga_setting( 'adopcion', 'retention_months', 12 );
if ( $months <= 0 ) {
return 0;
}
$old = get_posts( array(
'post_type' => 'ga_mensaje',
'post_status' => 'any',
'numberposts' => 200,
'fields' => 'ids',
'date_query' => array( array( 'before' => "$months months ago" ) ),
) );
foreach ( $old as $id ) {
wp_delete_post( $id, true );
}
return count( $old );
}

View File

@@ -382,8 +382,6 @@ add_action( 'manage_animal_posts_custom_column', function ( $col, $post_id ) {
echo get_the_post_thumbnail( $post_id, array( 60, 60 ) ); echo get_the_post_thumbnail( $post_id, array( 60, 60 ) );
} elseif ( 'ga_estado' === $col ) { } elseif ( 'ga_estado' === $col ) {
echo esc_html( ga_label( $post_id, 'estado' ) ); echo esc_html( ga_label( $post_id, 'estado' ) );
} elseif ( 'ga_destacado' === $col ) {
echo ga_get( $post_id, 'destacado' ) ? '★' : '';
} }
}, 10, 2 ); }, 10, 2 );

View File

@@ -236,6 +236,8 @@ function ga_customizer_registry() {
'process_note' => $t( 'Proceso · nota', 'Enviar la solicitud no confirma una adopción: es el primer paso para conocernos.', 'Eskaera bidaltzeak ez du adopzioa baieztatzen: elkar ezagutzeko lehen urratsa da.' ), 'process_note' => $t( 'Proceso · nota', 'Enviar la solicitud no confirma una adopción: es el primer paso para conocernos.', 'Eskaera bidaltzeak ez du adopzioa baieztatzen: elkar ezagutzeko lehen urratsa da.' ),
'share' => array( 'type' => 'checkbox', 'label' => 'Botones para compartir la ficha (WhatsApp, Facebook, copiar enlace)', 'default' => true ), 'share' => array( 'type' => 'checkbox', 'label' => 'Botones para compartir la ficha (WhatsApp, Facebook, copiar enlace)', 'default' => true ),
'form_email' => array( 'type' => 'email', 'label' => 'Correo que recibe las solicitudes y mensajes', 'default' => '', 'description' => 'Vacío = el de la variable GA_CONTACT_EMAIL del servidor.' ), 'form_email' => array( 'type' => 'email', 'label' => 'Correo que recibe las solicitudes y mensajes', 'default' => '', 'description' => 'Vacío = el de la variable GA_CONTACT_EMAIL del servidor.' ),
'review_days' => array( 'type' => 'number', 'label' => 'Avisar en el Escritorio si un animal lleva «En proceso» más de (días)', 'default' => 30, 'min' => 1, 'max' => 365 ),
'retention_months' => array( 'type' => 'number', 'label' => 'Borrar los mensajes del formulario pasados (meses, 0 = nunca)', 'default' => 12, 'min' => 0, 'max' => 120, 'description' => 'Protección de datos: no guardar datos personales más tiempo del necesario.' ),
), ),
), ),
'material' => array( 'material' => array(