feat(visibility): sistema de visibilidad por tipo de página
- Añadir PageVisibility use case y repositorio - Implementar PageTypeDetector para detectar home/single/page/archive - Actualizar FieldMappers con soporte show_on_[page_type] - Extender FormBuilders con UI de visibilidad por página - Refactorizar Renderers para evaluar visibilidad dinámica - Limpiar schemas removiendo campos de visibilidad legacy - Añadir MigrationCommand para migrar configuraciones existentes - Implementar adsense-loader.js para carga lazy de ads - Actualizar front-page.php con nueva estructura - Extender DIContainer con nuevos servicios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,16 @@ final class AdsensePlacementFieldMapper implements FieldMapperInterface
|
|||||||
'adsense-placementVignetteReshowEnabled' => ['group' => 'vignette_ads', 'attribute' => 'vignette_reshow_enabled'],
|
'adsense-placementVignetteReshowEnabled' => ['group' => 'vignette_ads', 'attribute' => 'vignette_reshow_enabled'],
|
||||||
'adsense-placementVignetteReshowTime' => ['group' => 'vignette_ads', 'attribute' => 'vignette_reshow_time'],
|
'adsense-placementVignetteReshowTime' => ['group' => 'vignette_ads', 'attribute' => 'vignette_reshow_time'],
|
||||||
'adsense-placementVignetteMaxPerSession' => ['group' => 'vignette_ads', 'attribute' => 'vignette_max_per_session'],
|
'adsense-placementVignetteMaxPerSession' => ['group' => 'vignette_ads', 'attribute' => 'vignette_max_per_session'],
|
||||||
|
|
||||||
|
// SEARCH RESULTS (ROI APU Search)
|
||||||
|
'adsense-placementSearchAdsEnabled' => ['group' => 'search_results', 'attribute' => 'search_ads_enabled'],
|
||||||
|
'adsense-placementSearchTopAdEnabled' => ['group' => 'search_results', 'attribute' => 'search_top_ad_enabled'],
|
||||||
|
'adsense-placementSearchTopAdFormat' => ['group' => 'search_results', 'attribute' => 'search_top_ad_format'],
|
||||||
|
'adsense-placementSearchBetweenEnabled' => ['group' => 'search_results', 'attribute' => 'search_between_enabled'],
|
||||||
|
'adsense-placementSearchBetweenMax' => ['group' => 'search_results', 'attribute' => 'search_between_max'],
|
||||||
|
'adsense-placementSearchBetweenFormat' => ['group' => 'search_results', 'attribute' => 'search_between_format'],
|
||||||
|
'adsense-placementSearchBetweenPosition' => ['group' => 'search_results', 'attribute' => 'search_between_position'],
|
||||||
|
'adsense-placementSearchBetweenEvery' => ['group' => 'search_results', 'attribute' => 'search_between_every'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ final class AdsensePlacementFormBuilder
|
|||||||
$html .= $this->buildRailAdsGroup($componentId);
|
$html .= $this->buildRailAdsGroup($componentId);
|
||||||
$html .= $this->buildAnchorAdsGroup($componentId);
|
$html .= $this->buildAnchorAdsGroup($componentId);
|
||||||
$html .= $this->buildVignetteAdsGroup($componentId);
|
$html .= $this->buildVignetteAdsGroup($componentId);
|
||||||
|
$html .= $this->buildSearchResultsGroup($componentId);
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
@@ -708,6 +709,101 @@ final class AdsensePlacementFormBuilder
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seccion para anuncios en resultados de busqueda (ROI APU Search)
|
||||||
|
*/
|
||||||
|
private function buildSearchResultsGroup(string $cid): string
|
||||||
|
{
|
||||||
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #fd7e14;">';
|
||||||
|
$html .= ' <div class="card-body">';
|
||||||
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
|
$html .= ' <i class="bi bi-search me-2" style="color: #fd7e14;"></i>';
|
||||||
|
$html .= ' Resultados de Busqueda';
|
||||||
|
$html .= ' <span class="badge bg-secondary ms-2">ROI APU Search</span>';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
$html .= ' <p class="small text-muted mb-3">Insertar anuncios en los resultados del buscador de Analisis de Precios Unitarios.</p>';
|
||||||
|
|
||||||
|
// Master switch
|
||||||
|
$searchAdsEnabled = $this->renderer->getFieldValue($cid, 'search_results', 'search_ads_enabled', false);
|
||||||
|
$html .= $this->buildSwitch($cid . 'SearchAdsEnabled', 'Activar ads en busqueda', $searchAdsEnabled, 'bi-power');
|
||||||
|
|
||||||
|
// Anuncio superior
|
||||||
|
$html .= '<div class="border rounded p-3 mb-3" style="background: #fff8f0;">';
|
||||||
|
$html .= '<div class="d-flex align-items-center gap-2 mb-2">';
|
||||||
|
$html .= ' <span class="badge" style="background: #fd7e14;">ANUNCIO SUPERIOR</span>';
|
||||||
|
$html .= ' <small class="text-muted">Debajo del campo de busqueda</small>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$topEnabled = $this->renderer->getFieldValue($cid, 'search_results', 'search_top_ad_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'SearchTopAdEnabled', 'Activar', $topEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$topFormat = $this->renderer->getFieldValue($cid, 'search_results', 'search_top_ad_format', 'auto');
|
||||||
|
$html .= $this->buildSelect($cid . 'SearchTopAdFormat', 'Formato',
|
||||||
|
(string)$topFormat,
|
||||||
|
['auto' => 'Auto (responsive)', 'display' => 'Display (fijo)', 'in-article' => 'In-Article (fluid)']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Anuncios entre resultados
|
||||||
|
$html .= '<div class="border rounded p-3" style="background: #fff8f0;">';
|
||||||
|
$html .= '<div class="d-flex align-items-center gap-2 mb-2">';
|
||||||
|
$html .= ' <span class="badge" style="background: #fd7e14;">ENTRE RESULTADOS</span>';
|
||||||
|
$html .= ' <small class="text-muted">Intercalados con los resultados</small>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2 mb-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$betweenEnabled = $this->renderer->getFieldValue($cid, 'search_results', 'search_between_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'SearchBetweenEnabled', 'Activar', $betweenEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$betweenMax = $this->renderer->getFieldValue($cid, 'search_results', 'search_between_max', '1');
|
||||||
|
$html .= $this->buildSelect($cid . 'SearchBetweenMax', 'Maximo ads',
|
||||||
|
(string)$betweenMax,
|
||||||
|
['1' => '1 anuncio', '2' => '2 anuncios', '3' => '3 anuncios (max)']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2 mb-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$betweenFormat = $this->renderer->getFieldValue($cid, 'search_results', 'search_between_format', 'in-article');
|
||||||
|
$html .= $this->buildSelect($cid . 'SearchBetweenFormat', 'Formato',
|
||||||
|
(string)$betweenFormat,
|
||||||
|
['in-article' => 'In-Article (fluid)', 'auto' => 'Auto (responsive)', 'autorelaxed' => 'Autorelaxed (feed)']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$betweenPosition = $this->renderer->getFieldValue($cid, 'search_results', 'search_between_position', 'random');
|
||||||
|
$html .= $this->buildSelect($cid . 'SearchBetweenPosition', 'Posicion',
|
||||||
|
(string)$betweenPosition,
|
||||||
|
['random' => 'Aleatorio', 'fixed' => 'Fijo (cada N)', 'first_half' => 'Primera mitad']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$betweenEvery = $this->renderer->getFieldValue($cid, 'search_results', 'search_between_every', '5');
|
||||||
|
$html .= $this->buildSelect($cid . 'SearchBetweenEvery', 'Cada N resultados (si es fijo)',
|
||||||
|
(string)$betweenEvery,
|
||||||
|
['3' => 'Cada 3', '4' => 'Cada 4', '5' => 'Cada 5', '6' => 'Cada 6', '7' => 'Cada 7', '8' => 'Cada 8', '10' => 'Cada 10']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
private function buildExclusionsGroup(string $cid): string
|
private function buildExclusionsGroup(string $cid): string
|
||||||
{
|
{
|
||||||
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #6c757d;">';
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #6c757d;">';
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class ContactFormFieldMapper implements FieldMapperInterface
|
|||||||
'contactFormEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'contactFormEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'contactFormShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'contactFormShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'contactFormShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'contactFormShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'contactFormShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'contactFormVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'contactFormVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'contactFormVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'contactFormVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'contactFormVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'contactFormSectionTitle' => ['group' => 'content', 'attribute' => 'section_title'],
|
'contactFormSectionTitle' => ['group' => 'content', 'attribute' => 'section_title'],
|
||||||
|
|||||||
@@ -93,17 +93,38 @@ final class ContactFormFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
||||||
$html .= $this->buildSwitch('contactFormShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('contactFormShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'all');
|
// =============================================
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <label for="contactFormShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
// =============================================
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' </label>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <select id="contactFormShowOnPages" class="form-select form-select-sm">';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="all"' . ($showOnPages === 'all' ? ' selected' : '') . '>Todos</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="posts"' . ($showOnPages === 'posts' ? ' selected' : '') . '>Solo posts</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="pages"' . ($showOnPages === 'pages' ? ' selected' : '') . '>Solo paginas</option>';
|
|
||||||
$html .= ' </select>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('contactFormVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('contactFormVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('contactFormVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('contactFormVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('contactFormVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -598,4 +619,26 @@ final class ContactFormFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ final class CtaBoxSidebarFieldMapper implements FieldMapperInterface
|
|||||||
'ctaEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'ctaEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'ctaShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'ctaShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'ctaShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'ctaShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'ctaShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'ctaVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'ctaVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'ctaVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'ctaVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'ctaVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'ctaTitle' => ['group' => 'content', 'attribute' => 'title'],
|
'ctaTitle' => ['group' => 'content', 'attribute' => 'title'],
|
||||||
|
|||||||
@@ -94,18 +94,40 @@ final class CtaBoxSidebarFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', false);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', false);
|
||||||
$html .= $this->buildSwitch('ctaShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('ctaShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
// show_on_pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="ctaShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// =============================================
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' </label>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <select id="ctaShowOnPages" class="form-select form-select-sm">';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="all"' . ($showOnPages === 'all' ? ' selected' : '') . '>Todos</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="posts"' . ($showOnPages === 'posts' ? ' selected' : '') . '>Solo posts</option>';
|
|
||||||
$html .= ' <option value="pages"' . ($showOnPages === 'pages' ? ' selected' : '') . '>Solo paginas</option>';
|
// Obtener valores de _page_visibility (grupo especial)
|
||||||
$html .= ' </select>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
// Grid 3 columnas según Design System
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -515,4 +537,29 @@ final class CtaBoxSidebarFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera un checkbox de visibilidad por tipo de pagina
|
||||||
|
*
|
||||||
|
* Sigue Design System: form-check-checkbox es obligatorio
|
||||||
|
*/
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, bool $checked): string
|
||||||
|
{
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class CtaLetsTalkFieldMapper implements FieldMapperInterface
|
|||||||
'ctaLetsTalkEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'ctaLetsTalkEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'ctaLetsTalkShowDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'ctaLetsTalkShowDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'ctaLetsTalkShowMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'ctaLetsTalkShowMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'ctaLetsTalkShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'ctaLetsTalkVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'ctaLetsTalkVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'ctaLetsTalkVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'ctaLetsTalkVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'ctaLetsTalkVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'ctaLetsTalkButtonText' => ['group' => 'content', 'attribute' => 'button_text'],
|
'ctaLetsTalkButtonText' => ['group' => 'content', 'attribute' => 'button_text'],
|
||||||
|
|||||||
@@ -120,16 +120,38 @@ final class CtaLetsTalkFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Select: Show on Pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'all');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-0">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="ctaLetsTalkShowOnPages" class="form-label small mb-1 fw-semibold">Mostrar en</label>';
|
// =============================================
|
||||||
$html .= ' <select id="ctaLetsTalkShowOnPages" name="visibility[show_on_pages]" class="form-select form-select-sm">';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las páginas</option>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <option value="home" ' . selected($showOnPages, 'home', false) . '>Solo página de inicio</option>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts individuales</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo páginas</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' </select>';
|
|
||||||
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaLetsTalkVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaLetsTalkVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaLetsTalkVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaLetsTalkVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaLetsTalkVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -447,4 +469,26 @@ final class CtaLetsTalkFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class CtaPostFieldMapper implements FieldMapperInterface
|
|||||||
'ctaPostEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'ctaPostEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'ctaPostShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'ctaPostShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'ctaPostShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'ctaPostShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'ctaPostShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'ctaPostVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'ctaPostVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'ctaPostVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'ctaPostVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'ctaPostVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'ctaPostTitle' => ['group' => 'content', 'attribute' => 'title'],
|
'ctaPostTitle' => ['group' => 'content', 'attribute' => 'title'],
|
||||||
|
|||||||
@@ -85,17 +85,38 @@ final class CtaPostFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
||||||
$html .= $this->buildSwitch('ctaPostShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('ctaPostShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// =============================================
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <label for="ctaPostShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
// =============================================
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' </label>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <select id="ctaPostShowOnPages" class="form-select form-select-sm">';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="all"' . ($showOnPages === 'all' ? ' selected' : '') . '>Todos</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="posts"' . ($showOnPages === 'posts' ? ' selected' : '') . '>Solo posts</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="pages"' . ($showOnPages === 'pages' ? ' selected' : '') . '>Solo paginas</option>';
|
|
||||||
$html .= ' </select>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaPostVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaPostVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaPostVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaPostVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('ctaPostVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -437,4 +458,26 @@ final class CtaPostFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class FeaturedImageFieldMapper implements FieldMapperInterface
|
|||||||
'featuredImageEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'featuredImageEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'featuredImageShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'featuredImageShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'featuredImageShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'featuredImageShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'featuredImageShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'featuredImageVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'featuredImageVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'featuredImageVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'featuredImageVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'featuredImageVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'featuredImageSize' => ['group' => 'content', 'attribute' => 'image_size'],
|
'featuredImageSize' => ['group' => 'content', 'attribute' => 'image_size'],
|
||||||
|
|||||||
@@ -100,17 +100,38 @@ final class FeaturedImageFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// =============================================
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <label for="featuredImageShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
// =============================================
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' </label>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <select id="featuredImageShowOnPages" class="form-select form-select-sm">';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las paginas</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts individuales</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo paginas</option>';
|
|
||||||
$html .= ' </select>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', false);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('featuredImageVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('featuredImageVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('featuredImageVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('featuredImageVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('featuredImageVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -119,6 +140,28 @@ final class FeaturedImageFormBuilder
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
private function buildContentGroup(string $componentId): string
|
private function buildContentGroup(string $componentId): string
|
||||||
{
|
{
|
||||||
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #1e3a5f;">';
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #1e3a5f;">';
|
||||||
|
|||||||
@@ -26,9 +26,15 @@ final class HeroFieldMapper implements FieldMapperInterface
|
|||||||
'heroEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'heroEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'heroShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'heroShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'heroShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'heroShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'heroShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
'heroIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
'heroIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||||
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'heroVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'heroVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'heroVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'heroVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'heroVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'heroShowCategories' => ['group' => 'content', 'attribute' => 'show_categories'],
|
'heroShowCategories' => ['group' => 'content', 'attribute' => 'show_categories'],
|
||||||
'heroShowBadgeIcon' => ['group' => 'content', 'attribute' => 'show_badge_icon'],
|
'heroShowBadgeIcon' => ['group' => 'content', 'attribute' => 'show_badge_icon'],
|
||||||
|
|||||||
@@ -102,18 +102,38 @@ final class HeroFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// =============================================
|
||||||
$html .= ' <div class="mb-2 mt-3">';
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <label for="heroShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
// =============================================
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' </label>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <select id="heroShowOnPages" class="form-select form-select-sm">';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las páginas</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts individuales</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo páginas</option>';
|
|
||||||
$html .= ' <option value="home" ' . selected($showOnPages, 'home', false) . '>Solo página de inicio</option>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', false);
|
||||||
$html .= ' </select>';
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('heroVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('heroVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('heroVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('heroVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('heroVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Switch: CSS Crítico
|
// Switch: CSS Crítico
|
||||||
@@ -427,4 +447,26 @@ final class HeroFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,16 @@ final class NavbarFieldMapper implements FieldMapperInterface
|
|||||||
'navbarEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'navbarEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'navbarShowMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'navbarShowMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'navbarShowDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'navbarShowDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'navbarShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
'navbarSticky' => ['group' => 'visibility', 'attribute' => 'sticky_enabled'],
|
'navbarSticky' => ['group' => 'visibility', 'attribute' => 'sticky_enabled'],
|
||||||
'navbarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
'navbarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||||
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'navbarVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'navbarVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'navbarVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'navbarVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'navbarVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
'navbarContainerType' => ['group' => 'layout', 'attribute' => 'container_type'],
|
'navbarContainerType' => ['group' => 'layout', 'attribute' => 'container_type'],
|
||||||
'navbarPaddingVertical' => ['group' => 'layout', 'attribute' => 'padding_vertical'],
|
'navbarPaddingVertical' => ['group' => 'layout', 'attribute' => 'padding_vertical'],
|
||||||
|
|||||||
@@ -105,16 +105,38 @@ final class NavbarFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Select: Show on Pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'all');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-2">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="navbarShowOnPages" class="form-label small mb-1 fw-semibold">Mostrar en</label>';
|
// =============================================
|
||||||
$html .= ' <select id="navbarShowOnPages" name="visibility[show_on_pages]" class="form-select form-select-sm">';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las páginas</option>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <option value="home" ' . selected($showOnPages, 'home', false) . '>Solo página de inicio</option>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts individuales</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo páginas</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' </select>';
|
|
||||||
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', true);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', true);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('navbarVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('navbarVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('navbarVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('navbarVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('navbarVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Switch: Sticky
|
// Switch: Sticky
|
||||||
@@ -527,4 +549,26 @@ final class NavbarFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ final class RelatedPostFieldMapper implements FieldMapperInterface
|
|||||||
'relatedPostEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'relatedPostEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'relatedPostShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'relatedPostShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'relatedPostShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'relatedPostShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'relatedPostShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'relatedPostVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'relatedPostVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'relatedPostVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'relatedPostVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'relatedPostVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'relatedPostSectionTitle' => ['group' => 'content', 'attribute' => 'section_title'],
|
'relatedPostSectionTitle' => ['group' => 'content', 'attribute' => 'section_title'],
|
||||||
|
|||||||
@@ -86,17 +86,38 @@ final class RelatedPostFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
||||||
$html .= $this->buildSwitch('relatedPostShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('relatedPostShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// =============================================
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <label for="relatedPostShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
// =============================================
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' </label>';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' <select id="relatedPostShowOnPages" class="form-select form-select-sm">';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <option value="all"' . ($showOnPages === 'all' ? ' selected' : '') . '>Todos</option>';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="posts"' . ($showOnPages === 'posts' ? ' selected' : '') . '>Solo posts</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="pages"' . ($showOnPages === 'pages' ? ' selected' : '') . '>Solo paginas</option>';
|
|
||||||
$html .= ' </select>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('relatedPostVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('relatedPostVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('relatedPostVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('relatedPostVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('relatedPostVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -498,4 +519,26 @@ final class RelatedPostFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class SocialShareFieldMapper implements FieldMapperInterface
|
|||||||
'socialShareEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'socialShareEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'socialShareShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'socialShareShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'socialShareShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'socialShareShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'socialShareShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'socialShareVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'socialShareVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'socialShareVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'socialShareVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'socialShareVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'socialShareShowLabel' => ['group' => 'content', 'attribute' => 'show_label'],
|
'socialShareShowLabel' => ['group' => 'content', 'attribute' => 'show_label'],
|
||||||
|
|||||||
@@ -94,18 +94,38 @@ final class SocialShareFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', true);
|
||||||
$html .= $this->buildSwitch('socialShareShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('socialShareShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
// show_on_pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="socialShareShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// =============================================
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' </label>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <select id="socialShareShowOnPages" class="form-select form-select-sm">';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="all"' . ($showOnPages === 'all' ? ' selected' : '') . '>Todos</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="posts"' . ($showOnPages === 'posts' ? ' selected' : '') . '>Solo posts</option>';
|
|
||||||
$html .= ' <option value="pages"' . ($showOnPages === 'pages' ? ' selected' : '') . '>Solo paginas</option>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
$html .= ' </select>';
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('socialShareVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('socialShareVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('socialShareVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('socialShareVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('socialShareVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -526,4 +546,26 @@ final class SocialShareFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ final class TableOfContentsFieldMapper implements FieldMapperInterface
|
|||||||
'tocEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'tocEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'tocShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'tocShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'tocShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'tocShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'tocShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'tocVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'tocVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'tocVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'tocVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'tocVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'tocTitle' => ['group' => 'content', 'attribute' => 'title'],
|
'tocTitle' => ['group' => 'content', 'attribute' => 'title'],
|
||||||
|
|||||||
@@ -94,18 +94,38 @@ final class TableOfContentsFormBuilder
|
|||||||
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', false);
|
$showOnMobile = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_mobile', false);
|
||||||
$html .= $this->buildSwitch('tocShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
$html .= $this->buildSwitch('tocShowOnMobile', 'Mostrar en movil', 'bi-phone', $showOnMobile);
|
||||||
|
|
||||||
// show_on_pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'posts');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-0 mt-3">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="tocShowOnPages" class="form-label small mb-1 fw-semibold">';
|
// =============================================
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' </label>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <select id="tocShowOnPages" class="form-select form-select-sm">';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las paginas</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts</option>';
|
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo paginas</option>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', false);
|
||||||
$html .= ' </select>';
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('tocVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('tocVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('tocVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('tocVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('tocVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -585,4 +605,26 @@ final class TableOfContentsFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,15 @@ final class TopNotificationBarFieldMapper implements FieldMapperInterface
|
|||||||
'topBarEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
'topBarEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
'topBarShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
'topBarShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
'topBarShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
'topBarShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
'topBarShowOnPages' => ['group' => 'visibility', 'attribute' => 'show_on_pages'],
|
|
||||||
'topBarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
'topBarIsCritical' => ['group' => 'visibility', 'attribute' => 'is_critical'],
|
||||||
|
|
||||||
|
// Page Visibility (grupo especial _page_visibility)
|
||||||
|
'topBarVisibilityHome' => ['group' => '_page_visibility', 'attribute' => 'show_on_home'],
|
||||||
|
'topBarVisibilityPosts' => ['group' => '_page_visibility', 'attribute' => 'show_on_posts'],
|
||||||
|
'topBarVisibilityPages' => ['group' => '_page_visibility', 'attribute' => 'show_on_pages'],
|
||||||
|
'topBarVisibilityArchives' => ['group' => '_page_visibility', 'attribute' => 'show_on_archives'],
|
||||||
|
'topBarVisibilitySearch' => ['group' => '_page_visibility', 'attribute' => 'show_on_search'],
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
'topBarIconClass' => ['group' => 'content', 'attribute' => 'icon_class'],
|
'topBarIconClass' => ['group' => 'content', 'attribute' => 'icon_class'],
|
||||||
'topBarLabelText' => ['group' => 'content', 'attribute' => 'label_text'],
|
'topBarLabelText' => ['group' => 'content', 'attribute' => 'label_text'],
|
||||||
|
|||||||
@@ -105,19 +105,38 @@ final class TopNotificationBarFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Select: Show on Pages
|
// =============================================
|
||||||
$showOnPages = $this->renderer->getFieldValue($componentId, 'visibility', 'show_on_pages', 'all');
|
// Checkboxes de visibilidad por tipo de página
|
||||||
$html .= ' <div class="mb-2 mt-3">';
|
// Grupo especial: _page_visibility
|
||||||
$html .= ' <label for="topBarShowOnPages" class="form-label small mb-1 fw-semibold" style="color: #495057;">';
|
// =============================================
|
||||||
$html .= ' <i class="bi bi-file-earmark-text me-1" style="color: #FF8600;"></i>';
|
$html .= ' <hr class="my-3">';
|
||||||
$html .= ' Mostrar en';
|
$html .= ' <p class="small fw-semibold mb-2">';
|
||||||
$html .= ' </label>';
|
$html .= ' <i class="bi bi-eye me-1" style="color: #FF8600;"></i>';
|
||||||
$html .= ' <select id="topBarShowOnPages" class="form-select form-select-sm">';
|
$html .= ' Mostrar en tipos de pagina';
|
||||||
$html .= ' <option value="all" ' . selected($showOnPages, 'all', false) . '>Todas las páginas</option>';
|
$html .= ' </p>';
|
||||||
$html .= ' <option value="home" ' . selected($showOnPages, 'home', false) . '>Solo página de inicio</option>';
|
|
||||||
$html .= ' <option value="posts" ' . selected($showOnPages, 'posts', false) . '>Solo posts individuales</option>';
|
$showOnHome = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_home', true);
|
||||||
$html .= ' <option value="pages" ' . selected($showOnPages, 'pages', false) . '>Solo páginas</option>';
|
$showOnPosts = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_posts', true);
|
||||||
$html .= ' </select>';
|
$showOnPages = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_pages', true);
|
||||||
|
$showOnArchives = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_archives', false);
|
||||||
|
$showOnSearch = $this->renderer->getFieldValue($componentId, '_page_visibility', 'show_on_search', false);
|
||||||
|
|
||||||
|
$html .= ' <div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('topBarVisibilityHome', 'Home', 'bi-house', $showOnHome);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('topBarVisibilityPosts', 'Posts', 'bi-file-earmark-text', $showOnPosts);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('topBarVisibilityPages', 'Paginas', 'bi-file-earmark', $showOnPages);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('topBarVisibilityArchives', 'Archivos', 'bi-archive', $showOnArchives);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
|
$html .= $this->buildPageVisibilityCheckbox('topBarVisibilitySearch', 'Busqueda', 'bi-search', $showOnSearch);
|
||||||
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// Switch: CSS Crítico
|
// Switch: CSS Crítico
|
||||||
@@ -319,4 +338,26 @@ final class TopNotificationBarFormBuilder
|
|||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildPageVisibilityCheckbox(string $id, string $label, string $icon, mixed $checked): string
|
||||||
|
{
|
||||||
|
$checked = $checked === true || $checked === '1' || $checked === 1;
|
||||||
|
|
||||||
|
$html = ' <div class="form-check form-check-checkbox mb-2">';
|
||||||
|
$html .= sprintf(
|
||||||
|
' <input class="form-check-input" type="checkbox" id="%s" %s>',
|
||||||
|
esc_attr($id),
|
||||||
|
$checked ? 'checked' : ''
|
||||||
|
);
|
||||||
|
$html .= sprintf(
|
||||||
|
' <label class="form-check-label small" for="%s">',
|
||||||
|
esc_attr($id)
|
||||||
|
);
|
||||||
|
$html .= sprintf(' <i class="bi %s me-1" style="color: #FF8600;"></i>', esc_attr($icon));
|
||||||
|
$html .= sprintf(' %s', esc_html($label));
|
||||||
|
$html .= ' </label>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,10 +182,72 @@
|
|||||||
}, CONFIG.timeout);
|
}, CONFIG.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activa slots de AdSense insertados dinamicamente
|
||||||
|
* Escucha el evento 'roi-adsense-activate' disparado por otros scripts
|
||||||
|
*/
|
||||||
|
function setupDynamicAdsListener() {
|
||||||
|
window.addEventListener('roi-adsense-activate', function() {
|
||||||
|
debugLog('Evento roi-adsense-activate recibido');
|
||||||
|
|
||||||
|
// Si AdSense aun no ha cargado, forzar carga ahora
|
||||||
|
if (!adsenseLoaded) {
|
||||||
|
debugLog('AdSense no cargado, forzando carga...');
|
||||||
|
loadAdSense();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdSense ya cargado - activar nuevos slots
|
||||||
|
debugLog('Activando nuevos slots dinamicos...');
|
||||||
|
activateDynamicSlots();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activa slots de AdSense que fueron insertados despues de la carga inicial
|
||||||
|
*/
|
||||||
|
function activateDynamicSlots() {
|
||||||
|
// Buscar scripts de push que aun no han sido ejecutados
|
||||||
|
var pendingPushScripts = document.querySelectorAll('script[data-adsense-push][type="text/plain"]');
|
||||||
|
|
||||||
|
if (pendingPushScripts.length === 0) {
|
||||||
|
debugLog('No hay slots pendientes por activar');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLog('Activando ' + pendingPushScripts.length + ' slot(s) dinamico(s)');
|
||||||
|
|
||||||
|
// Asegurar que adsbygoogle existe
|
||||||
|
window.adsbygoogle = window.adsbygoogle || [];
|
||||||
|
|
||||||
|
pendingPushScripts.forEach(function(oldScript) {
|
||||||
|
try {
|
||||||
|
// Crear nuevo script ejecutable
|
||||||
|
var newScript = document.createElement('script');
|
||||||
|
newScript.type = 'text/javascript';
|
||||||
|
newScript.innerHTML = oldScript.innerHTML;
|
||||||
|
|
||||||
|
// Reemplazar el placeholder con el script real
|
||||||
|
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||||
|
} catch (e) {
|
||||||
|
debugLog('Error activando slot: ' + e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inicializa el cargador retrasado de AdSense
|
* Inicializa el cargador retrasado de AdSense
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
|
// =========================================================================
|
||||||
|
// NUEVO: Siempre configurar listener para ads dinamicos
|
||||||
|
// IMPORTANTE: Esto debe ejecutarse ANTES del early return
|
||||||
|
// porque los ads dinamicos pueden necesitar activarse aunque
|
||||||
|
// el delay global este deshabilitado
|
||||||
|
// =========================================================================
|
||||||
|
setupDynamicAdsListener();
|
||||||
|
debugLog('Listener para ads dinamicos configurado');
|
||||||
|
|
||||||
// Verificar si el retardo de AdSense está habilitado
|
// Verificar si el retardo de AdSense está habilitado
|
||||||
if (!window.roiAdsenseDelayed) {
|
if (!window.roiAdsenseDelayed) {
|
||||||
debugLog('Retardo de AdSense no habilitado');
|
debugLog('Retardo de AdSense no habilitado');
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ function roi_get_featured_image($post_id = null, $size = 'roi-featured-large', $
|
|||||||
return ''; // No placeholder - retornar vacío
|
return ''; // No placeholder - retornar vacío
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista, no solo el attachment ID
|
||||||
|
$thumbnailId = get_post_thumbnail_id($post_id);
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return ''; // Archivo no existe en servidor
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener tipo de post
|
// Obtener tipo de post
|
||||||
$post_type = get_post_type($post_id);
|
$post_type = get_post_type($post_id);
|
||||||
|
|
||||||
@@ -145,6 +152,13 @@ function roi_get_post_thumbnail($post_id = null, $with_link = true) {
|
|||||||
return ''; // No placeholder - retornar vacío
|
return ''; // No placeholder - retornar vacío
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista
|
||||||
|
$thumbnailId = get_post_thumbnail_id($post_id);
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener la imagen con clases Bootstrap
|
// Obtener la imagen con clases Bootstrap
|
||||||
$image = get_the_post_thumbnail($post_id, 'roi-featured-medium', array(
|
$image = get_the_post_thumbnail($post_id, 'roi-featured-medium', array(
|
||||||
'class' => 'img-fluid post-thumbnail',
|
'class' => 'img-fluid post-thumbnail',
|
||||||
@@ -216,6 +230,13 @@ function roi_get_post_thumbnail_small($post_id = null, $with_link = true) {
|
|||||||
return ''; // No placeholder - retornar vacío
|
return ''; // No placeholder - retornar vacío
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista
|
||||||
|
$thumbnailId = get_post_thumbnail_id($post_id);
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener la imagen
|
// Obtener la imagen
|
||||||
$image = get_the_post_thumbnail($post_id, 'roi-thumbnail', array(
|
$image = get_the_post_thumbnail($post_id, 'roi-thumbnail', array(
|
||||||
'class' => 'img-fluid post-thumbnail-small',
|
'class' => 'img-fluid post-thumbnail-small',
|
||||||
@@ -287,6 +308,13 @@ function roi_should_show_featured_image($post_id = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista
|
||||||
|
$thumbnailId = get_post_thumbnail_id($post_id);
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener tipo de post
|
// Obtener tipo de post
|
||||||
$post_type = get_post_type($post_id);
|
$post_type = get_post_type($post_id);
|
||||||
|
|
||||||
@@ -338,6 +366,13 @@ function roi_get_featured_image_url($post_id = null, $size = 'roi-featured-large
|
|||||||
return ''; // No placeholder - retornar vacío
|
return ''; // No placeholder - retornar vacío
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista
|
||||||
|
$thumbnailId = get_post_thumbnail_id($post_id);
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener URL de la imagen
|
// Obtener URL de la imagen
|
||||||
$image_url = get_the_post_thumbnail_url($post_id, $size);
|
$image_url = get_the_post_thumbnail_url($post_id, $size);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\ContactForm\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ContactFormRenderer - Renderiza formulario de contacto con webhook
|
* ContactFormRenderer - Renderiza formulario de contacto con webhook
|
||||||
@@ -22,6 +23,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class ContactFormRenderer implements RendererInterface
|
final class ContactFormRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'contact-form';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private CSSGeneratorInterface $cssGenerator
|
private CSSGeneratorInterface $cssGenerator
|
||||||
) {}
|
) {}
|
||||||
@@ -34,7 +37,7 @@ final class ContactFormRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ final class ContactFormRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'contact-form';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -76,22 +79,6 @@ final class ContactFormRenderer implements RendererInterface
|
|||||||
return $value === true || $value === '1' || $value === 1;
|
return $value === true || $value === '1' || $value === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'all';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getVisibilityClass(array $data): ?string
|
private function getVisibilityClass(array $data): ?string
|
||||||
{
|
{
|
||||||
$showDesktop = $data['visibility']['show_on_desktop'] ?? true;
|
$showDesktop = $data['visibility']['show_on_desktop'] ?? true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\CtaBoxSidebar\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CtaBoxSidebarRenderer - Renderiza caja CTA en sidebar
|
* CtaBoxSidebarRenderer - Renderiza caja CTA en sidebar
|
||||||
@@ -27,6 +28,12 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class CtaBoxSidebarRenderer implements RendererInterface
|
final class CtaBoxSidebarRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Nombre del componente para visibilidad
|
||||||
|
* Evita strings hardcodeados y facilita mantenimiento
|
||||||
|
*/
|
||||||
|
private const COMPONENT_NAME = 'cta-box-sidebar';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private CSSGeneratorInterface $cssGenerator
|
private CSSGeneratorInterface $cssGenerator
|
||||||
) {}
|
) {}
|
||||||
@@ -39,7 +46,8 @@ final class CtaBoxSidebarRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
// Evaluar visibilidad por tipo de página (usa Helper, NO cambia constructor)
|
||||||
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +60,7 @@ final class CtaBoxSidebarRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'cta-box-sidebar';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -60,22 +68,6 @@ final class CtaBoxSidebarRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCSS(array $data): string
|
private function generateCSS(array $data): string
|
||||||
{
|
{
|
||||||
$colors = $data['colors'] ?? [];
|
$colors = $data['colors'] ?? [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\CtaLetsTalk\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CtaLetsTalkRenderer
|
* Class CtaLetsTalkRenderer
|
||||||
@@ -34,6 +35,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class CtaLetsTalkRenderer implements RendererInterface
|
final class CtaLetsTalkRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'cta-lets-talk';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +57,7 @@ final class CtaLetsTalkRenderer implements RendererInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validar visibilidad por página
|
// Validar visibilidad por página
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ final class CtaLetsTalkRenderer implements RendererInterface
|
|||||||
*/
|
*/
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'cta-lets-talk';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,25 +94,6 @@ final class CtaLetsTalkRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verificar si debe mostrarse en la página actual
|
|
||||||
*
|
|
||||||
* @param array $data Datos del componente
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'all';
|
|
||||||
|
|
||||||
return match ($showOn) {
|
|
||||||
'all' => true,
|
|
||||||
'home' => is_front_page(),
|
|
||||||
'posts' => is_single(),
|
|
||||||
'pages' => is_page(),
|
|
||||||
default => true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calcular clases de visibilidad responsive
|
* Calcular clases de visibilidad responsive
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\CtaPost\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CtaPostRenderer - Renderiza CTA promocional debajo del contenido
|
* CtaPostRenderer - Renderiza CTA promocional debajo del contenido
|
||||||
@@ -22,6 +23,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class CtaPostRenderer implements RendererInterface
|
final class CtaPostRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'cta-post';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private CSSGeneratorInterface $cssGenerator
|
private CSSGeneratorInterface $cssGenerator
|
||||||
) {}
|
) {}
|
||||||
@@ -34,7 +37,7 @@ final class CtaPostRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ final class CtaPostRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'cta-post';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -55,22 +58,6 @@ final class CtaPostRenderer implements RendererInterface
|
|||||||
return $value === true || $value === '1' || $value === 1;
|
return $value === true || $value === '1' || $value === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCSS(array $data): string
|
private function generateCSS(array $data): string
|
||||||
{
|
{
|
||||||
$colors = $data['colors'] ?? [];
|
$colors = $data['colors'] ?? [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\FeaturedImage\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FeaturedImageRenderer - Renderiza la imagen destacada del post
|
* FeaturedImageRenderer - Renderiza la imagen destacada del post
|
||||||
@@ -27,6 +28,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class FeaturedImageRenderer implements RendererInterface
|
final class FeaturedImageRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'featured-image';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private CSSGeneratorInterface $cssGenerator
|
private CSSGeneratorInterface $cssGenerator
|
||||||
) {}
|
) {}
|
||||||
@@ -39,7 +42,7 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'featured-image';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -71,25 +74,24 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hasPostThumbnail(): bool
|
private function hasPostThumbnail(): bool
|
||||||
{
|
{
|
||||||
return is_singular() && has_post_thumbnail();
|
if (!is_singular() || !has_post_thumbnail()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que el archivo físico exista, no solo el attachment ID
|
||||||
|
$thumbnailId = get_post_thumbnail_id();
|
||||||
|
if (!$thumbnailId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = get_attached_file($thumbnailId);
|
||||||
|
if (empty($filePath) || !file_exists($filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\Hero\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class HeroRenderer
|
* Class HeroRenderer
|
||||||
@@ -33,6 +34,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class HeroRenderer implements RendererInterface
|
final class HeroRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'hero';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +51,7 @@ final class HeroRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +71,7 @@ final class HeroRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'hero';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -76,24 +79,6 @@ final class HeroRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'home':
|
|
||||||
return is_front_page() || is_home();
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generar CSS usando CSSGeneratorService
|
* Generar CSS usando CSSGeneratorService
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\Navbar\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
use Walker_Nav_Menu;
|
use Walker_Nav_Menu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +29,8 @@ use Walker_Nav_Menu;
|
|||||||
*/
|
*/
|
||||||
final class NavbarRenderer implements RendererInterface
|
final class NavbarRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'navbar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||||
*/
|
*/
|
||||||
@@ -43,6 +46,10 @@ final class NavbarRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
$html = $this->buildMenu($data);
|
$html = $this->buildMenu($data);
|
||||||
|
|
||||||
// Si is_critical=true, CSS ya fue inyectado en <head> por CriticalCSSService
|
// Si is_critical=true, CSS ya fue inyectado en <head> por CriticalCSSService
|
||||||
@@ -281,7 +288,7 @@ final class NavbarRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'navbar';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\RelatedPost\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RelatedPostRenderer - Renderiza seccion de posts relacionados
|
* RelatedPostRenderer - Renderiza seccion de posts relacionados
|
||||||
@@ -22,6 +23,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class RelatedPostRenderer implements RendererInterface
|
final class RelatedPostRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'related-post';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private CSSGeneratorInterface $cssGenerator
|
private CSSGeneratorInterface $cssGenerator
|
||||||
) {}
|
) {}
|
||||||
@@ -34,7 +37,7 @@ final class RelatedPostRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +54,7 @@ final class RelatedPostRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'related-post';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -60,22 +63,6 @@ final class RelatedPostRenderer implements RendererInterface
|
|||||||
return $value === true || $value === '1' || $value === 1;
|
return $value === true || $value === '1' || $value === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getVisibilityClass(array $data): ?string
|
private function getVisibilityClass(array $data): ?string
|
||||||
{
|
{
|
||||||
$showDesktop = $data['visibility']['show_on_desktop'] ?? true;
|
$showDesktop = $data['visibility']['show_on_desktop'] ?? true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\SocialShare\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SocialShareRenderer - Renderiza botones de compartir en redes sociales
|
* SocialShareRenderer - Renderiza botones de compartir en redes sociales
|
||||||
@@ -27,6 +28,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class SocialShareRenderer implements RendererInterface
|
final class SocialShareRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'social-share';
|
||||||
|
|
||||||
private const NETWORKS = [
|
private const NETWORKS = [
|
||||||
'facebook' => [
|
'facebook' => [
|
||||||
'field' => 'show_facebook',
|
'field' => 'show_facebook',
|
||||||
@@ -84,7 +87,7 @@ final class SocialShareRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +99,7 @@ final class SocialShareRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'social-share';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -105,22 +108,6 @@ final class SocialShareRenderer implements RendererInterface
|
|||||||
return $value === true || $value === '1' || $value === 1;
|
return $value === true || $value === '1' || $value === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCSS(array $data): string
|
private function generateCSS(array $data): string
|
||||||
{
|
{
|
||||||
$colors = $data['colors'] ?? [];
|
$colors = $data['colors'] ?? [];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\TableOfContents\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use DOMXPath;
|
use DOMXPath;
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ use DOMXPath;
|
|||||||
*/
|
*/
|
||||||
final class TableOfContentsRenderer implements RendererInterface
|
final class TableOfContentsRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'table-of-contents';
|
||||||
|
|
||||||
private array $headingCounter = [];
|
private array $headingCounter = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -44,7 +47,7 @@ final class TableOfContentsRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ final class TableOfContentsRenderer implements RendererInterface
|
|||||||
|
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'table-of-contents';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isEnabled(array $data): bool
|
private function isEnabled(array $data): bool
|
||||||
@@ -71,22 +74,6 @@ final class TableOfContentsRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
||||||
|
|
||||||
switch ($showOn) {
|
|
||||||
case 'all':
|
|
||||||
return true;
|
|
||||||
case 'posts':
|
|
||||||
return is_single();
|
|
||||||
case 'pages':
|
|
||||||
return is_page();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getVisibilityClasses(bool $desktop, bool $mobile): ?string
|
private function getVisibilityClasses(bool $desktop, bool $mobile): ?string
|
||||||
{
|
{
|
||||||
if (!$desktop && !$mobile) {
|
if (!$desktop && !$mobile) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ROITheme\Public\TopNotificationBar\Infrastructure\Ui;
|
|||||||
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
||||||
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
use ROITheme\Shared\Domain\Entities\Component;
|
use ROITheme\Shared\Domain\Entities\Component;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class TopNotificationBarRenderer
|
* Class TopNotificationBarRenderer
|
||||||
@@ -34,6 +35,8 @@ use ROITheme\Shared\Domain\Entities\Component;
|
|||||||
*/
|
*/
|
||||||
final class TopNotificationBarRenderer implements RendererInterface
|
final class TopNotificationBarRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
|
private const COMPONENT_NAME = 'top-notification-bar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
* @param CSSGeneratorInterface $cssGenerator Servicio de generación de CSS
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +57,7 @@ final class TopNotificationBarRenderer implements RendererInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validar visibilidad por página
|
// Validar visibilidad por página
|
||||||
if (!$this->shouldShowOnCurrentPage($data)) {
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +81,7 @@ final class TopNotificationBarRenderer implements RendererInterface
|
|||||||
*/
|
*/
|
||||||
public function supports(string $componentType): bool
|
public function supports(string $componentType): bool
|
||||||
{
|
{
|
||||||
return $componentType === 'top-notification-bar';
|
return $componentType === self::COMPONENT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,46 +95,6 @@ final class TopNotificationBarRenderer implements RendererInterface
|
|||||||
return ($data['visibility']['is_enabled'] ?? false) === true;
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verificar si debe mostrarse en la página actual
|
|
||||||
*
|
|
||||||
* @param array $data Datos del componente
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function shouldShowOnCurrentPage(array $data): bool
|
|
||||||
{
|
|
||||||
$showOn = $data['visibility']['show_on_pages'] ?? 'all';
|
|
||||||
|
|
||||||
return match ($showOn) {
|
|
||||||
'all' => true,
|
|
||||||
'home' => is_front_page(),
|
|
||||||
'posts' => is_single(),
|
|
||||||
'pages' => is_page(),
|
|
||||||
'custom' => $this->isInCustomPages($data),
|
|
||||||
default => true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verificar si está en páginas personalizadas
|
|
||||||
*
|
|
||||||
* @param array $data Datos del componente
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isInCustomPages(array $data): bool
|
|
||||||
{
|
|
||||||
$pageIds = $data['visibility']['custom_page_ids'] ?? '';
|
|
||||||
|
|
||||||
if (empty($pageIds)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$allowedIds = array_map('trim', explode(',', $pageIds));
|
|
||||||
$currentId = (string) get_the_ID();
|
|
||||||
|
|
||||||
return in_array($currentId, $allowedIds, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verificar si el componente fue dismissed por el usuario
|
* Verificar si el componente fue dismissed por el usuario
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -110,3 +110,14 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
FIX: Legacy wrapper with padding-top
|
||||||
|
Removes duplicate aspect-ratio from parent
|
||||||
|
containers that use the old padding-top trick
|
||||||
|
(prevents double spacing above videos)
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
div[style*="padding-top"]:has(> .youtube-facade) {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -437,6 +437,72 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"search_results": {
|
||||||
|
"label": "Resultados de Busqueda (ROI APU Search)",
|
||||||
|
"priority": 73,
|
||||||
|
"fields": {
|
||||||
|
"search_ads_enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Activar ads en busqueda",
|
||||||
|
"default": false,
|
||||||
|
"editable": true,
|
||||||
|
"description": "Insertar anuncios en resultados del buscador APU"
|
||||||
|
},
|
||||||
|
"search_top_ad_enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Anuncio fijo arriba",
|
||||||
|
"default": true,
|
||||||
|
"editable": true,
|
||||||
|
"description": "Mostrar anuncio debajo del campo de busqueda"
|
||||||
|
},
|
||||||
|
"search_top_ad_format": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Formato anuncio superior",
|
||||||
|
"default": "auto",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["auto", "display", "in-article"]
|
||||||
|
},
|
||||||
|
"search_between_enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Anuncios entre resultados",
|
||||||
|
"default": true,
|
||||||
|
"editable": true
|
||||||
|
},
|
||||||
|
"search_between_max": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Maximo anuncios entre resultados",
|
||||||
|
"default": "1",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["1", "2", "3"],
|
||||||
|
"description": "Maximo 3 por politicas AdSense"
|
||||||
|
},
|
||||||
|
"search_between_format": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Formato entre resultados",
|
||||||
|
"default": "in-article",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["auto", "in-article", "autorelaxed"]
|
||||||
|
},
|
||||||
|
"search_between_position": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Posicion de anuncios",
|
||||||
|
"default": "random",
|
||||||
|
"editable": true,
|
||||||
|
"options": {
|
||||||
|
"random": "Aleatorio",
|
||||||
|
"fixed": "Fijo (cada N resultados)",
|
||||||
|
"first_half": "Primera mitad"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search_between_every": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Cada N resultados (si es fijo)",
|
||||||
|
"default": "5",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["3", "4", "5", "6", "7", "8", "10"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"label": "Ubicaciones Archivos/Globales",
|
"label": "Ubicaciones Archivos/Globales",
|
||||||
"priority": 80,
|
"priority": 80,
|
||||||
|
|||||||
@@ -27,14 +27,6 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "all",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,14 +27,6 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,20 +29,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el botón en pantallas móviles (<992px). Por defecto oculto para ahorrar espacio en navbar móvil"
|
"description": "Muestra el botón en pantallas móviles (<992px). Por defecto oculto para ahorrar espacio en navbar móvil"
|
||||||
},
|
},
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "all",
|
|
||||||
"editable": true,
|
|
||||||
"required": true,
|
|
||||||
"options": {
|
|
||||||
"all": "Todas las páginas",
|
|
||||||
"home": "Solo página de inicio",
|
|
||||||
"posts": "Solo posts individuales",
|
|
||||||
"pages": "Solo páginas"
|
|
||||||
},
|
|
||||||
"description": "Define en qué páginas se mostrará el botón"
|
|
||||||
},
|
|
||||||
"is_critical": {
|
"is_critical": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "CSS Crítico",
|
"label": "CSS Crítico",
|
||||||
|
|||||||
@@ -27,14 +27,6 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,19 +30,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "Muestra la imagen en dispositivos moviles (<768px)"
|
"description": "Muestra la imagen en dispositivos moviles (<768px)"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"required": true,
|
|
||||||
"options": {
|
|
||||||
"all": "Todas las paginas",
|
|
||||||
"posts": "Solo posts individuales",
|
|
||||||
"pages": "Solo paginas"
|
|
||||||
},
|
|
||||||
"description": "Define en que tipo de contenido se muestra la imagen"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,20 +31,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"description": "Muestra el hero en dispositivos móviles (<768px)"
|
"description": "Muestra el hero en dispositivos móviles (<768px)"
|
||||||
},
|
},
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"required": true,
|
|
||||||
"options": {
|
|
||||||
"all": "Todas las páginas",
|
|
||||||
"posts": "Solo posts individuales",
|
|
||||||
"pages": "Solo páginas",
|
|
||||||
"home": "Solo página de inicio"
|
|
||||||
},
|
|
||||||
"description": "Define en qué tipo de contenido se mostrará el hero"
|
|
||||||
},
|
|
||||||
"is_critical": {
|
"is_critical": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "CSS Crítico",
|
"label": "CSS Crítico",
|
||||||
|
|||||||
@@ -29,19 +29,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el menú en dispositivos de escritorio (≥768px)"
|
"description": "Muestra el menú en dispositivos de escritorio (≥768px)"
|
||||||
},
|
},
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "all",
|
|
||||||
"editable": true,
|
|
||||||
"options": {
|
|
||||||
"all": "Todas las páginas",
|
|
||||||
"home": "Solo página de inicio",
|
|
||||||
"posts": "Solo posts individuales",
|
|
||||||
"pages": "Solo páginas"
|
|
||||||
},
|
|
||||||
"description": "Define en qué páginas se muestra el navbar"
|
|
||||||
},
|
|
||||||
"sticky_enabled": {
|
"sticky_enabled": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Navbar fijo (sticky)",
|
"label": "Navbar fijo (sticky)",
|
||||||
|
|||||||
@@ -27,14 +27,6 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,14 +27,6 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,14 +28,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Muestra el componente en pantallas < 992px"
|
"description": "Muestra el componente en pantallas < 992px"
|
||||||
},
|
},
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "posts",
|
|
||||||
"editable": true,
|
|
||||||
"options": ["all", "posts", "pages"],
|
|
||||||
"description": "Tipos de contenido donde se muestra"
|
|
||||||
},
|
|
||||||
"is_critical": {
|
"is_critical": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "CSS Crítico",
|
"label": "CSS Crítico",
|
||||||
|
|||||||
@@ -15,20 +15,6 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"description": "Activa o desactiva la barra de notificación superior"
|
"description": "Activa o desactiva la barra de notificación superior"
|
||||||
},
|
},
|
||||||
"show_on_pages": {
|
|
||||||
"type": "select",
|
|
||||||
"label": "Mostrar en",
|
|
||||||
"default": "all",
|
|
||||||
"editable": true,
|
|
||||||
"required": true,
|
|
||||||
"options": {
|
|
||||||
"all": "Todas las páginas",
|
|
||||||
"home": "Solo página de inicio",
|
|
||||||
"posts": "Solo posts individuales",
|
|
||||||
"pages": "Solo páginas"
|
|
||||||
},
|
|
||||||
"description": "Define en qué páginas se mostrará la barra"
|
|
||||||
},
|
|
||||||
"show_on_desktop": {
|
"show_on_desktop": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Mostrar en desktop",
|
"label": "Mostrar en desktop",
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Application\UseCases\EvaluatePageVisibility;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageTypeDetectorInterface;
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageVisibilityRepositoryInterface;
|
||||||
|
use ROITheme\Shared\Domain\Constants\VisibilityDefaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caso de uso: Evaluar si un componente debe mostrarse en la página actual
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Application\UseCases\EvaluatePageVisibility
|
||||||
|
*/
|
||||||
|
final class EvaluatePageVisibilityUseCase
|
||||||
|
{
|
||||||
|
// NOTA: Usa VisibilityDefaults::DEFAULT_VISIBILITY para cumplir DRY
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly PageTypeDetectorInterface $pageTypeDetector,
|
||||||
|
private readonly PageVisibilityRepositoryInterface $visibilityRepository
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evalúa si el componente debe mostrarse en la página actual
|
||||||
|
*/
|
||||||
|
public function execute(string $componentName): bool
|
||||||
|
{
|
||||||
|
$config = $this->visibilityRepository->getVisibilityConfig($componentName);
|
||||||
|
|
||||||
|
if (empty($config)) {
|
||||||
|
// Usar constante compartida (DRY)
|
||||||
|
$config = VisibilityDefaults::DEFAULT_VISIBILITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageType = $this->pageTypeDetector->detect();
|
||||||
|
$visibilityField = $pageType->toVisibilityField();
|
||||||
|
|
||||||
|
return $this->toBool($config[$visibilityField] ?? true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toBool(mixed $value): bool
|
||||||
|
{
|
||||||
|
return $value === true || $value === '1' || $value === 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Shared/Domain/Constants/VisibilityDefaults.php
Normal file
45
Shared/Domain/Constants/VisibilityDefaults.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Domain\Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constantes de visibilidad por defecto para componentes
|
||||||
|
*
|
||||||
|
* Centraliza los valores por defecto para cumplir con DRY.
|
||||||
|
* Usado por:
|
||||||
|
* - EvaluatePageVisibilityUseCase (cuando no hay config en BD)
|
||||||
|
* - MigratePageVisibilityService (para crear registros iniciales)
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Domain\Constants
|
||||||
|
*/
|
||||||
|
final class VisibilityDefaults
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Configuración de visibilidad por defecto para nuevos componentes
|
||||||
|
*
|
||||||
|
* - Home: SÍ mostrar (página principal)
|
||||||
|
* - Posts: SÍ mostrar (artículos del blog)
|
||||||
|
* - Pages: SÍ mostrar (páginas estáticas)
|
||||||
|
* - Archives: NO mostrar (listados de categorías/tags)
|
||||||
|
* - Search: NO mostrar (resultados de búsqueda)
|
||||||
|
*/
|
||||||
|
public const DEFAULT_VISIBILITY = [
|
||||||
|
'show_on_home' => true,
|
||||||
|
'show_on_posts' => true,
|
||||||
|
'show_on_pages' => true,
|
||||||
|
'show_on_archives' => false,
|
||||||
|
'show_on_search' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista de campos de visibilidad válidos
|
||||||
|
*/
|
||||||
|
public const VISIBILITY_FIELDS = [
|
||||||
|
'show_on_home',
|
||||||
|
'show_on_posts',
|
||||||
|
'show_on_pages',
|
||||||
|
'show_on_archives',
|
||||||
|
'show_on_search',
|
||||||
|
];
|
||||||
|
}
|
||||||
25
Shared/Domain/Contracts/PageTypeDetectorInterface.php
Normal file
25
Shared/Domain/Contracts/PageTypeDetectorInterface.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Domain\Contracts;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\ValueObjects\PageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrato para detectar el tipo de página actual
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Domain\Contracts
|
||||||
|
*/
|
||||||
|
interface PageTypeDetectorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Detecta y retorna el tipo de página actual
|
||||||
|
*/
|
||||||
|
public function detect(): PageType;
|
||||||
|
|
||||||
|
public function isHome(): bool;
|
||||||
|
public function isPost(): bool;
|
||||||
|
public function isPage(): bool;
|
||||||
|
public function isArchive(): bool;
|
||||||
|
public function isSearch(): bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Domain\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrato para acceder a la configuración de visibilidad por página
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Domain\Contracts
|
||||||
|
*/
|
||||||
|
interface PageVisibilityRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Obtiene la configuración de visibilidad de un componente
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return array<string, bool> Mapa de campo => habilitado
|
||||||
|
*/
|
||||||
|
public function getVisibilityConfig(string $componentName): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarda la configuración de visibilidad de un componente
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente
|
||||||
|
* @param array<string, bool> $config Configuración a guardar
|
||||||
|
*/
|
||||||
|
public function saveVisibilityConfig(string $componentName, array $config): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si existe configuración de visibilidad para un componente
|
||||||
|
*/
|
||||||
|
public function hasVisibilityConfig(string $componentName): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene lista de todos los componentes registrados
|
||||||
|
*
|
||||||
|
* @return array<string> Lista de nombres de componentes
|
||||||
|
*/
|
||||||
|
public function getAllComponentNames(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea configuración de visibilidad por defecto para un componente
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente
|
||||||
|
* @param array<string, bool> $defaults Valores por defecto
|
||||||
|
*/
|
||||||
|
public function createDefaultVisibility(string $componentName, array $defaults): void;
|
||||||
|
}
|
||||||
@@ -93,6 +93,9 @@ final readonly class ComponentConfiguration
|
|||||||
'widget_3', // Widget 3 del footer (menú)
|
'widget_3', // Widget 3 del footer (menú)
|
||||||
'newsletter', // Sección newsletter del footer
|
'newsletter', // Sección newsletter del footer
|
||||||
'footer_bottom', // Pie del footer (copyright)
|
'footer_bottom', // Pie del footer (copyright)
|
||||||
|
|
||||||
|
// Sistema de visibilidad por página
|
||||||
|
'_page_visibility', // Visibilidad por tipo de página (home, posts, pages, archives, search)
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
90
Shared/Domain/ValueObjects/PageType.php
Normal file
90
Shared/Domain/ValueObjects/PageType.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Domain\ValueObjects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value Object que representa los tipos de página válidos
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Domain\ValueObjects
|
||||||
|
*/
|
||||||
|
final class PageType
|
||||||
|
{
|
||||||
|
public const HOME = 'home';
|
||||||
|
public const POST = 'post';
|
||||||
|
public const PAGE = 'page';
|
||||||
|
public const ARCHIVE = 'archive';
|
||||||
|
public const SEARCH = 'search';
|
||||||
|
public const UNKNOWN = 'unknown';
|
||||||
|
|
||||||
|
private const VALID_TYPES = [
|
||||||
|
self::HOME,
|
||||||
|
self::POST,
|
||||||
|
self::PAGE,
|
||||||
|
self::ARCHIVE,
|
||||||
|
self::SEARCH,
|
||||||
|
self::UNKNOWN,
|
||||||
|
];
|
||||||
|
|
||||||
|
private function __construct(
|
||||||
|
private readonly string $value
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function fromString(string $type): self
|
||||||
|
{
|
||||||
|
if (!in_array($type, self::VALID_TYPES, true)) {
|
||||||
|
return new self(self::UNKNOWN);
|
||||||
|
}
|
||||||
|
return new self($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function home(): self
|
||||||
|
{
|
||||||
|
return new self(self::HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function post(): self
|
||||||
|
{
|
||||||
|
return new self(self::POST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function page(): self
|
||||||
|
{
|
||||||
|
return new self(self::PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function archive(): self
|
||||||
|
{
|
||||||
|
return new self(self::ARCHIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function search(): self
|
||||||
|
{
|
||||||
|
return new self(self::SEARCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function equals(self $other): bool
|
||||||
|
{
|
||||||
|
return $this->value === $other->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna el nombre del campo de visibilidad correspondiente
|
||||||
|
*/
|
||||||
|
public function toVisibilityField(): string
|
||||||
|
{
|
||||||
|
return match ($this->value) {
|
||||||
|
self::HOME => 'show_on_home',
|
||||||
|
self::POST => 'show_on_posts',
|
||||||
|
self::PAGE => 'show_on_pages',
|
||||||
|
self::ARCHIVE => 'show_on_archives',
|
||||||
|
self::SEARCH => 'show_on_search',
|
||||||
|
default => 'show_on_posts',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace ROITheme\Shared\Infrastructure\Api\WordPress;
|
namespace ROITheme\Shared\Infrastructure\Api\WordPress;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Infrastructure\Di\DIContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WP-CLI Command para Sincronización de Schemas
|
* WP-CLI Command para Sincronización de Schemas
|
||||||
*
|
*
|
||||||
@@ -297,6 +299,298 @@ final class MigrationCommand
|
|||||||
'stats' => $stats
|
'stats' => $stats
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migra configuración de visibilidad para todos los componentes
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp roi-theme migrate-visibility
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
*/
|
||||||
|
public function migrate_visibility(): void
|
||||||
|
{
|
||||||
|
$container = DIContainer::getInstance();
|
||||||
|
$service = $container->getMigratePageVisibilityService();
|
||||||
|
|
||||||
|
$result = $service->migrate();
|
||||||
|
|
||||||
|
\WP_CLI::success(sprintf(
|
||||||
|
'Migración completada: %d creados, %d omitidos',
|
||||||
|
$result['created'],
|
||||||
|
$result['skipped']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcodes que DEBEN ser preservados
|
||||||
|
*/
|
||||||
|
private const PROTECTED_SHORTCODES = ['[roi_apu_search', '[roi_'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Máximo porcentaje de contenido que puede eliminarse
|
||||||
|
*/
|
||||||
|
private const MAX_CONTENT_LOSS_PERCENT = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia contenido Thrive congelado de páginas (H2 y paginación)
|
||||||
|
*
|
||||||
|
* LIMPIEZA QUIRÚRGICA CON VALIDACIONES DE SEGURIDAD:
|
||||||
|
* - Elimina H2 con data-shortcode="tcb_post_title"
|
||||||
|
* - Elimina paginación rota ([tcb_pagination_current_page], [tcb_pagination_total_pages])
|
||||||
|
* - PRESERVA todo el demás contenido incluyendo shortcodes [roi_apu_search]
|
||||||
|
* - Verifica que shortcodes importantes NO sean eliminados
|
||||||
|
* - Aborta si se detecta pérdida excesiva de contenido (>50%)
|
||||||
|
*
|
||||||
|
* ## OPTIONS
|
||||||
|
*
|
||||||
|
* [--dry-run]
|
||||||
|
* : Mostrar qué se limpiaría sin modificar nada (OBLIGATORIO primero)
|
||||||
|
*
|
||||||
|
* [--force]
|
||||||
|
* : Ejecutar la limpieza real después de verificar dry-run
|
||||||
|
*
|
||||||
|
* [--include-others]
|
||||||
|
* : Incluir otras páginas afectadas (Blog, Curso)
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* # Ver qué se limpiaría (modo seguro) - SIEMPRE PRIMERO
|
||||||
|
* wp roi-theme clean_thrive --dry-run
|
||||||
|
*
|
||||||
|
* # Ejecutar limpieza real (requiere --force)
|
||||||
|
* wp roi-theme clean_thrive --force
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
*/
|
||||||
|
public function clean_thrive(array $args, array $assoc_args): void
|
||||||
|
{
|
||||||
|
$affectedPageIds = [
|
||||||
|
107264, 107312, 107340, 107345, 107351, 107357, 107362,
|
||||||
|
107369, 107374, 107379, 107384, 107389, 107395, 107399,
|
||||||
|
107403, 107407, 107411, 107416, 107421, 107425, 185752
|
||||||
|
];
|
||||||
|
$otherAffectedIds = [252030, 290709];
|
||||||
|
|
||||||
|
$dryRun = isset($assoc_args['dry-run']);
|
||||||
|
$includeOthers = isset($assoc_args['include-others']);
|
||||||
|
$force = isset($assoc_args['force']);
|
||||||
|
|
||||||
|
$pageIds = $affectedPageIds;
|
||||||
|
if ($includeOthers) {
|
||||||
|
$pageIds = array_merge($pageIds, $otherAffectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::line('╔══════════════════════════════════════════════════════════════════╗');
|
||||||
|
\WP_CLI::line('║ LIMPIEZA QUIRÚRGICA DE CONTENIDO THRIVE CONGELADO (v2.0) ║');
|
||||||
|
\WP_CLI::line('║ Con validaciones de seguridad para proteger shortcodes ║');
|
||||||
|
\WP_CLI::line('╚══════════════════════════════════════════════════════════════════╝');
|
||||||
|
\WP_CLI::line('');
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
\WP_CLI::warning('MODO DRY-RUN: No se modificará ningún contenido');
|
||||||
|
} else {
|
||||||
|
\WP_CLI::error('MODO REAL DESHABILITADO: Ejecuta primero con --dry-run', false);
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::line('Para ejecutar la limpieza real, primero revisa el dry-run:');
|
||||||
|
\WP_CLI::line(' wp roi-theme clean_thrive --dry-run');
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::line('Si el dry-run es correcto y deseas ejecutar:');
|
||||||
|
\WP_CLI::line(' wp roi-theme clean_thrive --force');
|
||||||
|
|
||||||
|
if (!$force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
\WP_CLI::warning('MODO REAL CON --force: Se modificará el contenido');
|
||||||
|
}
|
||||||
|
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::line('Páginas a procesar: ' . count($pageIds));
|
||||||
|
\WP_CLI::line('Shortcodes protegidos: ' . implode(', ', self::PROTECTED_SHORTCODES));
|
||||||
|
\WP_CLI::line('Máxima pérdida permitida: ' . self::MAX_CONTENT_LOSS_PERCENT . '%');
|
||||||
|
\WP_CLI::line('');
|
||||||
|
|
||||||
|
$totalH2Removed = 0;
|
||||||
|
$totalPaginationRemoved = 0;
|
||||||
|
$totalBytesFreed = 0;
|
||||||
|
$pagesModified = 0;
|
||||||
|
$pagesSkipped = 0;
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach ($pageIds as $id) {
|
||||||
|
$page = get_post($id);
|
||||||
|
if (!$page) {
|
||||||
|
\WP_CLI::warning("Página {$id} no encontrada, saltando...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalContent = $page->post_content;
|
||||||
|
$originalSize = strlen($originalContent);
|
||||||
|
|
||||||
|
$hasThrive = strpos($originalContent, 'tcb_post_title') !== false ||
|
||||||
|
strpos($originalContent, 'tcb_pagination') !== false;
|
||||||
|
|
||||||
|
if (!$hasThrive) {
|
||||||
|
\WP_CLI::line(sprintf("[SIN THRIVE] ID %d: %s", $id, mb_substr($page->post_title, 0, 50)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$h2Count = preg_match_all('/<h2[^>]*>\s*<span[^>]*data-shortcode="tcb_post_title"[^>]*>.*?<\/span>\s*<\/h2>/s', $originalContent);
|
||||||
|
$protectedBefore = $this->countProtectedShortcodes($originalContent);
|
||||||
|
$cleanResult = $this->cleanThriveContentSafely($originalContent);
|
||||||
|
|
||||||
|
if ($cleanResult['error']) {
|
||||||
|
$errors[] = "ID {$id}: {$cleanResult['error']}";
|
||||||
|
\WP_CLI::error(sprintf("[ERROR] ID %d: %s - %s", $id, mb_substr($page->post_title, 0, 40), $cleanResult['error']), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleanedContent = $cleanResult['content'];
|
||||||
|
$newSize = strlen($cleanedContent);
|
||||||
|
$protectedAfter = $this->countProtectedShortcodes($cleanedContent);
|
||||||
|
|
||||||
|
if ($protectedAfter < $protectedBefore) {
|
||||||
|
$errors[] = "ID {$id}: Se perderían shortcodes protegidos ({$protectedBefore} → {$protectedAfter})";
|
||||||
|
\WP_CLI::error(sprintf("[ABORTADO] ID %d: Se perderían shortcodes protegidos (%d → %d)", $id, $protectedBefore, $protectedAfter), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lossPercent = $originalSize > 0 ? (($originalSize - $newSize) / $originalSize) * 100 : 0;
|
||||||
|
if ($lossPercent > self::MAX_CONTENT_LOSS_PERCENT) {
|
||||||
|
$errors[] = "ID {$id}: Pérdida excesiva de contenido ({$lossPercent}%)";
|
||||||
|
\WP_CLI::error(sprintf("[ABORTADO] ID %d: Pérdida excesiva %.1f%% (máx %d%%)", $id, $lossPercent, self::MAX_CONTENT_LOSS_PERCENT), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasChanges = $originalContent !== $cleanedContent;
|
||||||
|
$bytesSaved = $originalSize - $newSize;
|
||||||
|
$paginationRemoved = (strpos($originalContent, 'tcb_pagination_current_page') !== false && strpos($cleanedContent, 'tcb_pagination_current_page') === false) ? 1 : 0;
|
||||||
|
|
||||||
|
if ($hasChanges) {
|
||||||
|
$pagesModified++;
|
||||||
|
$totalH2Removed += $h2Count;
|
||||||
|
$totalPaginationRemoved += $paginationRemoved;
|
||||||
|
$totalBytesFreed += $bytesSaved;
|
||||||
|
|
||||||
|
$status = $dryRun ? '[DRY-RUN]' : '[LIMPIADO]';
|
||||||
|
\WP_CLI::line(sprintf("%s ID %d: %s", $status, $id, mb_substr($page->post_title, 0, 50) . (mb_strlen($page->post_title) > 50 ? '...' : '')));
|
||||||
|
\WP_CLI::line(sprintf(" → H2 eliminados: %d | Paginación: %s | Pérdida: %.1f%%", $h2Count, $paginationRemoved ? 'Sí' : 'No', $lossPercent));
|
||||||
|
\WP_CLI::line(sprintf(" → Shortcodes [roi_*] preservados: %d | Bytes liberados: %s", $protectedAfter, $this->formatBytes($bytesSaved)));
|
||||||
|
|
||||||
|
if (!$dryRun && $force) {
|
||||||
|
wp_update_post(['ID' => $id, 'post_content' => $cleanedContent]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
\WP_CLI::line(sprintf("[SIN CAMBIOS] ID %d: %s", $id, mb_substr($page->post_title, 0, 50)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::line('════════════════════════════════════════════════════════════════════');
|
||||||
|
\WP_CLI::line('RESUMEN:');
|
||||||
|
\WP_CLI::line(sprintf(' Páginas modificadas: %d', $pagesModified));
|
||||||
|
\WP_CLI::line(sprintf(' Páginas omitidas: %d', $pagesSkipped));
|
||||||
|
\WP_CLI::line(sprintf(' Total H2 eliminados: %d', $totalH2Removed));
|
||||||
|
\WP_CLI::line(sprintf(' Paginaciones removidas: %d', $totalPaginationRemoved));
|
||||||
|
\WP_CLI::line(sprintf(' Espacio liberado: %s', $this->formatBytes($totalBytesFreed)));
|
||||||
|
\WP_CLI::line('════════════════════════════════════════════════════════════════════');
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::warning('ERRORES ENCONTRADOS:');
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
\WP_CLI::line(" - {$error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun && $pagesModified > 0 && count($errors) === 0) {
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::success('Dry-run completado SIN errores.');
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::warning('Para ejecutar la limpieza real:');
|
||||||
|
\WP_CLI::line(' wp roi-theme clean_thrive --force');
|
||||||
|
} elseif (!$dryRun && $force && $pagesModified > 0) {
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::success('Limpieza completada exitosamente.');
|
||||||
|
\WP_CLI::line('');
|
||||||
|
\WP_CLI::warning('IMPORTANTE: Purga el caché del sitio para ver los cambios.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el contenido con validaciones de seguridad
|
||||||
|
* @return array{content: string, error: string|null}
|
||||||
|
*/
|
||||||
|
private function cleanThriveContentSafely(string $content): array
|
||||||
|
{
|
||||||
|
$originalContent = $content;
|
||||||
|
|
||||||
|
// Patrón específico: H2 que contiene span con data-shortcode="tcb_post_title"
|
||||||
|
// Estructura: <h2><span data-shortcode="tcb_post_title"...>...</span></h2>
|
||||||
|
$result = preg_replace('/<h2[^>]*>\s*<span[^>]*data-shortcode="tcb_post_title"[^>]*>.*?<\/span>\s*<\/h2>/s', '', $content);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón H2'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
$result = preg_replace('/<p[^>]*>.*?\[tcb_pagination_current_page\].*?\[tcb_pagination_total_pages\].*?<\/p>/s', '', $content);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón paginación'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
$result = preg_replace('/<p[^>]*data-button_layout="[^"]*"[^>]*data-page="[^"]*"[^>]*>.*?<\/p>/s', '', $content);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón botones'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
$content = str_replace('[tcb_pagination_current_page]', '', $content);
|
||||||
|
$content = str_replace('[tcb_pagination_total_pages]', '', $content);
|
||||||
|
|
||||||
|
$result = preg_replace('/(\r?\n){3,}/', "\n\n", $content);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en limpieza líneas'];
|
||||||
|
}
|
||||||
|
$content = trim($result);
|
||||||
|
|
||||||
|
if (empty($content) && !empty($originalContent)) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'El contenido quedó vacío'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['content' => $content, 'error' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cuenta shortcodes protegidos en el contenido
|
||||||
|
*/
|
||||||
|
private function countProtectedShortcodes(string $content): int
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
foreach (self::PROTECTED_SHORTCODES as $shortcode) {
|
||||||
|
$count += substr_count($content, $shortcode);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatea bytes a formato legible
|
||||||
|
*/
|
||||||
|
private function formatBytes(int $bytes): string
|
||||||
|
{
|
||||||
|
if ($bytes < 1024) {
|
||||||
|
return $bytes . ' B';
|
||||||
|
} elseif ($bytes < 1048576) {
|
||||||
|
return round($bytes / 1024, 1) . ' KB';
|
||||||
|
} else {
|
||||||
|
return round($bytes / 1048576, 2) . ' MB';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar comando WP-CLI
|
// Registrar comando WP-CLI
|
||||||
|
|||||||
352
Shared/Infrastructure/CLI/CleanThriveContentCommand.php
Normal file
352
Shared/Infrastructure/CLI/CleanThriveContentCommand.php
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\CLI;
|
||||||
|
|
||||||
|
use WP_CLI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comando WP-CLI para limpiar contenido Thrive congelado de páginas
|
||||||
|
*
|
||||||
|
* LIMPIEZA QUIRÚRGICA CON VALIDACIONES DE SEGURIDAD:
|
||||||
|
* - Elimina H2 con data-shortcode="tcb_post_title"
|
||||||
|
* - Elimina paginación rota ([tcb_pagination_current_page], [tcb_pagination_total_pages])
|
||||||
|
* - PRESERVA todo el demás contenido incluyendo shortcodes [roi_apu_search]
|
||||||
|
* - Verifica que shortcodes importantes NO sean eliminados
|
||||||
|
* - Aborta si se detecta pérdida excesiva de contenido (>50%)
|
||||||
|
*
|
||||||
|
* USO:
|
||||||
|
* wp roi-theme clean_thrive --dry-run # Ver qué se limpiaría (OBLIGATORIO primero)
|
||||||
|
* wp roi-theme clean_thrive # Ejecutar limpieza real
|
||||||
|
*
|
||||||
|
* SEGURIDAD:
|
||||||
|
* - Verifica preservación de shortcodes [roi_apu_search]
|
||||||
|
* - Máximo 50% de reducción de contenido permitida
|
||||||
|
* - Valida cada preg_replace para evitar null returns
|
||||||
|
*/
|
||||||
|
final class CleanThriveContentCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* IDs de páginas buscar-apus afectadas
|
||||||
|
*/
|
||||||
|
private const AFFECTED_PAGE_IDS = [
|
||||||
|
107264, 107312, 107340, 107345, 107351, 107357, 107362,
|
||||||
|
107369, 107374, 107379, 107384, 107389, 107395, 107399,
|
||||||
|
107403, 107407, 107411, 107416, 107421, 107425, 185752
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otras páginas con contenido Thrive (Blog, Curso)
|
||||||
|
*/
|
||||||
|
private const OTHER_AFFECTED_IDS = [252030, 290709];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcodes que DEBEN ser preservados
|
||||||
|
*/
|
||||||
|
private const PROTECTED_SHORTCODES = [
|
||||||
|
'[roi_apu_search',
|
||||||
|
'[roi_',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Máximo porcentaje de contenido que puede eliminarse
|
||||||
|
*/
|
||||||
|
private const MAX_CONTENT_LOSS_PERCENT = 50;
|
||||||
|
|
||||||
|
public function __invoke(array $args, array $assoc_args): void
|
||||||
|
{
|
||||||
|
$dryRun = isset($assoc_args['dry-run']);
|
||||||
|
$includeOthers = isset($assoc_args['include-others']);
|
||||||
|
$force = isset($assoc_args['force']);
|
||||||
|
|
||||||
|
$pageIds = self::AFFECTED_PAGE_IDS;
|
||||||
|
if ($includeOthers) {
|
||||||
|
$pageIds = array_merge($pageIds, self::OTHER_AFFECTED_IDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::log('╔══════════════════════════════════════════════════════════════════╗');
|
||||||
|
WP_CLI::log('║ LIMPIEZA QUIRÚRGICA DE CONTENIDO THRIVE CONGELADO (v2.0) ║');
|
||||||
|
WP_CLI::log('║ Con validaciones de seguridad para proteger shortcodes ║');
|
||||||
|
WP_CLI::log('╚══════════════════════════════════════════════════════════════════╝');
|
||||||
|
WP_CLI::log('');
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
WP_CLI::warning('MODO DRY-RUN: No se modificará ningún contenido');
|
||||||
|
} else {
|
||||||
|
WP_CLI::error('MODO REAL DESHABILITADO: Ejecuta primero con --dry-run', false);
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::log('Para ejecutar la limpieza real, primero revisa el dry-run:');
|
||||||
|
WP_CLI::log(' wp roi-theme clean_thrive --dry-run');
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::log('Si el dry-run es correcto y deseas ejecutar:');
|
||||||
|
WP_CLI::log(' wp roi-theme clean_thrive --force');
|
||||||
|
|
||||||
|
if (!$force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WP_CLI::warning('MODO REAL CON --force: Se modificará el contenido');
|
||||||
|
}
|
||||||
|
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::log('Páginas a procesar: ' . count($pageIds));
|
||||||
|
WP_CLI::log('Shortcodes protegidos: ' . implode(', ', self::PROTECTED_SHORTCODES));
|
||||||
|
WP_CLI::log('Máxima pérdida permitida: ' . self::MAX_CONTENT_LOSS_PERCENT . '%');
|
||||||
|
WP_CLI::log('');
|
||||||
|
|
||||||
|
$totalH2Removed = 0;
|
||||||
|
$totalPaginationRemoved = 0;
|
||||||
|
$totalBytesFreed = 0;
|
||||||
|
$pagesModified = 0;
|
||||||
|
$pagesSkipped = 0;
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach ($pageIds as $id) {
|
||||||
|
$page = get_post($id);
|
||||||
|
if (!$page) {
|
||||||
|
WP_CLI::warning("Página {$id} no encontrada, saltando...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalContent = $page->post_content;
|
||||||
|
$originalSize = strlen($originalContent);
|
||||||
|
|
||||||
|
// Verificar si tiene contenido Thrive que limpiar
|
||||||
|
$hasThrive = strpos($originalContent, 'tcb_post_title') !== false ||
|
||||||
|
strpos($originalContent, 'tcb_pagination') !== false;
|
||||||
|
|
||||||
|
if (!$hasThrive) {
|
||||||
|
WP_CLI::log(sprintf(
|
||||||
|
"[SIN THRIVE] ID %d: %s",
|
||||||
|
$id,
|
||||||
|
mb_substr($page->post_title, 0, 50)
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contar elementos antes de limpiar
|
||||||
|
$h2Count = preg_match_all('/<h2[^>]*>.*?data-shortcode="tcb_post_title".*?<\/h2>/s', $originalContent);
|
||||||
|
|
||||||
|
// Contar shortcodes protegidos antes
|
||||||
|
$protectedBefore = $this->countProtectedShortcodes($originalContent);
|
||||||
|
|
||||||
|
// Limpiar contenido con validación
|
||||||
|
$cleanResult = $this->cleanContentSafely($originalContent);
|
||||||
|
|
||||||
|
if ($cleanResult['error']) {
|
||||||
|
$errors[] = "ID {$id}: {$cleanResult['error']}";
|
||||||
|
WP_CLI::error(sprintf(
|
||||||
|
"[ERROR] ID %d: %s - %s",
|
||||||
|
$id,
|
||||||
|
mb_substr($page->post_title, 0, 40),
|
||||||
|
$cleanResult['error']
|
||||||
|
), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleanedContent = $cleanResult['content'];
|
||||||
|
$newSize = strlen($cleanedContent);
|
||||||
|
|
||||||
|
// Contar shortcodes protegidos después
|
||||||
|
$protectedAfter = $this->countProtectedShortcodes($cleanedContent);
|
||||||
|
|
||||||
|
// VALIDACIÓN CRÍTICA: Verificar shortcodes protegidos
|
||||||
|
if ($protectedAfter < $protectedBefore) {
|
||||||
|
$errors[] = "ID {$id}: Se perderían shortcodes protegidos ({$protectedBefore} → {$protectedAfter})";
|
||||||
|
WP_CLI::error(sprintf(
|
||||||
|
"[ABORTADO] ID %d: Se perderían shortcodes protegidos (%d → %d)",
|
||||||
|
$id,
|
||||||
|
$protectedBefore,
|
||||||
|
$protectedAfter
|
||||||
|
), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar pérdida excesiva de contenido
|
||||||
|
$lossPercent = $originalSize > 0 ? (($originalSize - $newSize) / $originalSize) * 100 : 0;
|
||||||
|
if ($lossPercent > self::MAX_CONTENT_LOSS_PERCENT) {
|
||||||
|
$errors[] = "ID {$id}: Pérdida excesiva de contenido ({$lossPercent}%)";
|
||||||
|
WP_CLI::error(sprintf(
|
||||||
|
"[ABORTADO] ID %d: Pérdida excesiva %.1f%% (máx %d%%)",
|
||||||
|
$id,
|
||||||
|
$lossPercent,
|
||||||
|
self::MAX_CONTENT_LOSS_PERCENT
|
||||||
|
), false);
|
||||||
|
$pagesSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si hubo cambios
|
||||||
|
$hasChanges = $originalContent !== $cleanedContent;
|
||||||
|
$bytesSaved = $originalSize - $newSize;
|
||||||
|
|
||||||
|
// Contar paginación removida
|
||||||
|
$paginationRemoved = (
|
||||||
|
strpos($originalContent, 'tcb_pagination_current_page') !== false &&
|
||||||
|
strpos($cleanedContent, 'tcb_pagination_current_page') === false
|
||||||
|
) ? 1 : 0;
|
||||||
|
|
||||||
|
if ($hasChanges) {
|
||||||
|
$pagesModified++;
|
||||||
|
$totalH2Removed += $h2Count;
|
||||||
|
$totalPaginationRemoved += $paginationRemoved;
|
||||||
|
$totalBytesFreed += $bytesSaved;
|
||||||
|
|
||||||
|
$status = $dryRun ? '[DRY-RUN]' : '[LIMPIADO]';
|
||||||
|
WP_CLI::log(sprintf(
|
||||||
|
"%s ID %d: %s",
|
||||||
|
$status,
|
||||||
|
$id,
|
||||||
|
mb_substr($page->post_title, 0, 50) . (mb_strlen($page->post_title) > 50 ? '...' : '')
|
||||||
|
));
|
||||||
|
WP_CLI::log(sprintf(
|
||||||
|
" → H2 eliminados: %d | Paginación: %s | Pérdida: %.1f%%",
|
||||||
|
$h2Count,
|
||||||
|
$paginationRemoved ? 'Sí' : 'No',
|
||||||
|
$lossPercent
|
||||||
|
));
|
||||||
|
WP_CLI::log(sprintf(
|
||||||
|
" → Shortcodes [roi_*] preservados: %d | Bytes liberados: %s",
|
||||||
|
$protectedAfter,
|
||||||
|
$this->formatBytes($bytesSaved)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$dryRun && $force) {
|
||||||
|
wp_update_post([
|
||||||
|
'ID' => $id,
|
||||||
|
'post_content' => $cleanedContent
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WP_CLI::log(sprintf(
|
||||||
|
"[SIN CAMBIOS] ID %d: %s",
|
||||||
|
$id,
|
||||||
|
mb_substr($page->post_title, 0, 50)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::log('════════════════════════════════════════════════════════════════════');
|
||||||
|
WP_CLI::log('RESUMEN:');
|
||||||
|
WP_CLI::log(sprintf(' Páginas modificadas: %d', $pagesModified));
|
||||||
|
WP_CLI::log(sprintf(' Páginas omitidas: %d', $pagesSkipped));
|
||||||
|
WP_CLI::log(sprintf(' Total H2 eliminados: %d', $totalH2Removed));
|
||||||
|
WP_CLI::log(sprintf(' Paginaciones removidas: %d', $totalPaginationRemoved));
|
||||||
|
WP_CLI::log(sprintf(' Espacio liberado: %s', $this->formatBytes($totalBytesFreed)));
|
||||||
|
WP_CLI::log('════════════════════════════════════════════════════════════════════');
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::warning('ERRORES ENCONTRADOS:');
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
WP_CLI::log(" - {$error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun && $pagesModified > 0 && count($errors) === 0) {
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::success('Dry-run completado SIN errores.');
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::warning('Para ejecutar la limpieza real:');
|
||||||
|
WP_CLI::log(' wp roi-theme clean_thrive --force');
|
||||||
|
} elseif (!$dryRun && $force && $pagesModified > 0) {
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::success('Limpieza completada exitosamente.');
|
||||||
|
WP_CLI::log('');
|
||||||
|
WP_CLI::warning('IMPORTANTE: Purga el caché del sitio para ver los cambios.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el contenido con validaciones de seguridad
|
||||||
|
*
|
||||||
|
* @return array{content: string, error: string|null}
|
||||||
|
*/
|
||||||
|
private function cleanContentSafely(string $content): array
|
||||||
|
{
|
||||||
|
$originalContent = $content;
|
||||||
|
|
||||||
|
// 1. Eliminar H2 con data-shortcode="tcb_post_title"
|
||||||
|
$result = preg_replace(
|
||||||
|
'/<h2[^>]*>.*?data-shortcode="tcb_post_title".*?<\/h2>/s',
|
||||||
|
'',
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón H2'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
// 2. Eliminar paginación Thrive rota
|
||||||
|
$result = preg_replace(
|
||||||
|
'/<p[^>]*>.*?\[tcb_pagination_current_page\].*?\[tcb_pagination_total_pages\].*?<\/p>/s',
|
||||||
|
'',
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón paginación'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
// 3. Eliminar botones de paginación Thrive
|
||||||
|
$result = preg_replace(
|
||||||
|
'/<p[^>]*data-button_layout="[^"]*"[^>]*data-page="[^"]*"[^>]*>.*?<\/p>/s',
|
||||||
|
'',
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón botones'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
// 4. Eliminar shortcodes Thrive huérfanos
|
||||||
|
$content = str_replace('[tcb_pagination_current_page]', '', $content);
|
||||||
|
$content = str_replace('[tcb_pagination_total_pages]', '', $content);
|
||||||
|
|
||||||
|
// 5. Limpiar múltiples líneas vacías (con validación)
|
||||||
|
$result = preg_replace('/(\r?\n){3,}/', "\n\n", $content);
|
||||||
|
if ($result === null) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en limpieza líneas'];
|
||||||
|
}
|
||||||
|
$content = $result;
|
||||||
|
|
||||||
|
// 6. Trim
|
||||||
|
$content = trim($content);
|
||||||
|
|
||||||
|
// Validación final: no retornar vacío si original tenía contenido
|
||||||
|
if (empty($content) && !empty($originalContent)) {
|
||||||
|
return ['content' => $originalContent, 'error' => 'El contenido quedó vacío'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['content' => $content, 'error' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cuenta shortcodes protegidos en el contenido
|
||||||
|
*/
|
||||||
|
private function countProtectedShortcodes(string $content): int
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
foreach (self::PROTECTED_SHORTCODES as $shortcode) {
|
||||||
|
$count += substr_count($content, $shortcode);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatea bytes a formato legible
|
||||||
|
*/
|
||||||
|
private function formatBytes(int $bytes): string
|
||||||
|
{
|
||||||
|
if ($bytes < 1024) {
|
||||||
|
return $bytes . ' B';
|
||||||
|
} elseif ($bytes < 1048576) {
|
||||||
|
return round($bytes / 1024, 1) . ' KB';
|
||||||
|
} else {
|
||||||
|
return round($bytes / 1048576, 2) . ' MB';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,12 @@ use ROITheme\Shared\Infrastructure\Services\CriticalCSSCollector;
|
|||||||
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
|
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
|
||||||
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
|
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
|
||||||
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageVisibilityRepositoryInterface;
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageTypeDetectorInterface;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\WordPressPageTypeDetector;
|
||||||
|
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressPageVisibilityRepository;
|
||||||
|
use ROITheme\Shared\Application\UseCases\EvaluatePageVisibility\EvaluatePageVisibilityUseCase;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\MigratePageVisibilityService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DIContainer - Contenedor de Inyección de Dependencias
|
* DIContainer - Contenedor de Inyección de Dependencias
|
||||||
@@ -46,10 +52,38 @@ final class DIContainer
|
|||||||
{
|
{
|
||||||
private array $instances = [];
|
private array $instances = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instancia singleton del contenedor
|
||||||
|
* @var self|null
|
||||||
|
*/
|
||||||
|
private static ?self $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la instancia singleton del contenedor
|
||||||
|
*
|
||||||
|
* NOTA: Se debe haber creado una instancia previamente en functions.php
|
||||||
|
* El constructor registra automáticamente la instancia.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @throws \RuntimeException Si no se ha inicializado el contenedor
|
||||||
|
*/
|
||||||
|
public static function getInstance(): self
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'DIContainer no ha sido inicializado. Asegúrate de que functions.php se haya ejecutado primero.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private \wpdb $wpdb,
|
private \wpdb $wpdb,
|
||||||
private string $schemasPath
|
private string $schemasPath
|
||||||
) {}
|
) {
|
||||||
|
// Registrar como instancia singleton
|
||||||
|
self::$instance = $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtener repositorio de componentes
|
* Obtener repositorio de componentes
|
||||||
@@ -272,4 +306,61 @@ final class DIContainer
|
|||||||
|
|
||||||
return $this->instances['criticalCSSCollector'];
|
return $this->instances['criticalCSSCollector'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===============================
|
||||||
|
// Page Visibility System
|
||||||
|
// ===============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el repositorio de visibilidad de página
|
||||||
|
*
|
||||||
|
* IMPORTANTE: Inyecta $wpdb para consistencia con el resto del código
|
||||||
|
* (WordPressComponentSettingsRepository también recibe $wpdb por constructor)
|
||||||
|
*/
|
||||||
|
public function getPageVisibilityRepository(): PageVisibilityRepositoryInterface
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['pageVisibilityRepository'])) {
|
||||||
|
// Inyectar $wpdb siguiendo el patrón existente
|
||||||
|
$this->instances['pageVisibilityRepository'] = new WordPressPageVisibilityRepository($this->wpdb);
|
||||||
|
}
|
||||||
|
return $this->instances['pageVisibilityRepository'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el detector de tipo de página
|
||||||
|
*/
|
||||||
|
public function getPageTypeDetector(): PageTypeDetectorInterface
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['pageTypeDetector'])) {
|
||||||
|
$this->instances['pageTypeDetector'] = new WordPressPageTypeDetector();
|
||||||
|
}
|
||||||
|
return $this->instances['pageTypeDetector'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el caso de uso de evaluación de visibilidad
|
||||||
|
*/
|
||||||
|
public function getEvaluatePageVisibilityUseCase(): EvaluatePageVisibilityUseCase
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['evaluatePageVisibilityUseCase'])) {
|
||||||
|
$this->instances['evaluatePageVisibilityUseCase'] = new EvaluatePageVisibilityUseCase(
|
||||||
|
$this->getPageTypeDetector(),
|
||||||
|
$this->getPageVisibilityRepository()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->instances['evaluatePageVisibilityUseCase'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el servicio de migración de visibilidad
|
||||||
|
*/
|
||||||
|
public function getMigratePageVisibilityService(): MigratePageVisibilityService
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['migratePageVisibilityService'])) {
|
||||||
|
$this->instances['migratePageVisibilityService'] = new MigratePageVisibilityService(
|
||||||
|
$this->getPageVisibilityRepository()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->instances['migratePageVisibilityService'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Persistence\WordPress;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageVisibilityRepositoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementación WordPress del repositorio de visibilidad
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Persistence\WordPress
|
||||||
|
*/
|
||||||
|
final class WordPressPageVisibilityRepository implements PageVisibilityRepositoryInterface
|
||||||
|
{
|
||||||
|
private const GROUP_NAME = '_page_visibility';
|
||||||
|
private const TABLE_SUFFIX = 'roi_theme_component_settings';
|
||||||
|
|
||||||
|
private const VISIBILITY_FIELDS = [
|
||||||
|
'show_on_home',
|
||||||
|
'show_on_posts',
|
||||||
|
'show_on_pages',
|
||||||
|
'show_on_archives',
|
||||||
|
'show_on_search',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor con inyección de dependencias
|
||||||
|
*
|
||||||
|
* IMPORTANTE: Sigue el patrón existente de WordPressComponentSettingsRepository
|
||||||
|
* donde $wpdb se inyecta por constructor, no se usa global.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly \wpdb $wpdb
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getVisibilityConfig(string $componentName): array
|
||||||
|
{
|
||||||
|
$table = $this->wpdb->prefix . self::TABLE_SUFFIX;
|
||||||
|
|
||||||
|
$results = $this->wpdb->get_results(
|
||||||
|
$this->wpdb->prepare(
|
||||||
|
"SELECT attribute_name, attribute_value
|
||||||
|
FROM {$table}
|
||||||
|
WHERE component_name = %s
|
||||||
|
AND group_name = %s",
|
||||||
|
$componentName,
|
||||||
|
self::GROUP_NAME
|
||||||
|
),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($results)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = [];
|
||||||
|
foreach ($results as $row) {
|
||||||
|
$config[$row['attribute_name']] = $row['attribute_value'] === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveVisibilityConfig(string $componentName, array $config): void
|
||||||
|
{
|
||||||
|
$table = $this->wpdb->prefix . self::TABLE_SUFFIX;
|
||||||
|
|
||||||
|
foreach ($config as $field => $enabled) {
|
||||||
|
if (!in_array($field, self::VISIBILITY_FIELDS, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $this->wpdb->get_var($this->wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM {$table}
|
||||||
|
WHERE component_name = %s
|
||||||
|
AND group_name = %s
|
||||||
|
AND attribute_name = %s",
|
||||||
|
$componentName,
|
||||||
|
self::GROUP_NAME,
|
||||||
|
$field
|
||||||
|
));
|
||||||
|
|
||||||
|
$value = $enabled ? '1' : '0';
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
$this->wpdb->update(
|
||||||
|
$table,
|
||||||
|
[
|
||||||
|
'attribute_value' => $value,
|
||||||
|
'updated_at' => current_time('mysql'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'component_name' => $componentName,
|
||||||
|
'group_name' => self::GROUP_NAME,
|
||||||
|
'attribute_name' => $field,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->wpdb->insert($table, [
|
||||||
|
'component_name' => $componentName,
|
||||||
|
'group_name' => self::GROUP_NAME,
|
||||||
|
'attribute_name' => $field,
|
||||||
|
'attribute_value' => $value,
|
||||||
|
'is_editable' => 1,
|
||||||
|
'created_at' => current_time('mysql'),
|
||||||
|
'updated_at' => current_time('mysql'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasVisibilityConfig(string $componentName): bool
|
||||||
|
{
|
||||||
|
$table = $this->wpdb->prefix . self::TABLE_SUFFIX;
|
||||||
|
|
||||||
|
$count = $this->wpdb->get_var($this->wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM {$table}
|
||||||
|
WHERE component_name = %s
|
||||||
|
AND group_name = %s",
|
||||||
|
$componentName,
|
||||||
|
self::GROUP_NAME
|
||||||
|
));
|
||||||
|
|
||||||
|
return (int) $count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllComponentNames(): array
|
||||||
|
{
|
||||||
|
$table = $this->wpdb->prefix . self::TABLE_SUFFIX;
|
||||||
|
|
||||||
|
$results = $this->wpdb->get_col(
|
||||||
|
"SELECT DISTINCT component_name FROM {$table} ORDER BY component_name"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $results ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDefaultVisibility(string $componentName, array $defaults): void
|
||||||
|
{
|
||||||
|
if ($this->hasVisibilityConfig($componentName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveVisibilityConfig($componentName, $defaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Services;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageVisibilityRepositoryInterface;
|
||||||
|
use ROITheme\Shared\Domain\Constants\VisibilityDefaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servicio para migrar configuración de visibilidad inicial
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Services
|
||||||
|
*/
|
||||||
|
final class MigratePageVisibilityService
|
||||||
|
{
|
||||||
|
// NOTA: Usa VisibilityDefaults::DEFAULT_VISIBILITY para cumplir DRY
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly PageVisibilityRepositoryInterface $visibilityRepository
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ejecuta la migración para todos los componentes
|
||||||
|
*
|
||||||
|
* @return array{created: int, skipped: int}
|
||||||
|
*/
|
||||||
|
public function migrate(): array
|
||||||
|
{
|
||||||
|
$created = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
|
||||||
|
$components = $this->visibilityRepository->getAllComponentNames();
|
||||||
|
|
||||||
|
foreach ($components as $componentName) {
|
||||||
|
if ($this->visibilityRepository->hasVisibilityConfig($componentName)) {
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usar constante compartida (DRY)
|
||||||
|
$this->visibilityRepository->createDefaultVisibility(
|
||||||
|
$componentName,
|
||||||
|
VisibilityDefaults::DEFAULT_VISIBILITY
|
||||||
|
);
|
||||||
|
$created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'created' => $created,
|
||||||
|
'skipped' => $skipped,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Shared/Infrastructure/Services/PageVisibilityHelper.php
Normal file
39
Shared/Infrastructure/Services/PageVisibilityHelper.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Services;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Infrastructure\Di\DIContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade/Helper para evaluar visibilidad de componentes
|
||||||
|
*
|
||||||
|
* PROPÓSITO:
|
||||||
|
* Permite que los Renderers existentes evalúen visibilidad sin modificar sus constructores.
|
||||||
|
* Actúa como un Service Locator limitado a este único propósito.
|
||||||
|
*
|
||||||
|
* USO EN RENDERERS:
|
||||||
|
* ```php
|
||||||
|
* if (!PageVisibilityHelper::shouldShow('cta-box-sidebar')) {
|
||||||
|
* return '';
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Services
|
||||||
|
*/
|
||||||
|
final class PageVisibilityHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Evalúa si un componente debe mostrarse en la página actual
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return bool True si debe mostrarse
|
||||||
|
*/
|
||||||
|
public static function shouldShow(string $componentName): bool
|
||||||
|
{
|
||||||
|
$container = DIContainer::getInstance();
|
||||||
|
$useCase = $container->getEvaluatePageVisibilityUseCase();
|
||||||
|
|
||||||
|
return $useCase->execute($componentName);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Shared/Infrastructure/Services/WordPressPageTypeDetector.php
Normal file
65
Shared/Infrastructure/Services/WordPressPageTypeDetector.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Services;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\PageTypeDetectorInterface;
|
||||||
|
use ROITheme\Shared\Domain\ValueObjects\PageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementación WordPress del detector de tipo de página
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Services
|
||||||
|
*/
|
||||||
|
final class WordPressPageTypeDetector implements PageTypeDetectorInterface
|
||||||
|
{
|
||||||
|
public function detect(): PageType
|
||||||
|
{
|
||||||
|
if ($this->isHome()) {
|
||||||
|
return PageType::home();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isPost()) {
|
||||||
|
return PageType::post();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isPage()) {
|
||||||
|
return PageType::page();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isSearch()) {
|
||||||
|
return PageType::search();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isArchive()) {
|
||||||
|
return PageType::archive();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PageType::fromString(PageType::UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isHome(): bool
|
||||||
|
{
|
||||||
|
return is_front_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPost(): bool
|
||||||
|
{
|
||||||
|
return is_single() && !is_front_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPage(): bool
|
||||||
|
{
|
||||||
|
return is_page() && !is_front_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isArchive(): bool
|
||||||
|
{
|
||||||
|
return is_archive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSearch(): bool
|
||||||
|
{
|
||||||
|
return is_search();
|
||||||
|
}
|
||||||
|
}
|
||||||
206
front-page.php
206
front-page.php
@@ -2,7 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* The template for displaying the static front page
|
* The template for displaying the static front page
|
||||||
*
|
*
|
||||||
* Structure replicates template index.html lines 322-345 (Hero Section)
|
* Replica la estructura de single.php para consistencia visual.
|
||||||
|
* Grid layout: col-lg-9 (contenido) + col-lg-3 (sidebar)
|
||||||
*
|
*
|
||||||
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#front-page
|
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#front-page
|
||||||
*
|
*
|
||||||
@@ -13,118 +14,105 @@
|
|||||||
get_header();
|
get_header();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Hero Title Section (Template líneas 322-345) -->
|
<?php while (have_posts()) : the_post(); ?>
|
||||||
<div class="container-fluid py-5 mb-4 hero-title">
|
|
||||||
<div class="container">
|
<main id="main-content" class="site-main" role="main">
|
||||||
|
|
||||||
|
<!-- Hero Section - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('hero');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Main Content Grid -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Main Content Column (col-lg-9) -->
|
||||||
|
<div class="col-lg-9">
|
||||||
|
|
||||||
|
<!-- Featured Image - Componente dinámico -->
|
||||||
<?php
|
<?php
|
||||||
while ( have_posts() ) :
|
if (function_exists('roi_render_component')) {
|
||||||
the_post();
|
echo roi_render_component('featured-image');
|
||||||
|
}
|
||||||
// Categories Section
|
|
||||||
$categories = get_the_category();
|
|
||||||
if ( ! empty( $categories ) && count( $categories ) > 0 ) :
|
|
||||||
?>
|
|
||||||
<div class="mb-3 d-flex justify-content-center">
|
|
||||||
<div class="d-flex gap-2 flex-wrap justify-content-center">
|
|
||||||
<?php
|
|
||||||
// Limit to 3 categories max
|
|
||||||
$cat_count = 0;
|
|
||||||
foreach ( $categories as $category ) :
|
|
||||||
if ( $cat_count >= 3 ) break;
|
|
||||||
if ( $category->slug === 'uncategorized' ) continue;
|
|
||||||
?>
|
|
||||||
<a href="<?php echo esc_url( get_category_link( $category->term_id ) ); ?>" class="category-badge category-badge-hero">
|
|
||||||
<i class="bi bi-folder-fill me-1"></i>
|
|
||||||
<?php echo esc_html( $category->name ); ?>
|
|
||||||
</a>
|
|
||||||
<?php
|
|
||||||
$cat_count++;
|
|
||||||
endforeach;
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Page Title -->
|
|
||||||
<h1 class="display-5 fw-bold text-center">
|
|
||||||
<?php the_title(); ?>
|
|
||||||
</h1>
|
|
||||||
<?php endwhile; ?>
|
|
||||||
</div><!-- .container -->
|
|
||||||
</div><!-- .hero-title -->
|
|
||||||
|
|
||||||
<main id="main-content" class="site-main front-page" role="main">
|
|
||||||
|
|
||||||
<!-- Container Bootstrap (Template línea 347) -->
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<?php
|
|
||||||
while ( have_posts() ) :
|
|
||||||
the_post();
|
|
||||||
?>
|
|
||||||
|
|
||||||
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
|
|
||||||
|
|
||||||
<!-- Front Page Content -->
|
|
||||||
<div class="entry-content">
|
|
||||||
<?php
|
|
||||||
the_content();
|
|
||||||
|
|
||||||
// Display page links for paginated pages
|
|
||||||
wp_link_pages(
|
|
||||||
array(
|
|
||||||
'before' => '<div class="page-links">' . esc_html__( 'Pages:', 'roi-theme' ),
|
|
||||||
'after' => '</div>',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
</div><!-- .entry-content -->
|
|
||||||
|
|
||||||
<!-- Front Page Footer -->
|
|
||||||
<?php if ( get_edit_post_link() ) : ?>
|
|
||||||
<footer class="entry-footer">
|
|
||||||
<?php
|
|
||||||
// Edit post link for logged-in users with permission
|
|
||||||
edit_post_link(
|
|
||||||
sprintf(
|
|
||||||
wp_kses(
|
|
||||||
/* translators: %s: Page title. Only visible to screen readers. */
|
|
||||||
__( 'Edit<span class="screen-reader-text"> "%s"</span>', 'roi-theme' ),
|
|
||||||
array(
|
|
||||||
'span' => array(
|
|
||||||
'class' => array(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
get_the_title()
|
|
||||||
),
|
|
||||||
'<span class="edit-link">',
|
|
||||||
'</span>'
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
</footer><!-- .entry-footer -->
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</article><!-- #post-<?php the_ID(); ?> -->
|
|
||||||
|
|
||||||
<?php
|
|
||||||
// Display comments section if enabled
|
|
||||||
if ( comments_open() || get_comments_number() ) :
|
|
||||||
comments_template();
|
|
||||||
endif;
|
|
||||||
|
|
||||||
endwhile; // End of the loop.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to display additional content on front page
|
|
||||||
* This can be used to add featured posts, testimonials, etc.
|
|
||||||
*/
|
|
||||||
do_action( 'roi_front_page_content' );
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
</div><!-- .container -->
|
<!-- Page Content -->
|
||||||
|
<article id="post-<?php the_ID(); ?>" <?php post_class('post-content'); ?>>
|
||||||
|
<?php
|
||||||
|
the_content();
|
||||||
|
|
||||||
</main><!-- #main-content -->
|
wp_link_pages(array(
|
||||||
|
'before' => '<div class="page-links">' . esc_html__('Pages:', 'roi-theme'),
|
||||||
|
'after' => '</div>',
|
||||||
|
));
|
||||||
|
?>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Share Buttons - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('social-share');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- CTA Post - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('cta-post');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Related Posts - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('related-post');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Ad After Related Posts -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_ad_slot')) {
|
||||||
|
echo roi_render_ad_slot('after-related');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
</div><!-- .col-lg-9 -->
|
||||||
|
|
||||||
|
<!-- Sidebar Column (col-lg-3) -->
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="sidebar-sticky">
|
||||||
|
<!-- Table of Contents - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('table-of-contents');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- CTA Box Sidebar - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('cta-box-sidebar');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- .row -->
|
||||||
|
</div><!-- .container -->
|
||||||
|
|
||||||
|
</main><!-- #main-content -->
|
||||||
|
|
||||||
|
<?php endwhile; ?>
|
||||||
|
|
||||||
|
<!-- Contact Form Section - Componente dinámico -->
|
||||||
|
<?php
|
||||||
|
if (function_exists('roi_render_component')) {
|
||||||
|
echo roi_render_component('contact-form');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
get_footer();
|
get_footer();
|
||||||
|
|||||||
@@ -380,3 +380,127 @@ add_action('after_setup_theme', function() {
|
|||||||
// desde la base de datos a través de sus respectivos Renderers.
|
// desde la base de datos a través de sus respectivos Renderers.
|
||||||
// NO hardcodear CSS aquí - viola la arquitectura Clean Architecture.
|
// NO hardcodear CSS aquí - viola la arquitectura Clean Architecture.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// HELPER FUNCTION: roi_get_adsense_search_config()
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la configuracion de AdSense para resultados de busqueda
|
||||||
|
*
|
||||||
|
* Esta funcion es la API publica que el plugin roi-apu-search consume.
|
||||||
|
* El plugin NO debe acceder directamente a la tabla del tema.
|
||||||
|
*
|
||||||
|
* OPTIMIZACION: Una sola query carga todos los settings del componente.
|
||||||
|
*
|
||||||
|
* @return array Configuracion para JavaScript
|
||||||
|
*/
|
||||||
|
function roi_get_adsense_search_config(): array {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// CARGAR TODOS LOS SETTINGS EN UNA SOLA QUERY
|
||||||
|
// =========================================================================
|
||||||
|
$table = $wpdb->prefix . 'roi_theme_component_settings';
|
||||||
|
$rows = $wpdb->get_results($wpdb->prepare(
|
||||||
|
"SELECT group_name, attribute_name, attribute_value
|
||||||
|
FROM {$table}
|
||||||
|
WHERE component_name = %s",
|
||||||
|
'adsense-placement'
|
||||||
|
));
|
||||||
|
|
||||||
|
if (empty($rows)) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organizar en array asociativo por grupo/atributo
|
||||||
|
$settings = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
if (!isset($settings[$row->group_name])) {
|
||||||
|
$settings[$row->group_name] = [];
|
||||||
|
}
|
||||||
|
// Decodificar valor
|
||||||
|
$value = $row->attribute_value;
|
||||||
|
if ($value === '1') $value = true;
|
||||||
|
elseif ($value === '0') $value = false;
|
||||||
|
else {
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||||
|
$value = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$settings[$row->group_name][$row->attribute_name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper para obtener valor con default
|
||||||
|
$get = function(string $group, string $attr, $default = null) use ($settings) {
|
||||||
|
return $settings[$group][$attr] ?? $default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// VALIDAR CONDICIONES GLOBALES
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// AdSense global deshabilitado
|
||||||
|
if ($get('visibility', 'is_enabled', false) !== true) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ads en busqueda deshabilitados
|
||||||
|
if ($get('search_results', 'search_ads_enabled', false) !== true) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publisher ID vacio
|
||||||
|
$publisherId = $get('content', 'publisher_id', '');
|
||||||
|
if (empty($publisherId)) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// VALIDAR EXCLUSIONES (igual que el resto del sistema)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Ocultar para usuarios logueados
|
||||||
|
if ($get('visibility', 'hide_for_logged_in', false) === true && is_user_logged_in()) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visibilidad por dispositivo
|
||||||
|
$isMobile = wp_is_mobile();
|
||||||
|
if ($isMobile && $get('visibility', 'show_on_mobile', true) !== true) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
if (!$isMobile && $get('visibility', 'show_on_desktop', true) !== true) {
|
||||||
|
return ['enabled' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// CONSTRUIR CONFIGURACION
|
||||||
|
// =========================================================================
|
||||||
|
return [
|
||||||
|
'enabled' => true,
|
||||||
|
'publisherId' => $publisherId,
|
||||||
|
'slots' => [
|
||||||
|
'auto' => $get('content', 'slot_auto', ''),
|
||||||
|
'inArticle' => $get('content', 'slot_inarticle', ''),
|
||||||
|
'autorelaxed' => $get('content', 'slot_autorelaxed', ''),
|
||||||
|
'display' => $get('content', 'slot_display', ''),
|
||||||
|
],
|
||||||
|
'topAd' => [
|
||||||
|
'enabled' => $get('search_results', 'search_top_ad_enabled', true) === true,
|
||||||
|
'format' => $get('search_results', 'search_top_ad_format', 'auto'),
|
||||||
|
],
|
||||||
|
'betweenAds' => [
|
||||||
|
'enabled' => $get('search_results', 'search_between_enabled', true) === true,
|
||||||
|
'max' => min(3, max(1, (int) $get('search_results', 'search_between_max', '1'))),
|
||||||
|
'format' => $get('search_results', 'search_between_format', 'in-article'),
|
||||||
|
'position' => $get('search_results', 'search_between_position', 'random'),
|
||||||
|
'every' => (int) $get('search_results', 'search_between_every', '5'),
|
||||||
|
],
|
||||||
|
'delay' => [
|
||||||
|
'enabled' => $get('forms', 'delay_enabled', true) === true,
|
||||||
|
'timeout' => (int) $get('forms', 'delay_timeout', '5000'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
60
minify-css.php
Normal file
60
minify-css.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Simple CSS Minifier Script
|
||||||
|
* Run from command line: php minify-css.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
function minify_css($css) {
|
||||||
|
// Remove comments
|
||||||
|
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
||||||
|
|
||||||
|
// Remove space after colons
|
||||||
|
$css = str_replace(': ', ':', $css);
|
||||||
|
|
||||||
|
// Remove whitespace
|
||||||
|
$css = str_replace(array("\r\n", "\r", "\n", "\t", ' ', ' ', ' '), '', $css);
|
||||||
|
|
||||||
|
// Remove space before and after specific characters
|
||||||
|
$css = preg_replace('/\s*([{};,>+~])\s*/', '$1', $css);
|
||||||
|
|
||||||
|
// Remove last semicolon before closing brace
|
||||||
|
$css = str_replace(';}', '}', $css);
|
||||||
|
|
||||||
|
// Trim
|
||||||
|
$css = trim($css);
|
||||||
|
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [
|
||||||
|
'Assets/Css/css-global-accessibility.css' => 'Assets/Css/css-global-accessibility.min.css',
|
||||||
|
'Assets/Css/style.css' => 'Assets/Css/style.min.css',
|
||||||
|
];
|
||||||
|
|
||||||
|
$base_path = __DIR__ . '/';
|
||||||
|
|
||||||
|
foreach ($files as $source => $dest) {
|
||||||
|
$source_path = $base_path . $source;
|
||||||
|
$dest_path = $base_path . $dest;
|
||||||
|
|
||||||
|
if (file_exists($source_path)) {
|
||||||
|
$css = file_get_contents($source_path);
|
||||||
|
$minified = minify_css($css);
|
||||||
|
|
||||||
|
file_put_contents($dest_path, $minified);
|
||||||
|
|
||||||
|
$original_size = strlen($css);
|
||||||
|
$minified_size = strlen($minified);
|
||||||
|
$savings = $original_size - $minified_size;
|
||||||
|
$percent = round(($savings / $original_size) * 100, 1);
|
||||||
|
|
||||||
|
echo "Minified: $source\n";
|
||||||
|
echo " Original: " . number_format($original_size) . " bytes\n";
|
||||||
|
echo " Minified: " . number_format($minified_size) . " bytes\n";
|
||||||
|
echo " Savings: " . number_format($savings) . " bytes ($percent%)\n\n";
|
||||||
|
} else {
|
||||||
|
echo "File not found: $source\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Done!\n";
|
||||||
Reference in New Issue
Block a user