From a17bd50744b4e10b99e0ac1b9a6eb765636da682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ib=C3=A1=C3=B1ez?= Date: Fri, 13 Feb 2026 13:14:41 +0100 Subject: [PATCH] Geoposicionador de cosas --- fp-geo-content/README.md | 182 +++++ fp-geo-content/assets/css/admin.css | 132 +++ fp-geo-content/assets/css/map.css | 514 ++++++++++++ fp-geo-content/assets/js/admin.js | 88 ++ fp-geo-content/assets/js/map.js | 630 +++++++++++++++ fp-geo-content/fp-geo-content.php | 313 +++++++ .../includes/class-data-provider.php | 280 +++++++ .../includes/class-map-renderer.php | 357 ++++++++ fp-geo-content/includes/class-settings.php | 765 ++++++++++++++++++ fp-geo-content/templates/settings-page.php | 179 ++++ 10 files changed, 3440 insertions(+) create mode 100644 fp-geo-content/README.md create mode 100644 fp-geo-content/assets/css/admin.css create mode 100644 fp-geo-content/assets/css/map.css create mode 100644 fp-geo-content/assets/js/admin.js create mode 100644 fp-geo-content/assets/js/map.js create mode 100644 fp-geo-content/fp-geo-content.php create mode 100644 fp-geo-content/includes/class-data-provider.php create mode 100644 fp-geo-content/includes/class-map-renderer.php create mode 100644 fp-geo-content/includes/class-settings.php create mode 100644 fp-geo-content/templates/settings-page.php diff --git a/fp-geo-content/README.md b/fp-geo-content/README.md new file mode 100644 index 0000000..7821cf2 --- /dev/null +++ b/fp-geo-content/README.md @@ -0,0 +1,182 @@ +# FP Geo Content + +Plugin genérico de WordPress para mostrar contenido geolocalizado en un mapa interactivo usando Leaflet y OpenStreetMap. + +## Descripción + +FP Geo Content permite mostrar cualquier tipo de contenido de WordPress que tenga campos de latitud y longitud en un mapa interactivo. Es completamente configurable y reutilizable en diferentes proyectos. + +## Características + +- 🗺️ **Mapa interactivo** con Leaflet y OpenStreetMap (sin necesidad de API keys) +- 📍 **Marcadores personalizables** con iconos y colores por categoría +- 🔍 **Filtros dinámicos** por taxonomías +- 📱 **Responsive** - funciona en móviles y tablets +- 🎨 **Personalizable** - variables CSS para adaptar al diseño +- 🔌 **Genérico** - funciona con cualquier CPT que tenga coordenadas + +## Instalación + +1. Sube la carpeta `fp-geo-content` a `/wp-content/plugins/` +2. Activa el plugin desde el panel de WordPress +3. Ve a **Ajustes > FP Geo Content** para configurar + +## Configuración + +### 1. Tipos de Contenido + +Selecciona qué tipos de contenido (posts, páginas, CPTs) quieres mostrar en el mapa. + +### 2. Campos de Geolocalización + +Indica el nombre de los campos ACF o meta que contienen la latitud y longitud: +- Campo de latitud (por defecto: `latitud`) +- Campo de longitud (por defecto: `longitud`) + +### 3. Configuración del Mapa + +- **Centro por defecto**: Coordenadas iniciales del mapa +- **Niveles de zoom**: Mínimo, máximo y por defecto +- **Proveedor de tiles**: OpenStreetMap, CartoDB, Stamen, etc. +- **Clusters**: Agrupar marcadores cercanos + +### 4. Filtros + +- Selecciona qué taxonomías pueden usarse como filtros +- Configura si los filtros se combinan con OR o AND + +### 5. Visualización + +- **Sidebar**: Panel lateral que se desliza sobre el mapa +- **Modal**: Ventana emergente centrada + +## Uso + +### Shortcode básico + +``` +[fp-geo-map] +``` + +### Con atributos personalizados + +``` +[fp-geo-map post_types="actuacion,entidad" height="700px" filters="true"] +``` + +### Todos los atributos + +| Atributo | Descripción | Ejemplo | +|----------|-------------|---------| +| `post_types` | Tipos de contenido (separados por coma) | `post_types="actuacion,entidad"` | +| `taxonomies` | Taxonomías para filtros | `taxonomies="iniciativa,linea_trabajo"` | +| `height` | Altura del mapa | `height="500px"` | +| `lat` / `lng` | Centro inicial | `lat="40.4168" lng="-3.7038"` | +| `zoom` | Nivel de zoom (1-18) | `zoom="14"` | +| `filters` | Mostrar filtros | `filters="true"` | +| `detail` | Modo de detalle | `detail="sidebar"` o `detail="modal"` | +| `cluster` | Agrupar marcadores | `cluster="true"` | +| `class` | Clases CSS adicionales | `class="mi-mapa"` | + +## Personalización de estilos + +### Variables CSS + +```css +:root { + --fp-geo-primary: #1E6B52; + --fp-geo-secondary: #2A9D8F; + --fp-geo-accent: #F4A261; + --fp-geo-text: #333; + --fp-geo-bg: #fff; + --fp-geo-radius: 8px; +} +``` + +### Selectores CSS principales + +| Selector | Descripción | +|----------|-------------| +| `.fp-geo-wrapper` | Contenedor principal | +| `.fp-geo-map` | Elemento del mapa | +| `.fp-geo-filters` | Barra de filtros | +| `.fp-geo-filter-btn` | Botones de filtro | +| `.fp-geo-detail` | Panel de detalle | +| `.fp-geo-sidebar` | Panel lateral | +| `.fp-geo-modal` | Ventana modal | + +### Ejemplo de personalización + +```css +/* Cambiar colores del tema */ +.fp-geo-filters { + background: #your-color; +} + +.fp-geo-filter-btn.active { + background: #your-accent; +} + +/* Personalizar panel de detalle */ +.fp-geo-detail-header { + background: linear-gradient(135deg, #color1, #color2); +} +``` + +## Hooks y Filtros + +### Filtrar datos del marcador + +```php +add_filter('fp_geo_marker_data', function($marker, $post) { + // Añadir campos personalizados + $marker['custom_field'] = get_field('mi_campo', $post->ID); + return $marker; +}, 10, 2); +``` + +## API JavaScript + +```javascript +// Obtener instancia del mapa +const mapData = FPGeoContent.getMap('fp-geo-map-1'); + +// Acceder al mapa Leaflet +const leafletMap = mapData.map; + +// Refrescar marcadores +FPGeoContent.refreshMarkers('fp-geo-map-1', newMarkers); + +// Aplicar filtros programáticamente +FPGeoContent.applyFilters('fp-geo-map-1'); +``` + +## Proveedores de Tiles disponibles + +- **OpenStreetMap** (estándar) +- **CartoDB Positron** (claro) +- **CartoDB Dark Matter** (oscuro) +- **CartoDB Voyager** (colores) +- **Stamen Toner** (blanco y negro) +- **Stamen Watercolor** (acuarela) + +## Requisitos + +- WordPress 6.0+ +- PHP 8.0+ +- ACF (recomendado para campos de geolocalización) + +## Compatibilidad + +- ✅ Divi Theme Builder +- ✅ Elementor +- ✅ Gutenberg +- ✅ Classic Editor + +## Soporte + +Plugin desarrollado por [Freepress Coop](https://freepress.coop) + +## Licencia + +GPL-2.0+ diff --git a/fp-geo-content/assets/css/admin.css b/fp-geo-content/assets/css/admin.css new file mode 100644 index 0000000..70e455d --- /dev/null +++ b/fp-geo-content/assets/css/admin.css @@ -0,0 +1,132 @@ +/** + * Estilos del admin - FP Geo Content + */ + +.fp-geo-settings { + max-width: 1000px; +} + +.fp-geo-settings h1 { + display: flex; + align-items: center; + gap: 10px; + color: #1E6B52; +} + +.fp-geo-settings h1 .dashicons { + font-size: 28px; + width: 28px; + height: 28px; +} + +.fp-geo-settings-intro { + background: linear-gradient(135deg, #1E6B52 0%, #2A9D8F 100%); + color: white; + padding: 20px; + border-radius: 8px; + margin: 20px 0; +} + +.fp-geo-settings-intro p { + margin: 0; + font-size: 15px; +} + +.fp-geo-settings h2 { + color: #1E6B52; + border-bottom: 2px solid #1E6B52; + padding-bottom: 10px; + margin-top: 30px; +} + +.fp-geo-settings h3 { + color: #333; + margin-top: 25px; +} + +.fp-geo-settings-help { + background: white; + padding: 20px 30px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + margin-top: 30px; +} + +.fp-geo-shortcode { + display: block; + background: #f0f0f0; + padding: 15px 20px; + border-radius: 6px; + font-size: 16px; + margin: 15px 0; +} + +.fp-geo-settings pre { + background: #f8f9fa; + padding: 15px; + border-radius: 6px; + overflow-x: auto; +} + +.fp-geo-settings pre code { + background: none; + padding: 0; +} + +.fp-geo-settings table code { + background: #f0f0f0; + padding: 2px 6px; + border-radius: 3px; + font-size: 12px; +} + +/* Media uploader */ +.fp-geo-media-upload { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.fp-geo-image-preview { + border: 2px dashed #ccc; + border-radius: 6px; + padding: 10px; + background: #f9f9f9; +} + +.fp-geo-image-preview img { + display: block; + border-radius: 4px; +} + +.fp-geo-upload-btn, +.fp-geo-remove-btn { + margin-top: 0 !important; +} + +.fp-geo-remove-btn { + color: #dc3232; +} + +/* Color picker */ +.fp-geo-color-picker { + width: 60px; + height: 40px; + padding: 2px; + cursor: pointer; + border: 1px solid #ddd; + border-radius: 4px; +} + +/* Category color options */ +.fp-geo-category-color-options { + background: #f9f9f9; + padding: 15px; + border-radius: 6px; + border: 1px solid #e0e0e0; +} + +.fp-geo-category-color-options select { + margin-left: 5px; +} diff --git a/fp-geo-content/assets/css/map.css b/fp-geo-content/assets/css/map.css new file mode 100644 index 0000000..d9fe78a --- /dev/null +++ b/fp-geo-content/assets/css/map.css @@ -0,0 +1,514 @@ +/** + * Estilos base del mapa - FP Geo Content + * + * Estilos mínimos para layout y funcionalidad. + * Los estilos visuales se pueden personalizar desde: + * - Ajustes > FP Geo Content > CSS Personalizado + * - O desde el tema activo + */ + +/* === LAYOUT BASE === */ +.fp-geo-wrapper { + position: relative; + width: 100%; + font-family: inherit; +} + +.fp-geo-map-container { + position: relative; + display: flex; + width: 100%; +} + +.fp-geo-map { + flex: 1; + min-height: 400px; + z-index: 1; +} + +/* === FILTROS - LAYOUT === */ +.fp-geo-filters { + padding: 1rem; +} + +.fp-geo-filters-inner { + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: flex-start; +} + +.fp-geo-filter-group { + flex: 1; + min-width: 200px; +} + +.fp-geo-filter-label { + display: block; + font-size: 0.875rem; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.fp-geo-filter-buttons { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.fp-geo-filter-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border: 1px solid currentColor; + border-radius: 4px; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s ease; + background: transparent; +} + +.fp-geo-filter-icon { + width: 18px; + height: 18px; +} + +.fp-geo-clear-filters { + padding: 8px 16px; + border-radius: 4px; + font-size: 0.8rem; + cursor: pointer; + background: transparent; + font-weight: bold; +} + +/* Footer de filtros con contador */ +.fp-geo-filters-footer { + display: flex; + align-items: center; + gap: 16px; + margin-top: 8px; + flex-wrap: wrap; +} + +.fp-geo-results-count { + font-size: 0.875rem; +} + +.fp-geo-results-number { + font-weight: 600; +} + +/* === LEYENDA - LAYOUT === */ +.fp-geo-legend { + position: absolute; + bottom: 20px; + right: 20px; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + padding: 14px 18px; + z-index: 800; + min-width: 180px; +} + +.fp-geo-sidebar-left .fp-geo-legend { + right: auto; + left: 20px; +} + +.fp-geo-legend-title { + font-size: 0.85rem; + font-weight: 700; + margin-bottom: 10px; + letter-spacing: 0.01em; +} + +/* Secciones */ +.fp-geo-legend-section { + margin-bottom: 4px; +} + +.fp-geo-legend-section-title { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #6b7280; + margin-bottom: 6px; +} + +.fp-geo-legend-items { + display: flex; + flex-direction: column; + gap: 7px; +} + +.fp-geo-legend-item { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.85rem; + line-height: 1.3; +} + +/* Marcador circular (iniciativas) */ +.fp-geo-legend-marker { + width: 14px; + height: 14px; + border-radius: 50%; + border: 2px solid white; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); + flex-shrink: 0; +} + +/* Sección Bloque Piloto — separada visualmente */ +.fp-geo-legend-pilot-section { + margin-top: 8px; + padding-top: 10px; + border-top: 1px solid #e5e7eb; +} + +.fp-geo-legend-marker-pilot { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 28px; +} + +.fp-geo-legend-marker-pilot svg { + display: block; + filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2)); +} + +.fp-geo-legend-pilot-desc { + font-size: 0.7rem; + color: #9ca3af; + margin: 2px 0 0 0; + line-height: 1.35; +} + +/* === AVISO DE SCROLL === */ +.fp-geo-scroll-hint { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.75); + color: white; + padding: 12px 24px; + border-radius: 25px; + font-size: 0.9rem; + z-index: 900; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + white-space: nowrap; +} + +.fp-geo-scroll-hint.visible { + opacity: 1; +} + +/* === PANEL DE DETALLE - LAYOUT === */ +.fp-geo-detail { + position: absolute; + background: #fff; + z-index: 1000; + overflow: hidden; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +/* Sidebar derecha (por defecto) */ +.fp-geo-sidebar { + top: 0; + right: 0; + width: 350px; + max-width: 40%; + height: 100%; + box-shadow: -4px 0 20px rgba(0,0,0,0.15); + transform: translateX(100%); + opacity: 0; + pointer-events: none; +} + +.fp-geo-sidebar.visible { + transform: translateX(0); + opacity: 1; + pointer-events: auto; +} + +/* Sidebar izquierda */ +.fp-geo-sidebar-left .fp-geo-sidebar { + right: auto; + left: 0; + transform: translateX(-100%); + box-shadow: 4px 0 20px rgba(0,0,0,0.15); +} + +.fp-geo-sidebar-left .fp-geo-sidebar.visible { + transform: translateX(0); +} + +/* Modal */ +.fp-geo-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.9); + width: 90%; + max-width: 500px; + max-height: 80vh; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.2); + opacity: 0; + pointer-events: none; + z-index: 10001; +} + +.fp-geo-modal.visible { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + pointer-events: auto; +} + +.fp-geo-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 10000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.fp-geo-overlay.visible { + opacity: 1; + pointer-events: auto; +} + +/* === CONTENIDO DEL DETALLE === */ +.fp-geo-detail-content { + height: 100%; + overflow-y: auto; +} + +.fp-geo-detail-close { + position: absolute; + top: 10px; + right: 10px; + width: 32px; + height: 32px; + background: rgba(255,255,255,0.9); + border: none; + border-radius: 50%; + font-size: 1.5rem; + line-height: 1; + cursor: pointer; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; +} + +.fp-geo-detail-header { + padding: 1.5rem; +} + +.fp-geo-detail-taxonomies { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.fp-geo-detail-tax-tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 20px; + font-size: 0.75rem; +} + +.fp-geo-detail-tax-icon { + width: 14px; + height: 14px; +} + +.fp-geo-detail-thumbnail { + width: 100%; + aspect-ratio: 16/9; + overflow: hidden; + display: none; +} + +.fp-geo-detail-thumbnail.has-image { + display: block; +} + +.fp-geo-detail-thumbnail img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.fp-geo-detail-title { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + padding: 1.25rem 1.25rem 0.5rem; + line-height: 1.3; +} + +.fp-geo-detail-excerpt { + font-size: 0.9rem; + line-height: 1.5; + padding: 0 1.25rem; + margin-bottom: 1rem; +} + +.fp-geo-detail-meta { + padding: 0 1.25rem; + margin-bottom: 1rem; +} + +.fp-geo-detail-location, +.fp-geo-detail-contact { + font-size: 0.85rem; +} + +.fp-geo-detail-location { + display: flex; + align-items: flex-start; + gap: 8px; + margin-bottom: 0.75rem; +} + +.fp-geo-detail-contact-item { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 0.5rem; +} + +.fp-geo-detail-contact-item a { + text-decoration: none; +} + +.fp-geo-detail-footer { + padding: 1.25rem; + border-top: 1px solid #eee; +} + +.fp-geo-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 10px 24px; + border: none; + border-radius: 4px; + font-size: 0.9rem; + font-weight: 500; + text-decoration: none; + cursor: pointer; +} + +/* === MARCADORES === */ +.fp-geo-circle-marker { + background: transparent !important; + border: none !important; +} + +.fp-geo-circle-marker svg { + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3)); +} + +/* === CLUSTERS === */ +.marker-cluster { + background-clip: padding-box; + border-radius: 20px; +} + +.marker-cluster div { + width: 30px; + height: 30px; + margin-left: 5px; + margin-top: 5px; + text-align: center; + border-radius: 15px; + font: 12px sans-serif; + font-weight: bold; + line-height: 30px; +} + +/* === ERROR === */ +.fp-geo-error { + background: #fee; + color: #c00; + padding: 15px 20px; + border-radius: 8px; + border-left: 4px solid #c00; +} + +/* === RESPONSIVE === */ +@media (max-width: 768px) { + .fp-geo-filters-inner { + flex-direction: column; + align-items: stretch; + } + + .fp-geo-filter-group { + min-width: 100%; + } + + .fp-geo-sidebar { + width: 100%; + max-width: 100%; + height: 60%; + top: auto; + bottom: 0; + right: 0; + left: 0; + transform: translateY(100%); + border-radius: 12px 12px 0 0; + box-shadow: 0 -4px 20px rgba(0,0,0,0.15); + } + + .fp-geo-sidebar.visible { + transform: translateY(0); + } + + .fp-geo-sidebar-left .fp-geo-sidebar { + right: 0; + left: 0; + transform: translateY(100%); + } + + .fp-geo-sidebar-left .fp-geo-sidebar.visible { + transform: translateY(0); + } + + .fp-geo-modal { + width: 95%; + max-height: 90vh; + } + + .fp-geo-legend { + bottom: 10px; + right: 10px; + left: auto; + padding: 10px 12px; + } + + .fp-geo-sidebar-left .fp-geo-legend { + right: 10px; + left: auto; + } +} diff --git a/fp-geo-content/assets/js/admin.js b/fp-geo-content/assets/js/admin.js new file mode 100644 index 0000000..174101a --- /dev/null +++ b/fp-geo-content/assets/js/admin.js @@ -0,0 +1,88 @@ +/** + * Scripts del admin - FP Geo Content + */ + +(function($) { + 'use strict'; + + $(document).ready(function() { + // Copiar shortcode al portapapeles + $('.fp-geo-shortcode').on('click', function() { + const text = $(this).text(); + navigator.clipboard.writeText(text).then(function() { + alert('Shortcode copiado al portapapeles'); + }); + }).css('cursor', 'pointer').attr('title', 'Clic para copiar'); + + // Media uploader para icono personalizado + let mediaUploader; + + $('.fp-geo-upload-btn').on('click', function(e) { + e.preventDefault(); + + const targetId = $(this).data('target'); + const $input = $('#' + targetId); + const $preview = $('#' + targetId + '_preview'); + const $removeBtn = $('.fp-geo-remove-btn[data-target="' + targetId + '"]'); + + // Si el uploader ya existe, abrirlo + if (mediaUploader) { + mediaUploader.open(); + return; + } + + // Crear el media uploader + mediaUploader = wp.media({ + title: 'Seleccionar icono de marcador', + button: { + text: 'Usar este icono' + }, + multiple: false, + library: { + type: 'image' + } + }); + + // Cuando se selecciona una imagen + mediaUploader.on('select', function() { + const attachment = mediaUploader.state().get('selection').first().toJSON(); + + // Actualizar input + $input.val(attachment.id); + + // Mostrar preview + const imgUrl = attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url; + $preview.html('').show(); + + // Mostrar botón eliminar + $removeBtn.show(); + }); + + mediaUploader.open(); + }); + + // Eliminar imagen + $('.fp-geo-remove-btn').on('click', function(e) { + e.preventDefault(); + + const targetId = $(this).data('target'); + const $input = $('#' + targetId); + const $preview = $('#' + targetId + '_preview'); + + $input.val(''); + $preview.html('').hide(); + $(this).hide(); + }); + + // Toggle opciones de color por categoría + $('input[name="fp_geo_content_options[use_category_colors]"]').on('change', function() { + const $options = $('.fp-geo-category-color-options'); + if ($(this).is(':checked')) { + $options.slideDown(); + } else { + $options.slideUp(); + } + }).trigger('change'); + }); + +})(jQuery); diff --git a/fp-geo-content/assets/js/map.js b/fp-geo-content/assets/js/map.js new file mode 100644 index 0000000..840e122 --- /dev/null +++ b/fp-geo-content/assets/js/map.js @@ -0,0 +1,630 @@ +/** + * Script del mapa - FP Geo Content + */ + +(function() { + 'use strict'; + + // Almacenar instancias de mapas + const mapInstances = {}; + + /** + * Inicializar cuando el DOM esté listo + */ + document.addEventListener('DOMContentLoaded', function() { + // Buscar todos los wrappers de mapas + document.querySelectorAll('.fp-geo-wrapper').forEach(function(wrapper) { + const instance = wrapper.dataset.instance; + const configName = 'fpGeoConfig_' + instance; + + if (window[configName]) { + initMap(window[configName]); + } + }); + }); + + /** + * Inicializar un mapa + */ + function initMap(config) { + const mapEl = document.getElementById(config.mapId); + if (!mapEl) return; + + // Configurar opciones de scroll wheel zoom + let scrollWheelZoomOption = false; + if (config.scrollWheelZoom === 'always') { + scrollWheelZoomOption = true; + } else if (config.scrollWheelZoom === 'ctrl') { + scrollWheelZoomOption = 'ctrl'; + } + + // Crear mapa Leaflet + const map = L.map(mapEl, { + center: config.center, + zoom: config.zoom, + minZoom: config.minZoom, + maxZoom: config.maxZoom, + scrollWheelZoom: scrollWheelZoomOption === 'ctrl' ? false : scrollWheelZoomOption, + }); + + // Añadir capa de tiles + L.tileLayer(config.tileUrl, { + attribution: config.tileAttribution, + subdomains: config.tileSubdomains || 'abc', + maxZoom: 19, + }).addTo(map); + + // Crear grupo de marcadores (con o sin cluster) + let markersLayer; + if (config.clusterEnabled) { + markersLayer = L.markerClusterGroup({ + showCoverageOnHover: false, + maxClusterRadius: 50, + }); + } else { + markersLayer = L.layerGroup(); + } + + // Almacenar datos + const mapData = { + map: map, + markersLayer: markersLayer, + allMarkers: [], + config: config, + activeFilters: {}, + }; + + mapInstances[config.mapId] = mapData; + + // Configurar control de scroll con Ctrl + if (config.scrollWheelZoom === 'ctrl') { + initCtrlScrollZoom(mapData); + } + + // Añadir marcadores iniciales + addMarkers(mapData, config.markers); + + // Añadir capa al mapa + map.addLayer(markersLayer); + + // Ajustar zoom para mostrar todos los marcadores + fitMapToMarkers(mapData); + + // Inicializar filtros + initFilters(mapData); + + // Inicializar panel de detalle + initDetailPanel(mapData); + } + + /** + * Inicializar control de zoom con Ctrl+scroll + */ + function initCtrlScrollZoom(mapData) { + const { map, config } = mapData; + const mapEl = document.getElementById(config.mapId); + let hintTimeout; + + // Crear elemento de aviso + const scrollHint = document.createElement('div'); + scrollHint.className = 'fp-geo-scroll-hint'; + scrollHint.textContent = config.i18n.scrollZoomHint || 'Usa Ctrl + scroll para hacer zoom'; + mapEl.parentElement.appendChild(scrollHint); + + // Detectar scroll sin Ctrl + mapEl.addEventListener('wheel', function(e) { + if (e.ctrlKey) { + // Habilitar zoom cuando Ctrl está presionado + map.scrollWheelZoom.enable(); + scrollHint.classList.remove('visible'); + } else { + // Mostrar aviso + map.scrollWheelZoom.disable(); + scrollHint.classList.add('visible'); + + clearTimeout(hintTimeout); + hintTimeout = setTimeout(function() { + scrollHint.classList.remove('visible'); + }, 1500); + } + }); + + // Deshabilitar zoom al soltar Ctrl + document.addEventListener('keyup', function(e) { + if (e.key === 'Control') { + map.scrollWheelZoom.disable(); + } + }); + } + + /** + * Ajustar el zoom del mapa para mostrar todos los marcadores + */ + function fitMapToMarkers(mapData) { + const { map, markersLayer, config } = mapData; + + // Obtener los límites del grupo de marcadores + const layers = markersLayer.getLayers(); + + if (layers.length === 0) { + // Sin marcadores, usar centro y zoom por defecto + return; + } + + if (layers.length === 1) { + // Un solo marcador: centrar en él con zoom razonable + const marker = layers[0]; + map.setView(marker.getLatLng(), Math.min(config.zoom || 14, config.maxZoom || 18)); + return; + } + + // Varios marcadores: ajustar para mostrar todos + const bounds = markersLayer.getBounds(); + + if (bounds.isValid()) { + map.fitBounds(bounds, { + padding: [50, 50], // Margen de 50px + maxZoom: config.maxZoom || 18, + }); + } + } + + /** + * Crear icono de marcador circular SVG + */ + function createCircleMarkerIcon(color, isPilot = false) { + const size = 36; + const strokeWidth = 3; + + // Crear SVG simple y limpio + let svg; + + if (isPilot) { + // Marcador con estrella para pilotos + svg = '' + + '' + + '' + + '' + + '' + + ''; + + return L.divIcon({ + className: 'fp-geo-circle-marker fp-geo-pilot-marker', + html: svg, + iconSize: [size, size + 12], + iconAnchor: [size/2, size + 12], + popupAnchor: [0, -(size + 12)], + }); + } else { + // Marcador circular normal con punta + svg = '' + + '' + + '' + + '' + + ''; + + return L.divIcon({ + className: 'fp-geo-circle-marker', + html: svg, + iconSize: [size, size + 12], + iconAnchor: [size/2, size + 10], + popupAnchor: [0, -(size + 10)], + }); + } + } + + /** + * Añadir marcadores al mapa + */ + function addMarkers(mapData, markers) { + const { markersLayer, config } = mapData; + + // Limpiar marcadores existentes + markersLayer.clearLayers(); + mapData.allMarkers = []; + + // Color por defecto + const defaultColor = config.markerDefaultColor || '#F97316'; + + markers.forEach(function(markerData) { + let icon; + let markerColor = defaultColor; + + // es_piloto viene directamente del post (actuación), no de los términos + let isPilot = markerData.es_piloto === true; + + // Buscar color de la taxonomía configurada para colores + if (config.useCategoryColors && markerData.taxonomies) { + // Primero intentar con la taxonomía de leyenda configurada + if (config.legendTaxonomy) { + const categoryTerms = markerData.taxonomies[config.legendTaxonomy]; + if (categoryTerms && categoryTerms.length > 0) { + if (categoryTerms[0].color) { + markerColor = categoryTerms[0].color; + } + } + } + + // Si no hay color, buscar en cualquier taxonomía + if (markerColor === defaultColor) { + for (const taxonomy in markerData.taxonomies) { + const terms = markerData.taxonomies[taxonomy]; + if (terms && terms.length > 0 && terms[0].color) { + markerColor = terms[0].color; + break; + } + } + } + } + + // Usar icono personalizado si existe, o círculo de color + if (config.markerIcon && config.markerIcon !== '') { + icon = L.icon({ + iconUrl: config.markerIcon, + iconSize: [40, 50], + iconAnchor: [20, 50], + popupAnchor: [0, -50], + }); + } else { + // Crear marcador circular con el color + icon = createCircleMarkerIcon(markerColor, isPilot); + } + + // Crear marcador + const marker = L.marker([markerData.lat, markerData.lng], { icon: icon }); + + // Guardar datos en el marcador + marker.markerData = markerData; + marker.markerColor = markerColor; + + // Evento de clic + marker.on('click', function() { + showDetail(mapData, markerData); + }); + + // Añadir al grupo + markersLayer.addLayer(marker); + mapData.allMarkers.push(marker); + }); + + // Actualizar contador + updateResultsCount(mapData, markers.length); + + // Debug: mostrar en consola cuántos marcadores se añadieron + console.log('[FP Geo] Añadidos ' + markers.length + ' marcadores con color por defecto: ' + defaultColor); + } + + /** + * Actualizar contador de resultados + */ + function updateResultsCount(mapData, count) { + const { config } = mapData; + const wrapper = document.getElementById(config.mapId + '-wrapper'); + if (!wrapper) return; + + const countEl = wrapper.querySelector('.fp-geo-results-number'); + const labelEl = wrapper.querySelector('.fp-geo-results-label'); + + if (countEl) { + countEl.textContent = count; + } + + if (labelEl) { + // Si solo hay un tipo de contenido, mostrar su nombre + if (config.postTypes && config.postTypes.length === 1) { + const postType = config.postTypes[0]; + // Usar el nombre del post type en plural/singular + const labels = config.postTypeLabels || {}; + if (count === 1) { + labelEl.textContent = labels[postType]?.singular || postType; + } else { + labelEl.textContent = labels[postType]?.plural || postType + 's'; + } + } else { + labelEl.textContent = count === 1 ? 'resultado' : 'resultados'; + } + } + } + + /** + * Inicializar filtros + */ + function initFilters(mapData) { + const { config } = mapData; + const wrapper = document.getElementById(config.mapId + '-wrapper'); + if (!wrapper) return; + + const filtersEl = wrapper.querySelector('.fp-geo-filters'); + if (!filtersEl) return; + + // Botones de filtro + filtersEl.querySelectorAll('.fp-geo-filter-btn').forEach(function(btn) { + btn.addEventListener('click', function() { + const taxonomy = btn.dataset.taxonomy; + const slug = btn.dataset.slug; + + // Toggle activo + btn.classList.toggle('active'); + + // Actualizar filtros activos + if (!mapData.activeFilters[taxonomy]) { + mapData.activeFilters[taxonomy] = []; + } + + const index = mapData.activeFilters[taxonomy].indexOf(slug); + if (index > -1) { + mapData.activeFilters[taxonomy].splice(index, 1); + } else { + mapData.activeFilters[taxonomy].push(slug); + } + + // Aplicar filtros + applyFilters(mapData); + }); + }); + + // Botón limpiar + const clearBtn = filtersEl.querySelector('.fp-geo-clear-filters'); + if (clearBtn) { + clearBtn.addEventListener('click', function() { + // Limpiar todos los filtros + mapData.activeFilters = {}; + filtersEl.querySelectorAll('.fp-geo-filter-btn.active').forEach(function(btn) { + btn.classList.remove('active'); + }); + applyFilters(mapData); + }); + } + } + + /** + * Aplicar filtros a los marcadores + */ + function applyFilters(mapData) { + const { markersLayer, allMarkers, activeFilters, config } = mapData; + const filterCombine = config.filterCombine || 'OR'; + + // Verificar si hay filtros activos + const hasActiveFilters = Object.values(activeFilters).some(arr => arr.length > 0); + let visibleCount = 0; + + allMarkers.forEach(function(marker) { + const data = marker.markerData; + let shouldShow = true; + + if (hasActiveFilters) { + if (filterCombine === 'AND') { + // Debe cumplir TODOS los filtros + shouldShow = Object.entries(activeFilters).every(function([taxonomy, slugs]) { + if (slugs.length === 0) return true; + + const markerTerms = data.taxonomies && data.taxonomies[taxonomy]; + if (!markerTerms) return false; + + return slugs.some(function(slug) { + return markerTerms.some(function(term) { + return term.slug === slug; + }); + }); + }); + } else { + // Debe cumplir AL MENOS UN filtro (OR) + shouldShow = Object.entries(activeFilters).some(function([taxonomy, slugs]) { + if (slugs.length === 0) return false; + + const markerTerms = data.taxonomies && data.taxonomies[taxonomy]; + if (!markerTerms) return false; + + return slugs.some(function(slug) { + return markerTerms.some(function(term) { + return term.slug === slug; + }); + }); + }); + + // Si no hay filtros activos en ninguna taxonomía, mostrar todo + if (!Object.values(activeFilters).some(arr => arr.length > 0)) { + shouldShow = true; + } + } + } + + if (shouldShow) { + markersLayer.addLayer(marker); + visibleCount++; + } else { + markersLayer.removeLayer(marker); + } + }); + + // Actualizar contador de resultados + updateResultsCount(mapData, visibleCount); + } + + /** + * Inicializar panel de detalle + */ + function initDetailPanel(mapData) { + const { config } = mapData; + const wrapper = document.getElementById(config.mapId + '-wrapper'); + if (!wrapper) return; + + const detailEl = document.getElementById(config.mapId + '-detail'); + const overlayEl = document.getElementById(config.mapId + '-overlay'); + + if (!detailEl) return; + + // Botón cerrar + const closeBtn = detailEl.querySelector('.fp-geo-detail-close'); + if (closeBtn) { + closeBtn.addEventListener('click', function() { + hideDetail(mapData); + }); + } + + // Cerrar al hacer clic en overlay (modal) + if (overlayEl) { + overlayEl.addEventListener('click', function() { + hideDetail(mapData); + }); + } + + // Cerrar con Escape + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + hideDetail(mapData); + } + }); + } + + /** + * Mostrar detalle de un marcador + */ + function showDetail(mapData, data) { + const { config } = mapData; + const detailEl = document.getElementById(config.mapId + '-detail'); + const overlayEl = document.getElementById(config.mapId + '-overlay'); + + if (!detailEl) return; + + // Rellenar taxonomías + const taxEl = detailEl.querySelector('.fp-geo-detail-taxonomies'); + if (taxEl) { + taxEl.innerHTML = ''; + if (data.taxonomies) { + for (const taxonomy in data.taxonomies) { + data.taxonomies[taxonomy].forEach(function(term) { + const tag = document.createElement('span'); + tag.className = 'fp-geo-detail-tax-tag'; + tag.style.cssText = term.color ? 'background-color: ' + term.color + ';' : ''; + + let html = ''; + if (term.icono) { + html += ''; + } + html += '' + term.name + ''; + tag.innerHTML = html; + taxEl.appendChild(tag); + }); + } + } + } + + // Thumbnail + const thumbEl = detailEl.querySelector('.fp-geo-detail-thumbnail'); + if (thumbEl) { + if (data.thumbnail) { + thumbEl.innerHTML = '' + data.title + ''; + thumbEl.classList.add('has-image'); + } else { + thumbEl.innerHTML = ''; + thumbEl.classList.remove('has-image'); + } + } + + // Título + const titleEl = detailEl.querySelector('.fp-geo-detail-title'); + if (titleEl) { + titleEl.textContent = data.title; + } + + // Excerpt + const excerptEl = detailEl.querySelector('.fp-geo-detail-excerpt'); + if (excerptEl) { + excerptEl.textContent = data.excerpt || ''; + } + + // Ubicación + const locationEl = detailEl.querySelector('.fp-geo-detail-location'); + if (locationEl) { + const locationParts = [data.direccion, data.localidad].filter(Boolean); + if (locationParts.length > 0) { + locationEl.innerHTML = '' + locationParts.join(', ') + ''; + locationEl.style.display = 'flex'; + } else { + locationEl.style.display = 'none'; + } + } + + // Contacto + const contactEl = detailEl.querySelector('.fp-geo-detail-contact'); + if (contactEl) { + let contactHtml = ''; + + if (data.telefono) { + contactHtml += '
📞 ' + data.telefono + '
'; + } + if (data.email) { + contactHtml += '
✉️ ' + data.email + '
'; + } + if (data.web) { + contactHtml += '
🌐 Sitio web
'; + } + + contactEl.innerHTML = contactHtml; + } + + // Link - actualizar texto si está configurado + const linkEl = detailEl.querySelector('.fp-geo-detail-link'); + if (linkEl) { + linkEl.href = data.url; + if (config.detailButtonText) { + linkEl.textContent = config.detailButtonText; + } + } + + // Footer - mostrar/ocultar según configuración + const footerEl = detailEl.querySelector('.fp-geo-detail-footer'); + if (footerEl) { + footerEl.style.display = config.showDetailButton ? 'block' : 'none'; + } + + // Mostrar panel + detailEl.classList.add('visible'); + if (overlayEl) { + overlayEl.classList.add('visible'); + } + } + + /** + * Ocultar panel de detalle + */ + function hideDetail(mapData) { + const { config } = mapData; + const detailEl = document.getElementById(config.mapId + '-detail'); + const overlayEl = document.getElementById(config.mapId + '-overlay'); + + if (detailEl) { + detailEl.classList.remove('visible'); + } + if (overlayEl) { + overlayEl.classList.remove('visible'); + } + } + + // Exponer API pública + window.FPGeoContent = { + getMap: function(mapId) { + return mapInstances[mapId]; + }, + refreshMarkers: function(mapId, markers) { + const mapData = mapInstances[mapId]; + if (mapData) { + addMarkers(mapData, markers); + } + }, + applyFilters: function(mapId) { + const mapData = mapInstances[mapId]; + if (mapData) { + applyFilters(mapData); + } + }, + fitToMarkers: function(mapId) { + const mapData = mapInstances[mapId]; + if (mapData) { + fitMapToMarkers(mapData); + } + }, + }; + +})(); diff --git a/fp-geo-content/fp-geo-content.php b/fp-geo-content/fp-geo-content.php new file mode 100644 index 0000000..2a818f3 --- /dev/null +++ b/fp-geo-content/fp-geo-content.php @@ -0,0 +1,313 @@ +load_dependencies(); + $this->init_hooks(); + $this->options = get_option('fp_geo_content_options', []); + } + + /** + * Cargar dependencias + */ + private function load_dependencies() { + require_once FP_GEO_PLUGIN_DIR . 'includes/class-settings.php'; + require_once FP_GEO_PLUGIN_DIR . 'includes/class-map-renderer.php'; + require_once FP_GEO_PLUGIN_DIR . 'includes/class-data-provider.php'; + } + + /** + * Inicializar hooks + */ + private function init_hooks() { + // Activación + register_activation_hook(__FILE__, [$this, 'activate']); + register_deactivation_hook(__FILE__, [$this, 'deactivate']); + + // Inicialización + add_action('init', [$this, 'init']); + add_action('plugins_loaded', [$this, 'load_textdomain']); + + // Admin + if (is_admin()) { + add_action('admin_menu', [$this, 'add_admin_menu']); + add_action('admin_init', [$this, 'register_settings']); + add_action('admin_enqueue_scripts', [$this, 'admin_assets']); + } + + // Frontend + add_action('wp_enqueue_scripts', [$this, 'register_frontend_assets']); + add_action('wp_head', [$this, 'output_custom_css'], 100); + + // Shortcode + add_shortcode('fp-geo-map', [FP_Geo_Map_Renderer::class, 'render_shortcode']); + + // AJAX + add_action('wp_ajax_fp_geo_get_markers', [FP_Geo_Data_Provider::class, 'ajax_get_markers']); + add_action('wp_ajax_nopriv_fp_geo_get_markers', [FP_Geo_Data_Provider::class, 'ajax_get_markers']); + } + + /** + * Inicialización + */ + public function init() { + // Registrar settings + FP_Geo_Settings::register(); + } + + /** + * Cargar traducciones + */ + public function load_textdomain() { + load_plugin_textdomain( + 'fp-geo-content', + false, + dirname(FP_GEO_PLUGIN_BASENAME) . '/languages/' + ); + } + + /** + * Añadir menú de administración + */ + public function add_admin_menu() { + add_options_page( + __('FP Geo Content', 'fp-geo-content'), + __('FP Geo Content', 'fp-geo-content'), + 'manage_options', + 'fp-geo-content', + [$this, 'render_settings_page'] + ); + } + + /** + * Registrar ajustes + */ + public function register_settings() { + FP_Geo_Settings::register_settings(); + } + + /** + * Renderizar página de ajustes + */ + public function render_settings_page() { + include FP_GEO_PLUGIN_DIR . 'templates/settings-page.php'; + } + + /** + * Assets del admin + */ + public function admin_assets($hook) { + if ($hook !== 'settings_page_fp-geo-content') { + return; + } + + // Cargar media uploader + wp_enqueue_media(); + + wp_enqueue_style( + 'fp-geo-admin', + FP_GEO_PLUGIN_URL . 'assets/css/admin.css', + [], + FP_GEO_VERSION + ); + + wp_enqueue_script( + 'fp-geo-admin', + FP_GEO_PLUGIN_URL . 'assets/js/admin.js', + ['jquery', 'wp-media-utils'], + FP_GEO_VERSION, + true + ); + } + + /** + * Registrar assets del frontend (no cargar por defecto) + */ + public function register_frontend_assets() { + // Leaflet CSS + wp_register_style( + 'leaflet', + 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', + [], + '1.9.4' + ); + + // Leaflet MarkerCluster CSS + wp_register_style( + 'leaflet-markercluster', + 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css', + ['leaflet'], + '1.5.3' + ); + + wp_register_style( + 'leaflet-markercluster-default', + 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css', + ['leaflet-markercluster'], + '1.5.3' + ); + + // Plugin CSS + wp_register_style( + 'fp-geo-content', + FP_GEO_PLUGIN_URL . 'assets/css/map.css', + ['leaflet', 'leaflet-markercluster-default'], + FP_GEO_VERSION + ); + + // Leaflet JS + wp_register_script( + 'leaflet', + 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', + [], + '1.9.4', + true + ); + + // Leaflet MarkerCluster JS + wp_register_script( + 'leaflet-markercluster', + 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js', + ['leaflet'], + '1.5.3', + true + ); + + // Plugin JS + wp_register_script( + 'fp-geo-content', + FP_GEO_PLUGIN_URL . 'assets/js/map.js', + ['leaflet', 'leaflet-markercluster'], + FP_GEO_VERSION, + true + ); + } + + /** + * Output custom CSS in head + */ + public function output_custom_css() { + $options = get_option('fp_geo_content_options', []); + $custom_css = $options['custom_css'] ?? ''; + + if (!empty(trim($custom_css))) { + echo "\n\n"; + } + } + + /** + * Activación + */ + public function activate() { + // Opciones por defecto + $defaults = [ + 'post_types' => [], + 'lat_field' => 'latitud', + 'lng_field' => 'longitud', + 'default_lat' => '40.4168', + 'default_lng' => '-3.7038', + 'default_zoom' => 12, + 'min_zoom' => 5, + 'max_zoom' => 18, + 'cluster_enabled' => true, + 'detail_display' => 'sidebar', // sidebar | modal + 'filter_combine' => 'OR', // OR | AND + 'tile_provider' => 'carto_light', + // Nuevas opciones + 'scroll_wheel_zoom' => 'ctrl', // ctrl | always | disabled + 'marker_icon' => 0, + 'marker_default_color' => '#F97316', + 'use_category_colors' => true, + 'show_legend' => true, + 'legend_taxonomy' => '', + 'sidebar_position' => 'right', // left | right + 'show_detail_button' => true, + 'detail_button_text' => 'Ver detalle', + ]; + + if (!get_option('fp_geo_content_options')) { + add_option('fp_geo_content_options', $defaults); + } + + update_option('fp_geo_content_version', FP_GEO_VERSION); + } + + /** + * Desactivación + */ + public function deactivate() { + // Nada que hacer por ahora + } + + /** + * Obtener opción + */ + public static function get_option($key, $default = null) { + $options = get_option('fp_geo_content_options', []); + return $options[$key] ?? $default; + } +} + +/** + * Iniciar el plugin + */ +function fp_geo_content() { + return FP_Geo_Content::get_instance(); +} + +// Arrancar +fp_geo_content(); diff --git a/fp-geo-content/includes/class-data-provider.php b/fp-geo-content/includes/class-data-provider.php new file mode 100644 index 0000000..da9eaad --- /dev/null +++ b/fp-geo-content/includes/class-data-provider.php @@ -0,0 +1,280 @@ + $options['post_types'] ?? [], + 'lat_field' => $options['lat_field'] ?? 'latitud', + 'lng_field' => $options['lng_field'] ?? 'longitud', + 'taxonomies' => [], + 'terms' => [], + 'filter_combine' => $options['filter_combine'] ?? 'OR', + ]; + + $args = wp_parse_args($args, $defaults); + + if (empty($args['post_types'])) { + return []; + } + + // Query args + $query_args = [ + 'post_type' => $args['post_types'], + 'posts_per_page' => -1, + 'post_status' => 'publish', + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => $args['lat_field'], + 'compare' => 'EXISTS', + ], + [ + 'key' => $args['lng_field'], + 'compare' => 'EXISTS', + ], + [ + 'key' => $args['lat_field'], + 'value' => '', + 'compare' => '!=', + ], + [ + 'key' => $args['lng_field'], + 'value' => '', + 'compare' => '!=', + ], + ], + ]; + + // Filtros por taxonomía + if (!empty($args['terms']) && is_array($args['terms'])) { + $tax_query = ['relation' => $args['filter_combine']]; + + foreach ($args['terms'] as $taxonomy => $term_slugs) { + if (!empty($term_slugs)) { + $tax_query[] = [ + 'taxonomy' => $taxonomy, + 'field' => 'slug', + 'terms' => (array) $term_slugs, + ]; + } + } + + if (count($tax_query) > 1) { + $query_args['tax_query'] = $tax_query; + } + } + + $query = new WP_Query($query_args); + $markers = []; + + if ($query->have_posts()) { + while ($query->have_posts()) { + $query->the_post(); + $post = get_post(); + + $marker = self::prepare_marker($post, $args); + if ($marker) { + $markers[] = $marker; + } + } + wp_reset_postdata(); + } + + return $markers; + } + + /** + * Preparar datos de un marcador + * + * @param WP_Post $post + * @param array $args + * @return array|null + */ + private static function prepare_marker($post, $args) { + // Obtener coordenadas + $lat = get_field($args['lat_field'], $post->ID); + $lng = get_field($args['lng_field'], $post->ID); + + // Si no hay coordenadas, intentar con get_post_meta + if (!$lat) { + $lat = get_post_meta($post->ID, $args['lat_field'], true); + } + if (!$lng) { + $lng = get_post_meta($post->ID, $args['lng_field'], true); + } + + // Validar coordenadas + if (!$lat || !$lng || !is_numeric($lat) || !is_numeric($lng)) { + return null; + } + + // Datos básicos + $marker = [ + 'id' => $post->ID, + 'title' => html_entity_decode(get_the_title($post), ENT_QUOTES, 'UTF-8'), + 'lat' => (float) $lat, + 'lng' => (float) $lng, + 'url' => get_permalink($post), + 'post_type' => $post->post_type, + 'excerpt' => wp_trim_words(get_the_excerpt($post), 20), + 'thumbnail' => get_the_post_thumbnail_url($post, 'medium'), + ]; + + // Obtener taxonomías + $options = get_option('fp_geo_content_options', []); + $filter_taxonomies = $options['filter_taxonomies'] ?? []; + + $marker['taxonomies'] = []; + foreach ($filter_taxonomies as $taxonomy) { + $terms = wp_get_post_terms($post->ID, $taxonomy); + if (!is_wp_error($terms) && !empty($terms)) { + $marker['taxonomies'][$taxonomy] = []; + foreach ($terms as $term) { + $term_data = [ + 'id' => $term->term_id, + 'name' => $term->name, + 'slug' => $term->slug, + ]; + + // Obtener campos ACF del término si existen (icono y color) + if (function_exists('get_field')) { + $icono = get_field('icono', $term); + $color = get_field('color', $term); + if ($icono) $term_data['icono'] = $icono; + if ($color) $term_data['color'] = $color; + } + + $marker['taxonomies'][$taxonomy][] = $term_data; + } + } + } + + // Campo es_piloto del post (para actuaciones) + $es_piloto = get_field('es_piloto', $post->ID); + if (!$es_piloto) { + $es_piloto = get_post_meta($post->ID, 'es_piloto', true); + } + if ($es_piloto) { + $marker['es_piloto'] = true; + } + + // Campos adicionales comunes + $additional_fields = ['direccion', 'localidad', 'telefono', 'email', 'web']; + foreach ($additional_fields as $field) { + $value = get_field($field, $post->ID); + if (!$value) { + $value = get_post_meta($post->ID, $field, true); + } + if ($value) { + $marker[$field] = $value; + } + } + + // Permitir filtrar los datos del marcador + return apply_filters('fp_geo_marker_data', $marker, $post); + } + + /** + * Handler AJAX para obtener marcadores + */ + public static function ajax_get_markers() { + check_ajax_referer('fp_geo_nonce', 'nonce'); + + $post_types = isset($_POST['post_types']) ? array_map('sanitize_text_field', (array) $_POST['post_types']) : []; + $terms = isset($_POST['terms']) ? $_POST['terms'] : []; + + // Sanitizar términos + $sanitized_terms = []; + if (is_array($terms)) { + foreach ($terms as $taxonomy => $slugs) { + $taxonomy = sanitize_text_field($taxonomy); + $sanitized_terms[$taxonomy] = array_map('sanitize_text_field', (array) $slugs); + } + } + + $markers = self::get_markers([ + 'post_types' => $post_types, + 'terms' => $sanitized_terms, + ]); + + wp_send_json_success(['markers' => $markers]); + } + + /** + * Obtener términos disponibles para filtros + * + * @param array $post_types + * @param array $taxonomies + * @return array + */ + public static function get_filter_terms($post_types = [], $taxonomies = []) { + $result = []; + + foreach ($taxonomies as $taxonomy) { + $tax_obj = get_taxonomy($taxonomy); + if (!$tax_obj) continue; + + // Verificar que la taxonomía está asociada a alguno de los post types + $associated = false; + foreach ($post_types as $pt) { + if (in_array($pt, $tax_obj->object_type)) { + $associated = true; + break; + } + } + + if (!$associated) continue; + + $terms = get_terms([ + 'taxonomy' => $taxonomy, + 'hide_empty' => true, + ]); + + if (!is_wp_error($terms) && !empty($terms)) { + $result[$taxonomy] = [ + 'label' => $tax_obj->label, + 'terms' => [], + ]; + + foreach ($terms as $term) { + $term_data = [ + 'id' => $term->term_id, + 'name' => $term->name, + 'slug' => $term->slug, + 'count' => $term->count, + ]; + + // Campos ACF del término (icono y color) + if (function_exists('get_field')) { + $icono = get_field('icono', $term); + $color = get_field('color', $term); + if ($icono) $term_data['icono'] = $icono; + if ($color) $term_data['color'] = $color; + } + + $result[$taxonomy]['terms'][] = $term_data; + } + } + } + + return $result; + } +} diff --git a/fp-geo-content/includes/class-map-renderer.php b/fp-geo-content/includes/class-map-renderer.php new file mode 100644 index 0000000..9c16829 --- /dev/null +++ b/fp-geo-content/includes/class-map-renderer.php @@ -0,0 +1,357 @@ + implode(',', $options['post_types'] ?? []), + 'taxonomies' => implode(',', $options['filter_taxonomies'] ?? []), + 'height' => '600px', + 'lat' => $options['default_lat'] ?? '40.4168', + 'lng' => $options['default_lng'] ?? '-3.7038', + 'zoom' => $options['default_zoom'] ?? 12, + 'filters' => 'true', + 'detail' => $options['detail_display'] ?? 'sidebar', + 'cluster' => $options['cluster_enabled'] ?? true, + 'sidebar_position' => $options['sidebar_position'] ?? 'right', + 'legend' => isset($options['show_legend']) ? ($options['show_legend'] ? 'true' : 'false') : 'false', + 'show_detail_btn' => isset($options['show_detail_button']) ? ($options['show_detail_button'] ? 'true' : 'false') : 'true', + 'detail_btn_text' => $options['detail_button_text'] ?? __('Ver detalle', 'fp-geo-content'), + 'class' => '', + ], $atts, 'fp-geo-map'); + + // Parsear valores + $post_types = array_filter(array_map('trim', explode(',', $atts['post_types']))); + $taxonomies = array_filter(array_map('trim', explode(',', $atts['taxonomies']))); + $show_filters = filter_var($atts['filters'], FILTER_VALIDATE_BOOLEAN); + $use_cluster = filter_var($atts['cluster'], FILTER_VALIDATE_BOOLEAN); + $show_legend = filter_var($atts['legend'], FILTER_VALIDATE_BOOLEAN); + $show_detail_btn = filter_var($atts['show_detail_btn'], FILTER_VALIDATE_BOOLEAN); + $sidebar_position = $atts['sidebar_position']; + + if (empty($post_types)) { + return '

' . __('No se han configurado tipos de contenido para el mapa.', 'fp-geo-content') . '

'; + } + + // Incrementar contador de instancias + self::$instance_count++; + $map_id = 'fp-geo-map-' . self::$instance_count; + + // Cargar assets + wp_enqueue_style('fp-geo-content'); + wp_enqueue_script('fp-geo-content'); + + // Obtener marcadores + $markers = FP_Geo_Data_Provider::get_markers([ + 'post_types' => $post_types, + ]); + + // Obtener términos para filtros + $filter_terms = []; + if ($show_filters && !empty($taxonomies)) { + $filter_terms = FP_Geo_Data_Provider::get_filter_terms($post_types, $taxonomies); + } + + // Obtener configuración de tiles + $tile_provider = $options['tile_provider'] ?? 'carto_light'; + $tile_providers = FP_Geo_Settings::get_tile_providers(); + $tile_config = $tile_providers[$tile_provider] ?? $tile_providers['carto_light']; + + // Obtener icono personalizado si existe + $marker_icon_id = $options['marker_icon'] ?? 0; + $marker_icon_url = $marker_icon_id ? wp_get_attachment_image_url($marker_icon_id, 'full') : ''; + + // Obtener etiquetas de los post types + $post_type_labels = []; + foreach ($post_types as $pt) { + $pt_obj = get_post_type_object($pt); + if ($pt_obj) { + $post_type_labels[$pt] = [ + 'singular' => $pt_obj->labels->singular_name, + 'plural' => $pt_obj->labels->name, + ]; + } + } + + // Obtener datos de leyenda si está habilitada + $legend_data = []; + $legend_taxonomy = $options['legend_taxonomy'] ?? ''; + if ($show_legend && !empty($legend_taxonomy) && isset($filter_terms[$legend_taxonomy])) { + // Verificar si hay marcadores piloto (es_piloto está en los posts, no en los términos) + $has_pilot_markers = false; + foreach ($markers as $marker) { + if (isset($marker['es_piloto']) && $marker['es_piloto']) { + $has_pilot_markers = true; + break; + } + } + + $legend_data = [ + 'taxonomy' => $legend_taxonomy, + 'label' => $filter_terms[$legend_taxonomy]['label'], + 'items' => $filter_terms[$legend_taxonomy]['terms'], + 'has_pilots' => $has_pilot_markers, + ]; + } + + // Configuración del mapa + $map_config = [ + 'mapId' => $map_id, + 'center' => [(float) $atts['lat'], (float) $atts['lng']], + 'zoom' => (int) $atts['zoom'], + 'minZoom' => (int) ($options['min_zoom'] ?? 5), + 'maxZoom' => (int) ($options['max_zoom'] ?? 18), + 'markers' => $markers, + 'filters' => $filter_terms, + 'filterCombine' => $options['filter_combine'] ?? 'OR', + 'detailDisplay' => $atts['detail'], + 'clusterEnabled' => $use_cluster, + 'tileUrl' => $tile_config['url'], + 'tileAttribution' => $tile_config['attribution'], + 'tileSubdomains' => $tile_config['subdomains'], + 'defaultIcon' => FP_GEO_PLUGIN_URL . 'assets/img/marker-icon.png', + 'ajaxUrl' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('fp_geo_nonce'), + 'postTypes' => $post_types, + 'postTypeLabels' => $post_type_labels, + // Nuevas opciones de marcadores + 'markerIcon' => $marker_icon_url, + 'markerDefaultColor' => $options['marker_default_color'] ?? '#F97316', + 'useCategoryColors' => isset($options['use_category_colors']) && $options['use_category_colors'], + 'legendTaxonomy' => $legend_taxonomy, + // Nuevas opciones de scroll + 'scrollWheelZoom' => $options['scroll_wheel_zoom'] ?? 'ctrl', + // Nuevas opciones de display + 'sidebarPosition' => $sidebar_position, + 'showLegend' => $show_legend, + 'legendData' => $legend_data, + 'showDetailButton' => $show_detail_btn, + 'detailButtonText' => $atts['detail_btn_text'], + 'i18n' => [ + 'loading' => __('Cargando...', 'fp-geo-content'), + 'noResults' => __('No se encontraron resultados', 'fp-geo-content'), + 'viewMore' => $atts['detail_btn_text'], + 'close' => __('Cerrar', 'fp-geo-content'), + 'clearFilters' => __('Limpiar filtros', 'fp-geo-content'), + 'scrollZoomHint' => __('Usa Ctrl + scroll para hacer zoom', 'fp-geo-content'), + ], + ]; + + // Pasar configuración al JS + wp_localize_script('fp-geo-content', 'fpGeoConfig_' . self::$instance_count, $map_config); + + ob_start(); + $wrapper_classes = [ + 'fp-geo-wrapper', + 'fp-geo-detail-' . esc_attr($atts['detail']), + 'fp-geo-sidebar-' . esc_attr($sidebar_position), + esc_attr($atts['class']), + ]; +?> +
+ + + + + +
+
+ + + + + + +
+
+ +
+
+ $data): ?> +
+ +
+ + + +
+
+ + + +
+
+ +
+
+ +
+
+
+ +
+ + +
+ +
+
+ + +
+
+
+ + +
+

+ +

+
+
+ +
+ +
+ + +
+
+
+
+ +
+ +

+ +
+ +
+
+
+
+ + + + +
+
+ + +
+ + [__CLASS__, 'sanitize_options'], + ]); + + // Sección: Tipos de contenido + add_settings_section( + 'fp_geo_content_types', + __('Tipos de Contenido', 'fp-geo-content'), + [__CLASS__, 'section_content_types'], + 'fp-geo-content' + ); + + add_settings_field( + 'post_types', + __('Tipos de contenido a mostrar', 'fp-geo-content'), + [__CLASS__, 'field_post_types'], + 'fp-geo-content', + 'fp_geo_content_types' + ); + + add_settings_field( + 'lat_field', + __('Campo de Latitud', 'fp-geo-content'), + [__CLASS__, 'field_lat'], + 'fp-geo-content', + 'fp_geo_content_types' + ); + + add_settings_field( + 'lng_field', + __('Campo de Longitud', 'fp-geo-content'), + [__CLASS__, 'field_lng'], + 'fp-geo-content', + 'fp_geo_content_types' + ); + + // Sección: Configuración del mapa + add_settings_section( + 'fp_geo_map_settings', + __('Configuración del Mapa', 'fp-geo-content'), + [__CLASS__, 'section_map_settings'], + 'fp-geo-content' + ); + + add_settings_field( + 'default_center', + __('Centro por defecto', 'fp-geo-content'), + [__CLASS__, 'field_default_center'], + 'fp-geo-content', + 'fp_geo_map_settings' + ); + + add_settings_field( + 'zoom_levels', + __('Niveles de zoom', 'fp-geo-content'), + [__CLASS__, 'field_zoom_levels'], + 'fp-geo-content', + 'fp_geo_map_settings' + ); + + add_settings_field( + 'tile_provider', + __('Proveedor de tiles', 'fp-geo-content'), + [__CLASS__, 'field_tile_provider'], + 'fp-geo-content', + 'fp_geo_map_settings' + ); + + add_settings_field( + 'cluster_enabled', + __('Agrupar marcadores', 'fp-geo-content'), + [__CLASS__, 'field_cluster'], + 'fp-geo-content', + 'fp_geo_map_settings' + ); + + add_settings_field( + 'scroll_wheel_zoom', + __('Zoom con scroll', 'fp-geo-content'), + [__CLASS__, 'field_scroll_wheel_zoom'], + 'fp-geo-content', + 'fp_geo_map_settings' + ); + + // Sección: Marcadores + add_settings_section( + 'fp_geo_markers', + __('Marcadores', 'fp-geo-content'), + [__CLASS__, 'section_markers'], + 'fp-geo-content' + ); + + add_settings_field( + 'marker_icon', + __('Icono personalizado', 'fp-geo-content'), + [__CLASS__, 'field_marker_icon'], + 'fp-geo-content', + 'fp_geo_markers' + ); + + add_settings_field( + 'marker_default_color', + __('Color por defecto', 'fp-geo-content'), + [__CLASS__, 'field_marker_default_color'], + 'fp-geo-content', + 'fp_geo_markers' + ); + + add_settings_field( + 'use_category_colors', + __('Colores por categoría', 'fp-geo-content'), + [__CLASS__, 'field_use_category_colors'], + 'fp-geo-content', + 'fp_geo_markers' + ); + + add_settings_field( + 'show_legend', + __('Mostrar leyenda', 'fp-geo-content'), + [__CLASS__, 'field_show_legend'], + 'fp-geo-content', + 'fp_geo_markers' + ); + + // Sección: Filtros + add_settings_section( + 'fp_geo_filters', + __('Filtros', 'fp-geo-content'), + [__CLASS__, 'section_filters'], + 'fp-geo-content' + ); + + add_settings_field( + 'filter_taxonomies', + __('Taxonomías para filtrar', 'fp-geo-content'), + [__CLASS__, 'field_filter_taxonomies'], + 'fp-geo-content', + 'fp_geo_filters' + ); + + add_settings_field( + 'filter_combine', + __('Combinación de filtros', 'fp-geo-content'), + [__CLASS__, 'field_filter_combine'], + 'fp-geo-content', + 'fp_geo_filters' + ); + + // Sección: Visualización + add_settings_section( + 'fp_geo_display', + __('Visualización', 'fp-geo-content'), + [__CLASS__, 'section_display'], + 'fp-geo-content' + ); + + add_settings_field( + 'detail_display', + __('Mostrar detalle en', 'fp-geo-content'), + [__CLASS__, 'field_detail_display'], + 'fp-geo-content', + 'fp_geo_display' + ); + + add_settings_field( + 'sidebar_position', + __('Posición del panel lateral', 'fp-geo-content'), + [__CLASS__, 'field_sidebar_position'], + 'fp-geo-content', + 'fp_geo_display' + ); + + add_settings_field( + 'show_detail_button', + __('Botón "Ver detalle"', 'fp-geo-content'), + [__CLASS__, 'field_show_detail_button'], + 'fp-geo-content', + 'fp_geo_display' + ); + + add_settings_field( + 'detail_button_text', + __('Texto del botón', 'fp-geo-content'), + [__CLASS__, 'field_detail_button_text'], + 'fp-geo-content', + 'fp_geo_display' + ); + + // Sección: CSS Personalizado + add_settings_section( + 'fp_geo_custom_css', + __('CSS Personalizado', 'fp-geo-content'), + [__CLASS__, 'section_custom_css'], + 'fp-geo-content' + ); + + add_settings_field( + 'custom_css', + __('Estilos CSS', 'fp-geo-content'), + [__CLASS__, 'field_custom_css'], + 'fp-geo-content', + 'fp_geo_custom_css' + ); + } + + /** + * Sanitizar opciones + */ + public static function sanitize_options($input) { + $sanitized = []; + + // Post types + $sanitized['post_types'] = isset($input['post_types']) && is_array($input['post_types']) + ? array_map('sanitize_text_field', $input['post_types']) + : []; + + // Campos de lat/lng + $sanitized['lat_field'] = sanitize_text_field($input['lat_field'] ?? 'latitud'); + $sanitized['lng_field'] = sanitize_text_field($input['lng_field'] ?? 'longitud'); + + // Centro por defecto + $sanitized['default_lat'] = floatval($input['default_lat'] ?? 40.4168); + $sanitized['default_lng'] = floatval($input['default_lng'] ?? -3.7038); + + // Zoom + $sanitized['default_zoom'] = intval($input['default_zoom'] ?? 12); + $sanitized['min_zoom'] = intval($input['min_zoom'] ?? 5); + $sanitized['max_zoom'] = intval($input['max_zoom'] ?? 18); + + // Cluster + $sanitized['cluster_enabled'] = isset($input['cluster_enabled']); + + // Tile provider + $sanitized['tile_provider'] = sanitize_text_field($input['tile_provider'] ?? 'carto_light'); + + // Filtros + $sanitized['filter_taxonomies'] = isset($input['filter_taxonomies']) && is_array($input['filter_taxonomies']) + ? array_map('sanitize_text_field', $input['filter_taxonomies']) + : []; + $sanitized['filter_combine'] = in_array($input['filter_combine'] ?? 'OR', ['OR', 'AND']) + ? $input['filter_combine'] + : 'OR'; + + // Display + $sanitized['detail_display'] = in_array($input['detail_display'] ?? 'sidebar', ['sidebar', 'modal']) + ? $input['detail_display'] + : 'sidebar'; + + // Scroll wheel zoom + $sanitized['scroll_wheel_zoom'] = sanitize_text_field($input['scroll_wheel_zoom'] ?? 'ctrl'); + + // Marcadores + $sanitized['marker_icon'] = isset($input['marker_icon']) ? absint($input['marker_icon']) : 0; + $sanitized['marker_default_color'] = sanitize_hex_color($input['marker_default_color'] ?? '#F97316'); + $sanitized['use_category_colors'] = isset($input['use_category_colors']); + $sanitized['show_legend'] = isset($input['show_legend']); + $sanitized['legend_taxonomy'] = sanitize_text_field($input['legend_taxonomy'] ?? ''); + + // Sidebar position + $sanitized['sidebar_position'] = in_array($input['sidebar_position'] ?? 'right', ['left', 'right']) + ? $input['sidebar_position'] + : 'right'; + + // Detail button + $sanitized['show_detail_button'] = isset($input['show_detail_button']); + $sanitized['detail_button_text'] = sanitize_text_field($input['detail_button_text'] ?? __('Ver detalle', 'fp-geo-content')); + + // Custom CSS - sanitizar pero permitir CSS válido + $sanitized['custom_css'] = isset($input['custom_css']) ? wp_strip_all_tags($input['custom_css']) : ''; + + return $sanitized; + } + + /** + * Sección: Tipos de contenido + */ + public static function section_content_types() { + echo '

' . __('Selecciona los tipos de contenido que tienen campos de geolocalización y que quieres mostrar en el mapa.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Post types + */ + public static function field_post_types() { + $options = get_option('fp_geo_content_options', []); + $selected = $options['post_types'] ?? []; + + // Obtener todos los post types públicos + $post_types = get_post_types(['public' => true], 'objects'); + + echo '
'; + foreach ($post_types as $pt) { + if ($pt->name === 'attachment') continue; + + $checked = in_array($pt->name, $selected) ? 'checked' : ''; + printf( + '', + esc_attr($pt->name), + $checked, + esc_html($pt->label), + esc_html($pt->name) + ); + } + echo '
'; + echo '

' . __('Solo se mostrarán los posts que tengan coordenadas de latitud y longitud.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Latitud + */ + public static function field_lat() { + $options = get_option('fp_geo_content_options', []); + $value = $options['lat_field'] ?? 'latitud'; + + printf( + '', + esc_attr($value) + ); + echo '

' . __('Nombre del campo ACF o meta que contiene la latitud.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Longitud + */ + public static function field_lng() { + $options = get_option('fp_geo_content_options', []); + $value = $options['lng_field'] ?? 'longitud'; + + printf( + '', + esc_attr($value) + ); + echo '

' . __('Nombre del campo ACF o meta que contiene la longitud.', 'fp-geo-content') . '

'; + } + + /** + * Sección: Configuración del mapa + */ + public static function section_map_settings() { + echo '

' . __('Configura el comportamiento y apariencia del mapa.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Centro por defecto + */ + public static function field_default_center() { + $options = get_option('fp_geo_content_options', []); + $lat = $options['default_lat'] ?? '40.4168'; + $lng = $options['default_lng'] ?? '-3.7038'; + + printf( + ' ', + esc_attr($lat) + ); + printf( + '', + esc_attr($lng) + ); + echo '

' . __('Coordenadas del centro inicial del mapa (por defecto: Madrid).', 'fp-geo-content') . '

'; + } + + /** + * Campo: Niveles de zoom + */ + public static function field_zoom_levels() { + $options = get_option('fp_geo_content_options', []); + $default = $options['default_zoom'] ?? 12; + $min = $options['min_zoom'] ?? 5; + $max = $options['max_zoom'] ?? 18; + + printf( + ' ', + esc_attr($default) + ); + printf( + ' ', + esc_attr($min) + ); + printf( + '', + esc_attr($max) + ); + } + + /** + * Campo: Proveedor de tiles + */ + public static function field_tile_provider() { + $options = get_option('fp_geo_content_options', []); + $value = $options['tile_provider'] ?? 'carto_light'; + + $providers = [ + 'osm' => 'OpenStreetMap (estándar)', + 'carto_light' => 'CartoDB Positron (claro)', + 'carto_dark' => 'CartoDB Dark Matter (oscuro)', + 'carto_voyager' => 'CartoDB Voyager (colores)', + 'stamen_toner' => 'Stamen Toner (B/N)', + 'stamen_watercolor' => 'Stamen Watercolor (acuarela)', + ]; + + echo ''; + } + + /** + * Campo: Cluster + */ + public static function field_cluster() { + $options = get_option('fp_geo_content_options', []); + $checked = isset($options['cluster_enabled']) && $options['cluster_enabled'] ? 'checked' : ''; + + printf( + '', + $checked, + __('Agrupar marcadores cercanos en clusters', 'fp-geo-content') + ); + } + + /** + * Sección: Filtros + */ + public static function section_filters() { + echo '

' . __('Configura qué taxonomías se pueden usar como filtros en el mapa.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Taxonomías para filtrar + */ + public static function field_filter_taxonomies() { + $options = get_option('fp_geo_content_options', []); + $selected = $options['filter_taxonomies'] ?? []; + + // Obtener todas las taxonomías públicas + $taxonomies = get_taxonomies(['public' => true], 'objects'); + + echo '
'; + foreach ($taxonomies as $tax) { + if (in_array($tax->name, ['post_format'])) continue; + + $checked = in_array($tax->name, $selected) ? 'checked' : ''; + printf( + '', + esc_attr($tax->name), + $checked, + esc_html($tax->label), + esc_html($tax->name) + ); + } + echo '
'; + } + + /** + * Campo: Combinación de filtros + */ + public static function field_filter_combine() { + $options = get_option('fp_geo_content_options', []); + $value = $options['filter_combine'] ?? 'OR'; + + echo ''; + } + + /** + * Sección: Visualización + */ + public static function section_display() { + echo '

' . __('Configura cómo se muestra la información de cada marcador.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Mostrar detalle + */ + public static function field_detail_display() { + $options = get_option('fp_geo_content_options', []); + $value = $options['detail_display'] ?? 'sidebar'; + + echo ''; + echo '

' . __('Cómo mostrar la ficha de detalle al hacer clic en un marcador.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Scroll wheel zoom + */ + public static function field_scroll_wheel_zoom() { + $options = get_option('fp_geo_content_options', []); + $value = $options['scroll_wheel_zoom'] ?? 'ctrl'; + + echo ''; + echo '

' . __('Controla si el scroll del ratón hace zoom en el mapa. "Solo con Ctrl" permite hacer scroll en la página sin hacer zoom accidentalmente.', 'fp-geo-content') . '

'; + } + + /** + * Sección: Marcadores + */ + public static function section_markers() { + echo '

' . __('Personaliza la apariencia de los marcadores en el mapa.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Icono personalizado + */ + public static function field_marker_icon() { + $options = get_option('fp_geo_content_options', []); + $image_id = $options['marker_icon'] ?? 0; + $image_url = $image_id ? wp_get_attachment_image_url($image_id, 'thumbnail') : ''; + + echo '
'; + printf( + '', + esc_attr($image_id) + ); + printf( + '
', + $image_url ? '' : 'display:none;' + ); + if ($image_url) { + printf('', esc_url($image_url)); + } + echo '
'; + printf( + '', + __('Subir icono', 'fp-geo-content') + ); + printf( + '', + $image_id ? '' : 'display:none;', + __('Eliminar', 'fp-geo-content') + ); + echo '
'; + echo '

' . __('Opcional: Sube un icono personalizado para los marcadores. Si no se sube, se usarán círculos de color. Tamaño recomendado: 40x50px.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Color por defecto + */ + public static function field_marker_default_color() { + $options = get_option('fp_geo_content_options', []); + $value = $options['marker_default_color'] ?? '#F97316'; + + printf( + '', + esc_attr($value) + ); + echo '

' . __('Color por defecto para los marcadores cuando no tienen color de categoría asignado.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Usar colores por categoría + */ + public static function field_use_category_colors() { + $options = get_option('fp_geo_content_options', []); + $checked = isset($options['use_category_colors']) && $options['use_category_colors'] ? 'checked' : ''; + $legend_taxonomy = $options['legend_taxonomy'] ?? ''; + + printf( + '', + $checked, + __('Usar colores de las categorías de los filtros (si tienen campo ACF "color")', 'fp-geo-content') + ); + + // Selector de taxonomía para los colores + $taxonomies = get_taxonomies(['public' => true], 'objects'); + echo '
'; + echo ''; + echo '

' . __('Selecciona la taxonomía cuyos colores quieres usar para los marcadores.', 'fp-geo-content') . '

'; + echo '
'; + } + + /** + * Campo: Mostrar leyenda + */ + public static function field_show_legend() { + $options = get_option('fp_geo_content_options', []); + $checked = isset($options['show_legend']) && $options['show_legend'] ? 'checked' : ''; + + printf( + '', + $checked, + __('Mostrar leyenda de colores sobre el mapa', 'fp-geo-content') + ); + echo '

' . __('Muestra una leyenda con los colores de las categorías en la esquina del mapa.', 'fp-geo-content') . '

'; + } + + /** + * Campo: Posición del sidebar + */ + public static function field_sidebar_position() { + $options = get_option('fp_geo_content_options', []); + $value = $options['sidebar_position'] ?? 'right'; + + echo ''; + echo '

' . __('Posición del panel lateral cuando se muestra el detalle (solo aplica al modo sidebar).', 'fp-geo-content') . '

'; + } + + /** + * Campo: Mostrar botón detalle + */ + public static function field_show_detail_button() { + $options = get_option('fp_geo_content_options', []); + // Por defecto activado si no existe la opción + $checked = !isset($options['show_detail_button']) || $options['show_detail_button'] ? 'checked' : ''; + + printf( + '', + $checked, + __('Mostrar botón para ir al detalle del contenido', 'fp-geo-content') + ); + } + + /** + * Campo: Texto del botón + */ + public static function field_detail_button_text() { + $options = get_option('fp_geo_content_options', []); + $value = $options['detail_button_text'] ?? __('Ver detalle', 'fp-geo-content'); + + printf( + '', + esc_attr($value) + ); + echo '

' . __('Texto del botón que enlaza al contenido completo. Ejemplos: "Ver detalle", "Ver más", "Ir al contenido"...', 'fp-geo-content') . '

'; + } + + /** + * Sección: CSS Personalizado + */ + public static function section_custom_css() { + echo '

' . __('Añade CSS personalizado para sobreescribir los estilos base del mapa. Estos estilos se cargarán después de los estilos base del plugin.', 'fp-geo-content') . '

'; + } + + /** + * Campo: CSS Personalizado + */ + public static function field_custom_css() { + $options = get_option('fp_geo_content_options', []); + $value = $options['custom_css'] ?? ''; + + printf( + '', + esc_textarea($value) + ); + echo '

' . __('Introduce CSS válido para personalizar el aspecto del mapa. No incluyas etiquetas <style>.', 'fp-geo-content') . '

'; + + // Mostrar clases disponibles + echo '
'; + echo '' . __('Ver clases CSS disponibles', 'fp-geo-content') . ''; + echo '
'; + echo '.fp-geo-wrapper - Contenedor principal
'; + echo '.fp-geo-filters - Barra de filtros
'; + echo '.fp-geo-filter-group - Grupo de filtros
'; + echo '.fp-geo-filter-label - Etiqueta del grupo
'; + echo '.fp-geo-filter-btn - Botón de filtro
'; + echo '.fp-geo-filter-btn.active - Botón activo
'; + echo '.fp-geo-clear-filters - Botón limpiar
'; + echo '.fp-geo-map - Contenedor del mapa
'; + echo '.fp-geo-legend - Leyenda
'; + echo '.fp-geo-legend-item - Item de leyenda
'; + echo '.fp-geo-legend-marker - Marcador en leyenda
'; + echo '.fp-geo-detail - Panel de detalle
'; + echo '.fp-geo-sidebar - Panel lateral
'; + echo '.fp-geo-modal - Modal
'; + echo '.fp-geo-detail-header - Cabecera del detalle
'; + echo '.fp-geo-detail-title - Título
'; + echo '.fp-geo-detail-excerpt - Extracto
'; + echo '.fp-geo-btn - Botones
'; + echo '.fp-geo-circle-marker - Marcadores circulares
'; + echo '.marker-cluster - Clusters
'; + echo '
'; + echo '
'; + } + + /** + * Obtener proveedores de tiles + */ + public static function get_tile_providers() { + return [ + 'osm' => [ + 'url' => 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'attribution' => '© OpenStreetMap', + 'subdomains' => 'abc', + ], + 'carto_light' => [ + 'url' => 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', + 'attribution' => '© CARTO', + 'subdomains' => 'abcd', + ], + 'carto_dark' => [ + 'url' => 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', + 'attribution' => '© CARTO', + 'subdomains' => 'abcd', + ], + 'carto_voyager' => [ + 'url' => 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', + 'attribution' => '© CARTO', + 'subdomains' => 'abcd', + ], + 'stamen_toner' => [ + 'url' => 'https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.png', + 'attribution' => '© Stadia Maps', + 'subdomains' => '', + ], + 'stamen_watercolor' => [ + 'url' => 'https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg', + 'attribution' => '© Stadia Maps', + 'subdomains' => '', + ], + ]; + } +} diff --git a/fp-geo-content/templates/settings-page.php b/fp-geo-content/templates/settings-page.php new file mode 100644 index 0000000..1e3ff74 --- /dev/null +++ b/fp-geo-content/templates/settings-page.php @@ -0,0 +1,179 @@ + +
+

+ + +

+ +
+

+
+ +
+ +
+ +
+

+ +

+ + [fp-geo-map] + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
post_typespost_types="actuacion,entidad"
taxonomiestaxonomies="iniciativa,linea_trabajo"
heightheight="500px"
lat / lnglat="40.4168" lng="-3.7038"
zoomzoom="14"
filtersfilters="true"
detaildetail="sidebar" o detail="modal"
clustercluster="true"
classclass="mi-mapa-custom"
sidebar_positionsidebar_position="left" o sidebar_position="right"
legendlegend="true"
show_detail_btnshow_detail_btn="true"
detail_btn_textdetail_btn_text="Ver más"
+ +

+ +
[fp-geo-map post_types="actuacion,entidad" height="600px" filters="true"]
+ +
[fp-geo-map post_types="evento_bloques" detail="modal" cluster="false"]
+ +
[fp-geo-map post_types="actuacion" sidebar_position="left" legend="true" detail_btn_text="Ver más información"]
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.fp-geo-wrapper
.fp-geo-map
.fp-geo-filters
.fp-geo-filter-btn
.fp-geo-detail
.fp-geo-sidebar
.fp-geo-modal
+ +

+ +
:root {
+    --fp-geo-primary: #1E6B52;
+    --fp-geo-secondary: #2A9D8F;
+    --fp-geo-accent: #F4A261;
+    --fp-geo-text: #333;
+    --fp-geo-bg: #fff;
+    --fp-geo-radius: 8px;
+}
+
+
\ No newline at end of file