Plugins bloques actualizados

This commit is contained in:
Jose Ibáñez
2026-02-14 18:21:10 +01:00
parent 26d7f11378
commit cc68c21d9a
7 changed files with 315 additions and 280 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -16,7 +16,7 @@ if (!defined('ABSPATH')) {
class Bloques_Actuaciones_Importer {
private const CSV_DELIMITER = ';';
private const CSV_DELIMITER_FALLBACK = ';';
private const NOMINATIM_DELAY_US = 1100000; // 1.1 s
private const NOMINATIM_USER_AGENT = 'BloquesTransicion/1.0 (WordPress; tangente.coop)';
@@ -411,6 +411,44 @@ class Bloques_Actuaciones_Importer {
/* ── CSV parser ── */
/**
* Detectar el delimitador del CSV analizando la primera línea.
* Prueba con ';' y ',' y elige el que produce más columnas.
*/
private static function detect_delimiter($path) {
$handle = fopen($path, 'r');
if (!$handle) {
return self::CSV_DELIMITER_FALLBACK;
}
// Saltar BOM
$bom = fread($handle, 3);
if ($bom !== "\xEF\xBB\xBF") {
rewind($handle);
}
$first_line = fgets($handle);
fclose($handle);
if ($first_line === false) {
return self::CSV_DELIMITER_FALLBACK;
}
$candidates = [',', ';', "\t"];
$best = self::CSV_DELIMITER_FALLBACK;
$best_count = 0;
foreach ($candidates as $delim) {
$count = count(str_getcsv($first_line, $delim));
if ($count > $best_count) {
$best_count = $count;
$best = $delim;
}
}
return $best;
}
private static function parse_csv($path, &$errors = []) {
$handle = fopen($path, 'r');
if (!$handle) {
@@ -424,7 +462,10 @@ class Bloques_Actuaciones_Importer {
rewind($handle);
}
$header = fgetcsv($handle, 0, self::CSV_DELIMITER);
// Auto-detectar delimitador
$delimiter = self::detect_delimiter($path);
$header = fgetcsv($handle, 0, $delimiter);
if ($header === false || empty($header)) {
fclose($handle);
$errors[] = __('Cabecera CSV no válida.', 'bloques-actuaciones-import');
@@ -434,7 +475,7 @@ class Bloques_Actuaciones_Importer {
$header = array_map('trim', $header);
$rows = [];
while (($raw = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) {
while (($raw = fgetcsv($handle, 0, $delimiter)) !== false) {
$padded = array_pad($raw, count($header), '');
$row = array_combine($header, array_slice($padded, 0, count($header)));
if (is_array($row)) {

View File

@@ -29,6 +29,128 @@
// Añadir cursor pointer a los shortcodes
$('.bloques-dashboard-shortcodes code').css('cursor', 'pointer').attr('title', 'Clic para copiar');
// Mostrar/ocultar metaboxes de Iniciativa y Línea de Trabajo en el editor de entradas
if (typeof bloquesAdminData !== 'undefined' && bloquesAdminData.isPostEditor) {
initConditionalTaxonomyBoxes();
}
});
/**
* Mostrar metaboxes de Iniciativa y Línea de Trabajo solo cuando
* la entrada tiene marcada la categoría "Bloques en Transición".
* Compatible con el editor clásico y Gutenberg.
*/
function initConditionalTaxonomyBoxes() {
const catIds = bloquesAdminData.bloquesCatIds || [];
if (!catIds.length) {
return;
}
// ── Editor clásico (metaboxes) ──
const $iniciativaBox = $('#iniciativadiv, #taxonomy-iniciativa').closest('.postbox');
const $lineaBox = $('#linea_trabajodiv, #taxonomy-linea_trabajo').closest('.postbox');
if ($iniciativaBox.length || $lineaBox.length) {
// Comprobar estado inicial y escuchar cambios en checkboxes de categorías
function checkClassicCategories() {
let isBloquesChecked = false;
catIds.forEach(function(catId) {
if ($('#in-category-' + catId).is(':checked')) {
isBloquesChecked = true;
}
});
if (isBloquesChecked) {
$iniciativaBox.slideDown(200);
$lineaBox.slideDown(200);
} else {
$iniciativaBox.slideUp(200);
$lineaBox.slideUp(200);
}
}
// Estado inicial (sin animación)
(function() {
let isBloquesChecked = false;
catIds.forEach(function(catId) {
if ($('#in-category-' + catId).is(':checked')) {
isBloquesChecked = true;
}
});
if (isBloquesChecked) {
$iniciativaBox.show();
$lineaBox.show();
} else {
$iniciativaBox.hide();
$lineaBox.hide();
}
})();
// Escuchar cambios en categorías
$('#categorychecklist').on('change', 'input[type="checkbox"]', checkClassicCategories);
}
// ── Gutenberg (editor de bloques) ──
if (typeof wp !== 'undefined' && wp.data && wp.data.select && wp.data.subscribe) {
let previousCategories = [];
// Selectores de los paneles de taxonomías en el sidebar de Gutenberg
function toggleGutenbergPanels(show) {
// Los metaboxes de taxonomías custom en Gutenberg tienen paneles con
// data-taxonomy o clases como .editor-post-taxonomies__hierarchical-terms-list
// Usamos un enfoque basado en MutationObserver + selectores
const selectors = [
'.editor-post-taxonomies__hierarchical-terms-list[data-taxonomy="iniciativa"]',
'.editor-post-taxonomies__hierarchical-terms-list[data-taxonomy="linea_trabajo"]',
// Paneles en el sidebar del editor
'[class*="iniciativa"]',
'[class*="linea_trabajo"]',
];
// Buscar los paneles padre (.components-panel__body) que contienen estas taxonomías
const panels = document.querySelectorAll('.components-panel__body');
panels.forEach(function(panel) {
const heading = panel.querySelector('.components-panel__body-title button');
if (heading) {
const text = heading.textContent.toLowerCase();
if (text.includes('iniciativa') || text.includes('línea') || text.includes('linea')) {
panel.style.display = show ? '' : 'none';
}
}
});
// También ocultar los metaboxes clásicos que pueda insertar WP en Gutenberg
['iniciativadiv', 'linea_trabajodiv'].forEach(function(id) {
const el = document.getElementById(id);
if (el) {
el.style.display = show ? '' : 'none';
}
});
}
wp.data.subscribe(function() {
const editor = wp.data.select('core/editor');
if (!editor) return;
const currentCategories = editor.getEditedPostAttribute('categories') || [];
// Solo actuar si las categorías cambiaron
if (JSON.stringify(currentCategories) === JSON.stringify(previousCategories)) {
return;
}
previousCategories = currentCategories.slice();
// Comprobar si alguna de las categorías de Bloques está seleccionada
const isBloquesChecked = catIds.some(function(catId) {
return currentCategories.includes(catId);
});
toggleGutenbergPanels(isBloquesChecked);
});
}
}
})(jQuery);

View File

@@ -22,11 +22,10 @@
const $container = $('#' + containerId);
const $itemsContainer = $container.find('.bloques-items');
// Estado de filtros activos (incluye category para noticias)
// Estado de filtros activos
let activeFilters = {
iniciativa: '',
linea_trabajo: '',
category: ''
linea_trabajo: ''
};
// Click en botones de filtro
@@ -71,7 +70,6 @@
type: type,
iniciativa: filters.iniciativa || '',
linea_trabajo: filters.linea_trabajo || '',
category: filters.category || '',
search: search,
widget: widget,
upcoming: upcoming
@@ -92,7 +90,7 @@
}
// Ocultar botón "Ver más" cuando hay filtros activos
const hasActiveFilters = filters.iniciativa || filters.linea_trabajo || filters.category || search;
const hasActiveFilters = filters.iniciativa || filters.linea_trabajo || search;
$container.find('.bloques-load-more-wrapper').toggle(!hasActiveFilters);
// Resetear offset
@@ -127,8 +125,7 @@
// Obtener filtros activos
const activeFilters = {
iniciativa: $filters.find('.bloques-filter-btn[data-taxonomy="iniciativa"].active').data('value') || '',
linea_trabajo: $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || '',
category: $filters.find('.bloques-filter-btn[data-taxonomy="category"].active').data('value') || ''
linea_trabajo: $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || ''
};
// Debounce
@@ -160,12 +157,10 @@
const $filters = $container.find('.bloques-filters');
let iniciativa = '';
let linea = '';
let category = '';
if ($filters.length) {
iniciativa = $filters.find('.bloques-filter-btn[data-taxonomy="iniciativa"].active').data('value') || '';
linea = $filters.find('.bloques-filter-btn[data-taxonomy="linea_trabajo"].active').data('value') || '';
category = $filters.find('.bloques-filter-btn[data-taxonomy="category"].active').data('value') || '';
}
// Mostrar loading
@@ -183,7 +178,6 @@
widget: widget,
iniciativa: iniciativa,
linea_trabajo: linea,
category: category,
upcoming: upcoming
},
success: function(response) {

View File

@@ -124,11 +124,16 @@ final class Bloques_Transicion {
public function admin_assets($hook) {
$screen = get_current_screen();
// Solo cargar en páginas del plugin
if ($screen && (
strpos($screen->id, 'bloques') !== false ||
in_array($screen->post_type, ['actuacion', 'recurso_bloques', 'evento_bloques'])
)) {
if (!$screen) {
return;
}
// Cargar en páginas del plugin y también en editor de entradas (post)
$is_bloques_page = strpos($screen->id, 'bloques') !== false ||
in_array($screen->post_type, ['actuacion', 'recurso_bloques', 'evento_bloques']);
$is_post_editor = $screen->post_type === 'post' && in_array($hook, ['post.php', 'post-new.php']);
if ($is_bloques_page || $is_post_editor) {
wp_enqueue_style(
'bloques-admin',
BLOQUES_PLUGIN_URL . 'assets/css/admin.css',
@@ -143,6 +148,25 @@ final class Bloques_Transicion {
BLOQUES_VERSION,
true
);
// Datos para el JS del admin
if ($is_post_editor) {
// Obtener los IDs de las categorías "bloques-en-transicion" y "bloques"
$bloques_cat_ids = [];
$bloques_cat = get_term_by('slug', 'bloques-en-transicion', 'category');
if ($bloques_cat) {
$bloques_cat_ids[] = $bloques_cat->term_id;
}
$bloques_cat2 = get_term_by('slug', 'bloques', 'category');
if ($bloques_cat2) {
$bloques_cat_ids[] = $bloques_cat2->term_id;
}
wp_localize_script('bloques-admin', 'bloquesAdminData', [
'isPostEditor' => true,
'bloquesCatIds' => $bloques_cat_ids,
]);
}
}
}

View File

@@ -78,6 +78,57 @@ class Bloques_Shortcodes {
return $permalink;
}
/**
* Construir tax_query correcta según el tipo de contenido.
*
* Para noticias: siempre incluye la condición base (category = bloques-en-transicion)
* y la combina con AND con los filtros de iniciativa/linea_trabajo.
* Para CPTs: solo filtra por iniciativa/linea_trabajo.
*
* @param string $type Tipo de contenido (noticias, actuaciones, recursos, eventos)
* @param string $iniciativa Slug de iniciativa para filtrar (vacío = sin filtro)
* @param string $linea Slug de línea de trabajo para filtrar (vacío = sin filtro)
* @return array tax_query lista para WP_Query
*/
private static function build_tax_query($type, $iniciativa = '', $linea = '') {
$tax_query = [];
// Para noticias: siempre requerir la categoría base
if ($type === 'noticias') {
$tax_query[] = [
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['bloques-en-transicion', 'bloques'],
'include_children' => true,
];
}
// Filtro por iniciativa
if (!empty($iniciativa)) {
$tax_query[] = [
'taxonomy' => 'iniciativa',
'field' => 'slug',
'terms' => is_array($iniciativa) ? $iniciativa : explode(',', $iniciativa),
];
}
// Filtro por línea de trabajo
if (!empty($linea)) {
$tax_query[] = [
'taxonomy' => 'linea_trabajo',
'field' => 'slug',
'terms' => is_array($linea) ? $linea : explode(',', $linea),
];
}
// Si hay más de una condición, usar AND
if (count($tax_query) > 1) {
$tax_query['relation'] = 'AND';
}
return $tax_query;
}
/**
* Mapeo de tipos
*/
@@ -106,14 +157,6 @@ class Bloques_Shortcodes {
'template' => 'noticias',
'label_singular' => __('noticia', 'bloques-transicion'),
'label_plural' => __('noticias', 'bloques-transicion'),
'tax_query' => [
'relation' => 'OR',
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['bloques-en-transicion', 'bloques'],
],
],
],
];
@@ -191,45 +234,11 @@ class Bloques_Shortcodes {
}
}
// Construir tax_query según el tipo
if ($atts['type'] === 'noticias') {
// Para noticias: siempre filtrar por categoría bloques-en-transicion
$args['tax_query'] = [
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['bloques-en-transicion', 'bloques'],
'include_children' => true,
],
];
} else {
// Para CPTs: filtros por taxonomía custom
$tax_query = [];
if (!empty($atts['iniciativa'])) {
$tax_query[] = [
'taxonomy' => 'iniciativa',
'field' => 'slug',
'terms' => explode(',', $atts['iniciativa']),
];
}
if (!empty($atts['linea'])) {
$tax_query[] = [
'taxonomy' => 'linea_trabajo',
'field' => 'slug',
'terms' => explode(',', $atts['linea']),
];
}
if (count($tax_query) > 1) {
$tax_query['relation'] = 'AND';
}
// Construir tax_query (para noticias incluye automáticamente la categoría base)
$tax_query = self::build_tax_query($atts['type'], $atts['iniciativa'], $atts['linea']);
if (!empty($tax_query)) {
$args['tax_query'] = $tax_query;
}
}
$query = new WP_Query($args);
$total_posts = $query->found_posts;
@@ -286,66 +295,6 @@ class Bloques_Shortcodes {
* Renderizar barra de filtros estilo botonera
*/
private static function render_filter_bar($type, $container_id, $show_search, $total_posts, $type_config) {
ob_start();
?>
<div class="bloques-filters" data-container="<?php echo esc_attr($container_id); ?>">
<?php if ($show_search): ?>
<div class="bloques-search-wrapper">
<input type="text" class="bloques-search-input" placeholder="<?php echo esc_attr(sprintf(__('Buscar %s...', 'bloques-transicion'), $type_config['label_plural'])); ?>">
</div>
<?php endif; ?>
<?php if ($type === 'noticias'): ?>
<?php
// Para noticias: mostrar subcategorías de "bloques-en-transicion" como filtros
$bloques_cat = get_term_by('slug', 'bloques-en-transicion', 'category');
if (!$bloques_cat) {
$bloques_cat = get_term_by('slug', 'bloques', 'category');
}
$child_cats = [];
if ($bloques_cat) {
$child_cats = get_terms([
'taxonomy' => 'category',
'child_of' => $bloques_cat->term_id,
'hide_empty' => true,
]);
}
if (!empty($child_cats) && !is_wp_error($child_cats)): ?>
<div class="bloques-filter-group">
<span class="bloques-filter-label"><?php _e('Filtrar por categoría', 'bloques-transicion'); ?></span>
<div class="bloques-filter-buttons">
<button type="button" class="bloques-filter-btn active" data-taxonomy="category" data-value="">
<?php _e('Todas', 'bloques-transicion'); ?>
</button>
<?php foreach ($child_cats as $cat):
// Intentar obtener color/icono de la iniciativa con el mismo slug
$color = '';
$icono = '';
$matched_iniciativa = get_term_by('slug', $cat->slug, 'iniciativa');
if ($matched_iniciativa) {
$color = get_field('color', $matched_iniciativa);
$icono = get_field('icono', $matched_iniciativa);
}
?>
<button type="button" class="bloques-filter-btn"
data-taxonomy="category"
data-value="<?php echo esc_attr($cat->slug); ?>"
style="<?php echo $color ? '--btn-active-color: ' . esc_attr($color) . ';' : ''; ?>">
<?php if ($icono): ?>
<img src="<?php echo esc_url($icono); ?>" alt="" class="bloques-filter-icon">
<?php endif; ?>
<?php echo esc_html($cat->name); ?>
</button>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<?php
// Para CPTs: mostrar filtros de iniciativa y línea de trabajo
$iniciativas = get_terms([
'taxonomy' => 'iniciativa',
'hide_empty' => true,
@@ -355,7 +304,15 @@ class Bloques_Shortcodes {
'taxonomy' => 'linea_trabajo',
'hide_empty' => true,
]);
ob_start();
?>
<div class="bloques-filters" data-container="<?php echo esc_attr($container_id); ?>">
<?php if ($show_search): ?>
<div class="bloques-search-wrapper">
<input type="text" class="bloques-search-input" placeholder="<?php echo esc_attr(sprintf(__('Buscar %s...', 'bloques-transicion'), $type_config['label_plural'])); ?>">
</div>
<?php endif; ?>
<?php if (!empty($iniciativas) && !is_wp_error($iniciativas)): ?>
<div class="bloques-filter-group">
@@ -406,7 +363,6 @@ class Bloques_Shortcodes {
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<div class="bloques-filters-footer">
<span class="bloques-results-count">
@@ -651,7 +607,7 @@ class Bloques_Shortcodes {
'subtitle' => __('Próximas actividades', 'bloques-transicion'),
'show_header' => 'true',
'link_text' => __('Ver calendario completo', 'bloques-transicion'),
'link_url' => '/bloques-en-transicion/eventos/',
'link_url' => '/bloques-en-transicion/noticias/',
'upcoming' => 'true',
'class' => '',
], $atts, 'bloques-agenda');
@@ -823,7 +779,6 @@ class Bloques_Shortcodes {
$type = sanitize_text_field($_POST['type'] ?? 'recursos');
$iniciativa = sanitize_text_field($_POST['iniciativa'] ?? '');
$linea = sanitize_text_field($_POST['linea_trabajo'] ?? '');
$category = sanitize_text_field($_POST['category'] ?? '');
$search = sanitize_text_field($_POST['search'] ?? '');
$widget = sanitize_text_field($_POST['widget'] ?? 'grid');
$upcoming = sanitize_text_field($_POST['upcoming'] ?? 'true');
@@ -860,60 +815,8 @@ class Bloques_Shortcodes {
}
}
// Construir tax_query según el tipo
if ($type === 'noticias') {
// Para noticias: filtrar por categorías de WordPress
$base_cat_query = [
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['bloques-en-transicion', 'bloques'],
'include_children' => true,
];
if (!empty($category)) {
// Filtro activo: base AND categoría seleccionada
$args['tax_query'] = [
'relation' => 'AND',
$base_cat_query,
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => $category,
'include_children' => true,
],
];
} else {
// Sin filtro: solo la condición base
$args['tax_query'] = [$base_cat_query];
}
} else {
// Para CPTs: filtrar por iniciativa y/o línea de trabajo
$tax_query = [];
if (!empty($iniciativa)) {
$tax_query[] = [
'taxonomy' => 'iniciativa',
'field' => 'slug',
'terms' => $iniciativa,
];
}
if (!empty($linea)) {
$tax_query[] = [
'taxonomy' => 'linea_trabajo',
'field' => 'slug',
'terms' => $linea,
];
}
if (count($tax_query) > 1) {
$tax_query['relation'] = 'AND';
}
if (!empty($tax_query)) {
$args['tax_query'] = $tax_query;
}
}
// Construir tax_query
$args['tax_query'] = self::build_tax_query($type, $iniciativa, $linea);
$query = new WP_Query($args);
@@ -950,7 +853,6 @@ class Bloques_Shortcodes {
$widget = sanitize_text_field($_POST['widget'] ?? 'grid');
$iniciativa = sanitize_text_field($_POST['iniciativa'] ?? '');
$linea = sanitize_text_field($_POST['linea_trabajo'] ?? '');
$category = sanitize_text_field($_POST['category'] ?? '');
$upcoming = sanitize_text_field($_POST['upcoming'] ?? 'true');
$type_config = self::get_type_config($type);
@@ -981,56 +883,8 @@ class Bloques_Shortcodes {
}
}
// Construir tax_query según el tipo
if ($type === 'noticias') {
$base_cat_query = [
'taxonomy' => 'category',
'field' => 'slug',
'terms' => ['bloques-en-transicion', 'bloques'],
'include_children' => true,
];
if (!empty($category)) {
$args['tax_query'] = [
'relation' => 'AND',
$base_cat_query,
[
'taxonomy' => 'category',
'field' => 'slug',
'terms' => $category,
'include_children' => true,
],
];
} else {
$args['tax_query'] = [$base_cat_query];
}
} else {
$tax_query = [];
if (!empty($iniciativa)) {
$tax_query[] = [
'taxonomy' => 'iniciativa',
'field' => 'slug',
'terms' => $iniciativa,
];
}
if (!empty($linea)) {
$tax_query[] = [
'taxonomy' => 'linea_trabajo',
'field' => 'slug',
'terms' => $linea,
];
}
if (count($tax_query) > 1) {
$tax_query['relation'] = 'AND';
}
if (!empty($tax_query)) {
$args['tax_query'] = $tax_query;
}
}
// Construir tax_query
$args['tax_query'] = self::build_tax_query($type, $iniciativa, $linea);
$query = new WP_Query($args);

View File

@@ -61,8 +61,8 @@ class Bloques_Taxonomies {
'query_var' => true,
];
// Asociar a todos los CPTs de Bloques
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques'];
// Asociar a todos los CPTs de Bloques + entradas (noticias)
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques', 'post'];
register_taxonomy('iniciativa', $post_types, $args);
}
@@ -107,7 +107,7 @@ class Bloques_Taxonomies {
'query_var' => true,
];
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques'];
$post_types = ['actuacion', 'recurso_bloques', 'evento_bloques', 'post'];
register_taxonomy('linea_trabajo', $post_types, $args);
}