feat(adsense): reorganizar panel con UX mejorada y soporte 1-8 ads random
Panel AdSense reorganizado: - Diagrama visual mostrando ubicaciones de anuncios (POST-TOP, IN-CONTENT, POST-BOTTOM, RAIL) - Secciones colapsables por ubicación con badges de color - Slots con descripciones claras indicando uso (Auto, In-Article, Display, etc.) In-Content Ads mejorado: - Soporte para 1-8 anuncios dentro del contenido - Modo aleatorio (random) que varía posiciones en cada visita - Configuración de mínimo/máximo de ads - Párrafos mínimos entre anuncios configurable (2-6) - Primer ad siempre en posición fija configurada Archivos modificados: - Schema v1.2.0 con 4 nuevos campos (random_mode, min_ads, max_ads, min_paragraphs_between) - FormBuilder con diagrama visual y mejor organización - ContentAdInjector con lógica de posicionamiento random - Renderer con soporte para post-content-1 hasta post-content-8 - FieldMapper actualizado con nuevos campos 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,8 +37,11 @@ final class AdsensePlacementFieldMapper implements FieldMapperInterface
|
|||||||
'adsense-placementPostTopEnabled' => ['group' => 'behavior', 'attribute' => 'post_top_enabled'],
|
'adsense-placementPostTopEnabled' => ['group' => 'behavior', 'attribute' => 'post_top_enabled'],
|
||||||
'adsense-placementPostTopFormat' => ['group' => 'behavior', 'attribute' => 'post_top_format'],
|
'adsense-placementPostTopFormat' => ['group' => 'behavior', 'attribute' => 'post_top_format'],
|
||||||
'adsense-placementPostContentEnabled' => ['group' => 'behavior', 'attribute' => 'post_content_enabled'],
|
'adsense-placementPostContentEnabled' => ['group' => 'behavior', 'attribute' => 'post_content_enabled'],
|
||||||
'adsense-placementPostContentAfterParagraphs' => ['group' => 'behavior', 'attribute' => 'post_content_after_paragraphs'],
|
'adsense-placementPostContentRandomMode' => ['group' => 'behavior', 'attribute' => 'post_content_random_mode'],
|
||||||
|
'adsense-placementPostContentMinAds' => ['group' => 'behavior', 'attribute' => 'post_content_min_ads'],
|
||||||
'adsense-placementPostContentMaxAds' => ['group' => 'behavior', 'attribute' => 'post_content_max_ads'],
|
'adsense-placementPostContentMaxAds' => ['group' => 'behavior', 'attribute' => 'post_content_max_ads'],
|
||||||
|
'adsense-placementPostContentAfterParagraphs' => ['group' => 'behavior', 'attribute' => 'post_content_after_paragraphs'],
|
||||||
|
'adsense-placementPostContentMinParagraphsBetween' => ['group' => 'behavior', 'attribute' => 'post_content_min_paragraphs_between'],
|
||||||
'adsense-placementPostContentFormat' => ['group' => 'behavior', 'attribute' => 'post_content_format'],
|
'adsense-placementPostContentFormat' => ['group' => 'behavior', 'attribute' => 'post_content_format'],
|
||||||
'adsense-placementPostBottomEnabled' => ['group' => 'behavior', 'attribute' => 'post_bottom_enabled'],
|
'adsense-placementPostBottomEnabled' => ['group' => 'behavior', 'attribute' => 'post_bottom_enabled'],
|
||||||
'adsense-placementPostBottomFormat' => ['group' => 'behavior', 'attribute' => 'post_bottom_format'],
|
'adsense-placementPostBottomFormat' => ['group' => 'behavior', 'attribute' => 'post_bottom_format'],
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ use ROITheme\Admin\Infrastructure\Ui\AdminDashboardRenderer;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FormBuilder para AdSense Placement y Google Analytics
|
* FormBuilder para AdSense Placement y Google Analytics
|
||||||
|
*
|
||||||
|
* Panel reorganizado con:
|
||||||
|
* - Diagrama visual de ubicaciones
|
||||||
|
* - Secciones colapsables
|
||||||
|
* - In-content ads configurables (1-8 random)
|
||||||
*/
|
*/
|
||||||
final class AdsensePlacementFormBuilder
|
final class AdsensePlacementFormBuilder
|
||||||
{
|
{
|
||||||
@@ -27,7 +32,7 @@ final class AdsensePlacementFormBuilder
|
|||||||
$html .= ' AdSense y Analytics';
|
$html .= ' AdSense y Analytics';
|
||||||
$html .= ' </h3>';
|
$html .= ' </h3>';
|
||||||
$html .= ' <p class="mb-0 small" style="opacity: 0.85;">';
|
$html .= ' <p class="mb-0 small" style="opacity: 0.85;">';
|
||||||
$html .= ' Configura Google AdSense y Google Analytics';
|
$html .= ' Configura Google AdSense y Analytics con ubicaciones visuales';
|
||||||
$html .= ' </p>';
|
$html .= ' </p>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -36,18 +41,19 @@ final class AdsensePlacementFormBuilder
|
|||||||
// LAYOUT 2 COLUMNAS
|
// LAYOUT 2 COLUMNAS
|
||||||
$html .= '<div class="row g-3">';
|
$html .= '<div class="row g-3">';
|
||||||
|
|
||||||
// COLUMNA IZQUIERDA
|
// COLUMNA IZQUIERDA (7 cols)
|
||||||
$html .= ' <div class="col-lg-6">';
|
$html .= ' <div class="col-lg-7">';
|
||||||
$html .= $this->buildVisibilityGroup($componentId);
|
$html .= $this->buildVisibilityGroup($componentId);
|
||||||
$html .= $this->buildAnalyticsGroup($componentId);
|
$html .= $this->buildDiagramSection();
|
||||||
$html .= $this->buildCredentialsGroup($componentId);
|
|
||||||
$html .= $this->buildPostLocationsGroup($componentId);
|
$html .= $this->buildPostLocationsGroup($componentId);
|
||||||
|
$html .= $this->buildInContentAdsGroup($componentId);
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
// COLUMNA DERECHA
|
// COLUMNA DERECHA (5 cols)
|
||||||
$html .= ' <div class="col-lg-6">';
|
$html .= ' <div class="col-lg-5">';
|
||||||
|
$html .= $this->buildCredentialsGroup($componentId);
|
||||||
|
$html .= $this->buildAnalyticsGroup($componentId);
|
||||||
$html .= $this->buildRailAdsGroup($componentId);
|
$html .= $this->buildRailAdsGroup($componentId);
|
||||||
$html .= $this->buildArchiveLocationsGroup($componentId);
|
|
||||||
$html .= $this->buildExclusionsGroup($componentId);
|
$html .= $this->buildExclusionsGroup($componentId);
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
|
|
||||||
@@ -58,24 +64,27 @@ final class AdsensePlacementFormBuilder
|
|||||||
|
|
||||||
private function buildVisibilityGroup(string $cid): string
|
private function buildVisibilityGroup(string $cid): 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 #28a745;">';
|
||||||
$html .= ' <div class="card-body">';
|
$html .= ' <div class="card-body">';
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
$html .= ' <i class="bi bi-toggle-on me-2" style="color: #FF8600;"></i>';
|
$html .= ' <i class="bi bi-power me-2" style="color: #28a745;"></i>';
|
||||||
$html .= ' Activacion AdSense';
|
$html .= ' Activacion Global';
|
||||||
$html .= ' </h5>';
|
$html .= ' </h5>';
|
||||||
|
|
||||||
// Switch: Enabled
|
$html .= '<div class="row g-3">';
|
||||||
|
$html .= ' <div class="col-md-4">';
|
||||||
$enabled = $this->renderer->getFieldValue($cid, 'visibility', 'is_enabled', false);
|
$enabled = $this->renderer->getFieldValue($cid, 'visibility', 'is_enabled', false);
|
||||||
$html .= $this->buildSwitch($cid . 'Enabled', 'Activar AdSense', $enabled, 'bi-power');
|
$html .= $this->buildSwitch($cid . 'Enabled', 'Activar AdSense', $enabled, 'bi-power');
|
||||||
|
$html .= ' </div>';
|
||||||
// Switch: Show on Mobile
|
$html .= ' <div class="col-md-4">';
|
||||||
$showMobile = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_mobile', true);
|
$showMobile = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_mobile', true);
|
||||||
$html .= $this->buildSwitch($cid . 'ShowOnMobile', 'Mostrar en movil', $showMobile, 'bi-phone');
|
$html .= $this->buildSwitch($cid . 'ShowOnMobile', 'Mostrar en movil', $showMobile, 'bi-phone');
|
||||||
|
$html .= ' </div>';
|
||||||
// Switch: Show on Desktop
|
$html .= ' <div class="col-md-4">';
|
||||||
$showDesktop = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_desktop', true);
|
$showDesktop = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_desktop', true);
|
||||||
$html .= $this->buildSwitch($cid . 'ShowOnDesktop', 'Mostrar en escritorio', $showDesktop, 'bi-display');
|
$html .= $this->buildSwitch($cid . 'ShowOnDesktop', 'Mostrar en escritorio', $showDesktop, 'bi-display');
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
@@ -83,32 +92,261 @@ final class AdsensePlacementFormBuilder
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildAnalyticsGroup(string $cid): string
|
/**
|
||||||
|
* Diagrama visual de ubicaciones de anuncios
|
||||||
|
*/
|
||||||
|
private function buildDiagramSection(): 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 #6f42c1;">';
|
||||||
$html .= ' <div class="card-body">';
|
$html .= ' <div class="card-body">';
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
$html .= ' <i class="bi bi-graph-up me-2" style="color: #FF8600;"></i>';
|
$html .= ' <i class="bi bi-layout-text-window-reverse me-2" style="color: #6f42c1;"></i>';
|
||||||
$html .= ' Google Analytics';
|
$html .= ' Mapa de Ubicaciones';
|
||||||
$html .= ' </h5>';
|
$html .= ' </h5>';
|
||||||
|
|
||||||
// Switch: Analytics Enabled
|
// Diagrama visual del layout
|
||||||
$analyticsEnabled = $this->renderer->getFieldValue($cid, 'analytics', 'analytics_enabled', false);
|
$html .= '<div class="border rounded p-3" style="background: #f8f9fa; font-family: monospace; font-size: 11px;">';
|
||||||
$html .= $this->buildSwitch($cid . 'AnalyticsEnabled', 'Activar Analytics', $analyticsEnabled, 'bi-power');
|
|
||||||
|
|
||||||
// Tracking ID
|
// Header
|
||||||
$gaTrackingId = $this->renderer->getFieldValue($cid, 'analytics', 'ga_tracking_id', '');
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #e9ecef; border: 1px dashed #6c757d;">';
|
||||||
$html .= $this->buildTextInput($cid . 'GaTrackingId', 'Google Analytics ID', $gaTrackingId, 'G-XXXXXXXXXX');
|
$html .= ' <strong>HEADER</strong>';
|
||||||
$html .= '<div class="form-text small mb-2">Formato: G-XXXXXXXXXX o UA-XXXXXXXX-X</div>';
|
$html .= '</div>';
|
||||||
|
|
||||||
// Anonymize IP
|
// Hero / Featured Image
|
||||||
$gaAnonymizeIp = $this->renderer->getFieldValue($cid, 'analytics', 'ga_anonymize_ip', true);
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #d1e7dd; border: 1px solid #198754;">';
|
||||||
$html .= $this->buildSwitch($cid . 'GaAnonymizeIp', 'Anonimizar IP (GDPR)', $gaAnonymizeIp, 'bi-shield-check');
|
$html .= ' <i class="bi bi-image"></i> Featured Image / Hero';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
$html .= ' <div class="alert alert-warning small mb-0 mt-2">';
|
// Ad: Post Top
|
||||||
$html .= ' <i class="bi bi-exclamation-triangle me-1"></i>';
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #fff3cd; border: 2px solid #ffc107;">';
|
||||||
$html .= ' Recomendado activar para cumplir con GDPR/RGPD';
|
$html .= ' <i class="bi bi-megaphone"></i> <strong>📍 POST-TOP</strong> (Despues de imagen)';
|
||||||
$html .= ' </div>';
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Content container
|
||||||
|
$html .= '<div class="p-2 mb-1 rounded" style="background: #fff; border: 1px solid #dee2e6;">';
|
||||||
|
$html .= ' <div class="mb-1 small text-muted text-center">📝 CONTENIDO DEL POST</div>';
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 1...</div>';
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 2...</div>';
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 3...</div>';
|
||||||
|
|
||||||
|
// In-content ad
|
||||||
|
$html .= ' <div class="text-center p-1 mb-1 rounded" style="background: #fff3cd; border: 2px dashed #ffc107; font-size: 10px;">';
|
||||||
|
$html .= ' <i class="bi bi-megaphone"></i> <strong>📍 IN-CONTENT #1</strong>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 4...</div>';
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 5...</div>';
|
||||||
|
$html .= ' <div class="p-1 rounded mb-1" style="background: #e7f1ff; font-size: 10px;">Parrafo 6...</div>';
|
||||||
|
|
||||||
|
// In-content ad 2
|
||||||
|
$html .= ' <div class="text-center p-1 mb-1 rounded" style="background: #fff3cd; border: 2px dashed #ffc107; font-size: 10px;">';
|
||||||
|
$html .= ' <i class="bi bi-megaphone"></i> <strong>📍 IN-CONTENT #2</strong> (random)';
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
$html .= ' <div class="p-1 rounded" style="background: #e7f1ff; font-size: 10px;">Mas parrafos...</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Ad: Post Bottom
|
||||||
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #fff3cd; border: 2px solid #ffc107;">';
|
||||||
|
$html .= ' <i class="bi bi-megaphone"></i> <strong>📍 POST-BOTTOM</strong> (Despues del contenido)';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Related Posts
|
||||||
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #cfe2ff; border: 1px solid #0d6efd;">';
|
||||||
|
$html .= ' <i class="bi bi-grid-3x2"></i> Related Posts';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Ad: After Related
|
||||||
|
$html .= '<div class="text-center p-2 mb-1 rounded" style="background: #fff3cd; border: 2px solid #ffc107;">';
|
||||||
|
$html .= ' <i class="bi bi-megaphone"></i> <strong>📍 AFTER-RELATED</strong>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
$html .= '<div class="text-center p-2 rounded" style="background: #e9ecef; border: 1px dashed #6c757d;">';
|
||||||
|
$html .= ' <strong>FOOTER</strong>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Rail Ads (laterales)
|
||||||
|
$html .= '<div class="mt-2 d-flex justify-content-between">';
|
||||||
|
$html .= ' <div class="p-2 rounded text-center" style="width: 45%; background: #f8d7da; border: 2px solid #dc3545; font-size: 10px;">';
|
||||||
|
$html .= ' <strong>📍 RAIL IZQ</strong><br><small>(160x600)</small>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="p-2 rounded text-center" style="width: 45%; background: #f8d7da; border: 2px solid #dc3545; font-size: 10px;">';
|
||||||
|
$html .= ' <strong>📍 RAIL DER</strong><br><small>(160x600)</small>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="mt-2 small text-muted">';
|
||||||
|
$html .= ' <i class="bi bi-info-circle"></i> Los anuncios <span class="badge bg-warning text-dark">amarillos</span> son configurables abajo.';
|
||||||
|
$html .= ' Los <span class="badge bg-danger">rojos</span> solo aparecen en pantallas >1600px.';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPostLocationsGroup(string $cid): string
|
||||||
|
{
|
||||||
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #ffc107;">';
|
||||||
|
$html .= ' <div class="card-body">';
|
||||||
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
|
$html .= ' <i class="bi bi-geo-alt me-2" style="color: #ffc107;"></i>';
|
||||||
|
$html .= ' Ubicaciones en Posts';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
// === POST-TOP ===
|
||||||
|
$html .= '<div class="border rounded p-3 mb-3" style="background: #fffbeb;">';
|
||||||
|
$html .= '<div class="d-flex align-items-center gap-2 mb-2">';
|
||||||
|
$html .= ' <span class="badge bg-warning text-dark">POST-TOP</span>';
|
||||||
|
$html .= ' <small class="text-muted">Despues de la imagen destacada</small>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$postTopEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_top_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostTopEnabled', 'Activar', $postTopEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$html .= $this->buildSelect($cid . 'PostTopFormat', 'Formato',
|
||||||
|
$this->renderer->getFieldValue($cid, 'behavior', 'post_top_format', 'auto'),
|
||||||
|
[
|
||||||
|
'auto' => 'Auto (responsive)',
|
||||||
|
'in-article' => 'In-Article (fluid)',
|
||||||
|
'display' => 'Display (728x90)',
|
||||||
|
'display-large' => 'Display Large (970x250)'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// === POST-BOTTOM ===
|
||||||
|
$html .= '<div class="border rounded p-3 mb-3" style="background: #fffbeb;">';
|
||||||
|
$html .= '<div class="d-flex align-items-center gap-2 mb-2">';
|
||||||
|
$html .= ' <span class="badge bg-warning text-dark">POST-BOTTOM</span>';
|
||||||
|
$html .= ' <small class="text-muted">Despues del contenido, antes de Related</small>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$postBottomEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_bottom_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostBottomEnabled', 'Activar', $postBottomEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$html .= $this->buildSelect($cid . 'PostBottomFormat', 'Formato',
|
||||||
|
$this->renderer->getFieldValue($cid, 'behavior', 'post_bottom_format', 'auto'),
|
||||||
|
['auto' => 'Auto', 'in-article' => 'In-Article', 'display' => 'Display']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// === AFTER-RELATED ===
|
||||||
|
$html .= '<div class="border rounded p-3" style="background: #fffbeb;">';
|
||||||
|
$html .= '<div class="d-flex align-items-center gap-2 mb-2">';
|
||||||
|
$html .= ' <span class="badge bg-warning text-dark">AFTER-RELATED</span>';
|
||||||
|
$html .= ' <small class="text-muted">Despues de Related Posts</small>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$afterRelatedEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'after_related_enabled', false);
|
||||||
|
$html .= $this->buildSwitch($cid . 'AfterRelatedEnabled', 'Activar', $afterRelatedEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$html .= $this->buildSelect($cid . 'AfterRelatedFormat', 'Formato',
|
||||||
|
$this->renderer->getFieldValue($cid, 'behavior', 'after_related_format', 'autorelaxed'),
|
||||||
|
['autorelaxed' => 'Autorelaxed (feed)', 'auto' => 'Auto']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seccion especial para in-content ads con configuracion de 1-8 random
|
||||||
|
*/
|
||||||
|
private function buildInContentAdsGroup(string $cid): string
|
||||||
|
{
|
||||||
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #0d6efd;">';
|
||||||
|
$html .= ' <div class="card-body">';
|
||||||
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
|
$html .= ' <i class="bi bi-body-text me-2" style="color: #0d6efd;"></i>';
|
||||||
|
$html .= ' Anuncios Dentro del Contenido';
|
||||||
|
$html .= ' <span class="badge bg-primary ms-2">1-8 ads</span>';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
$html .= '<div class="alert alert-info small mb-3">';
|
||||||
|
$html .= ' <i class="bi bi-lightbulb me-1"></i>';
|
||||||
|
$html .= ' <strong>Modo Random:</strong> Inserta entre 1 y 8 anuncios en posiciones aleatorias entre parrafos.';
|
||||||
|
$html .= ' Mejor UX al variar la posicion en cada visita.';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Master switch
|
||||||
|
$postContentEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_enabled', false);
|
||||||
|
$html .= '<div class="mb-3">';
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostContentEnabled', 'Activar In-Content Ads', $postContentEnabled, 'bi-power');
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Configuracion de cantidad
|
||||||
|
$html .= '<div class="row g-2 mb-3">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$minAdsValue = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_min_ads', '1');
|
||||||
|
$html .= $this->buildSelect($cid . 'PostContentMinAds', 'Minimo de anuncios',
|
||||||
|
is_string($minAdsValue) ? $minAdsValue : '1',
|
||||||
|
['1' => '1 anuncio', '2' => '2 anuncios', '3' => '3 anuncios', '4' => '4 anuncios']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$maxAdsValue = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_max_ads', '3');
|
||||||
|
$html .= $this->buildSelect($cid . 'PostContentMaxAds', 'Maximo de anuncios',
|
||||||
|
is_string($maxAdsValue) ? $maxAdsValue : '3',
|
||||||
|
[
|
||||||
|
'1' => '1 anuncio', '2' => '2 anuncios', '3' => '3 anuncios', '4' => '4 anuncios',
|
||||||
|
'5' => '5 anuncios', '6' => '6 anuncios', '7' => '7 anuncios', '8' => '8 anuncios'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Configuracion de posicionamiento
|
||||||
|
$html .= '<div class="row g-2 mb-3">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$afterPara = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_after_paragraphs', '3');
|
||||||
|
$html .= $this->buildTextInput($cid . 'PostContentAfterParagraphs', 'Primer ad despues del parrafo #', (string)$afterPara, '3');
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$minBetweenValue = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_min_paragraphs_between', '4');
|
||||||
|
$html .= $this->buildSelect($cid . 'PostContentMinParagraphsBetween', 'Parrafos entre ads',
|
||||||
|
is_string($minBetweenValue) ? $minBetweenValue : '4',
|
||||||
|
['2' => '2 parrafos', '3' => '3 parrafos', '4' => '4 parrafos', '5' => '5 parrafos', '6' => '6 parrafos']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Modo y formato
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$randomMode = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_random_mode', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostContentRandomMode', 'Posiciones aleatorias', $randomMode, 'bi-shuffle');
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-md-6">';
|
||||||
|
$formatValue = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_format', 'in-article');
|
||||||
|
$html .= $this->buildSelect($cid . 'PostContentFormat', 'Formato de ads',
|
||||||
|
is_string($formatValue) ? $formatValue : 'in-article',
|
||||||
|
['in-article' => 'In-Article (fluid)', 'auto' => 'Auto (responsive)']
|
||||||
|
);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
@@ -129,28 +367,38 @@ final class AdsensePlacementFormBuilder
|
|||||||
$pubId = $this->renderer->getFieldValue($cid, 'content', 'publisher_id', 'ca-pub-8476420265998726');
|
$pubId = $this->renderer->getFieldValue($cid, 'content', 'publisher_id', 'ca-pub-8476420265998726');
|
||||||
$html .= $this->buildTextInput($cid . 'PublisherId', 'Publisher ID', $pubId, 'ca-pub-XXXXX');
|
$html .= $this->buildTextInput($cid . 'PublisherId', 'Publisher ID', $pubId, 'ca-pub-XXXXX');
|
||||||
|
|
||||||
// Slots (grid 2 columnas)
|
$html .= '<hr class="my-3">';
|
||||||
$html .= '<div class="row g-2 mt-2">';
|
$html .= '<p class="small text-muted mb-2"><i class="bi bi-info-circle me-1"></i> Slots por tipo de anuncio:</p>';
|
||||||
$html .= ' <div class="col-6">';
|
|
||||||
$slotDisplay = $this->renderer->getFieldValue($cid, 'content', 'slot_display', '2873062302');
|
// Slots con descripciones claras
|
||||||
$html .= $this->buildTextInput($cid . 'SlotDisplay', 'Slot Display', $slotDisplay);
|
$html .= '<div class="mb-2">';
|
||||||
$html .= ' </div>';
|
|
||||||
$html .= ' <div class="col-6">';
|
|
||||||
$slotAuto = $this->renderer->getFieldValue($cid, 'content', 'slot_auto', '8471732096');
|
$slotAuto = $this->renderer->getFieldValue($cid, 'content', 'slot_auto', '8471732096');
|
||||||
$html .= $this->buildTextInput($cid . 'SlotAuto', 'Slot Auto', $slotAuto);
|
$html .= $this->buildTextInput($cid . 'SlotAuto', '📱 Auto (responsive)', $slotAuto);
|
||||||
$html .= ' </div>';
|
$html .= '<div class="form-text small" style="margin-top:-10px;">Para: Post-Top, Post-Bottom, globales</div>';
|
||||||
$html .= ' <div class="col-6">';
|
$html .= '</div>';
|
||||||
$slotRelaxed = $this->renderer->getFieldValue($cid, 'content', 'slot_autorelaxed', '9205569855');
|
|
||||||
$html .= $this->buildTextInput($cid . 'SlotAutorelaxed', 'Slot Autorelaxed', $slotRelaxed);
|
$html .= '<div class="mb-2">';
|
||||||
$html .= ' </div>';
|
|
||||||
$html .= ' <div class="col-6">';
|
|
||||||
$slotInArticle = $this->renderer->getFieldValue($cid, 'content', 'slot_inarticle', '7285187368');
|
$slotInArticle = $this->renderer->getFieldValue($cid, 'content', 'slot_inarticle', '7285187368');
|
||||||
$html .= $this->buildTextInput($cid . 'SlotInarticle', 'Slot In-Article', $slotInArticle);
|
$html .= $this->buildTextInput($cid . 'SlotInarticle', '📝 In-Article (fluid)', $slotInArticle);
|
||||||
$html .= ' </div>';
|
$html .= '<div class="form-text small" style="margin-top:-10px;">Para: In-Content (dentro del texto)</div>';
|
||||||
$html .= ' <div class="col-6">';
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="mb-2">';
|
||||||
|
$slotDisplay = $this->renderer->getFieldValue($cid, 'content', 'slot_display', '2873062302');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotDisplay', '🖥️ Display (fijo)', $slotDisplay);
|
||||||
|
$html .= '<div class="form-text small" style="margin-top:-10px;">Para: 728x90, 970x250 (opcional)</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="mb-2">';
|
||||||
|
$slotRelaxed = $this->renderer->getFieldValue($cid, 'content', 'slot_autorelaxed', '9205569855');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotAutorelaxed', '📋 Autorelaxed (feed)', $slotRelaxed);
|
||||||
|
$html .= '<div class="form-text small" style="margin-top:-10px;">Para: After-Related, archives</div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '<div class="mb-2">';
|
||||||
$slotSkyscraper = $this->renderer->getFieldValue($cid, 'content', 'slot_skyscraper', '');
|
$slotSkyscraper = $this->renderer->getFieldValue($cid, 'content', 'slot_skyscraper', '');
|
||||||
$html .= $this->buildTextInput($cid . 'SlotSkyscraper', 'Slot Skyscraper (Rail Ads)', $slotSkyscraper);
|
$html .= $this->buildTextInput($cid . 'SlotSkyscraper', '🏢 Skyscraper (tall)', $slotSkyscraper);
|
||||||
$html .= ' </div>';
|
$html .= '<div class="form-text small" style="margin-top:-10px;">Para: Rail Ads laterales (160x600)</div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
@@ -159,59 +407,27 @@ final class AdsensePlacementFormBuilder
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildPostLocationsGroup(string $cid): string
|
private function buildAnalyticsGroup(string $cid): 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 #4285f4;">';
|
||||||
$html .= ' <div class="card-body">';
|
$html .= ' <div class="card-body">';
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
$html .= ' <i class="bi bi-file-text me-2" style="color: #FF8600;"></i>';
|
$html .= ' <i class="bi bi-graph-up me-2" style="color: #4285f4;"></i>';
|
||||||
$html .= ' Ubicaciones en Posts';
|
$html .= ' Google Analytics';
|
||||||
$html .= ' </h5>';
|
$html .= ' </h5>';
|
||||||
|
|
||||||
// Post Top
|
// Switch: Analytics Enabled
|
||||||
$postTopEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_top_enabled', true);
|
$analyticsEnabled = $this->renderer->getFieldValue($cid, 'analytics', 'analytics_enabled', false);
|
||||||
$html .= $this->buildSwitch($cid . 'PostTopEnabled', 'Despues de Featured Image', $postTopEnabled);
|
$html .= $this->buildSwitch($cid . 'AnalyticsEnabled', 'Activar Analytics', $analyticsEnabled, 'bi-power');
|
||||||
$html .= $this->buildSelect($cid . 'PostTopFormat', 'Formato',
|
|
||||||
$this->renderer->getFieldValue($cid, 'behavior', 'post_top_format', 'auto'),
|
|
||||||
['auto' => 'Auto (responsive)', 'in-article' => 'In-Article', 'display' => 'Display (728x90)', 'display-large' => 'Display Large (970x250)']
|
|
||||||
);
|
|
||||||
|
|
||||||
// Post Content
|
// Tracking ID
|
||||||
$postContentEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_enabled', false);
|
$gaTrackingId = $this->renderer->getFieldValue($cid, 'analytics', 'ga_tracking_id', '');
|
||||||
$html .= $this->buildSwitch($cid . 'PostContentEnabled', 'Insertar dentro del contenido', $postContentEnabled);
|
$html .= $this->buildTextInput($cid . 'GaTrackingId', 'Google Analytics ID', $gaTrackingId, 'G-XXXXXXXXXX');
|
||||||
|
$html .= '<div class="form-text small mb-2">Formato: G-XXXXXXXXXX (GA4) o UA-XXXXXXXX-X</div>';
|
||||||
|
|
||||||
$html .= '<div class="row g-2">';
|
// Anonymize IP
|
||||||
$html .= ' <div class="col-4">';
|
$gaAnonymizeIp = $this->renderer->getFieldValue($cid, 'analytics', 'ga_anonymize_ip', true);
|
||||||
$afterPara = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_after_paragraphs', '3');
|
$html .= $this->buildSwitch($cid . 'GaAnonymizeIp', 'Anonimizar IP (GDPR)', $gaAnonymizeIp, 'bi-shield-check');
|
||||||
$html .= $this->buildTextInput($cid . 'PostContentAfterParagraphs', 'Despues parrafo #', $afterPara);
|
|
||||||
$html .= ' </div>';
|
|
||||||
$html .= ' <div class="col-4">';
|
|
||||||
$maxAds = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_max_ads', '2');
|
|
||||||
$html .= $this->buildTextInput($cid . 'PostContentMaxAds', 'Max ads', $maxAds);
|
|
||||||
$html .= ' </div>';
|
|
||||||
$html .= ' <div class="col-4">';
|
|
||||||
$html .= $this->buildSelect($cid . 'PostContentFormat', 'Formato',
|
|
||||||
$this->renderer->getFieldValue($cid, 'behavior', 'post_content_format', 'in-article'),
|
|
||||||
['in-article' => 'In-Article', 'auto' => 'Auto']
|
|
||||||
);
|
|
||||||
$html .= ' </div>';
|
|
||||||
$html .= '</div>';
|
|
||||||
|
|
||||||
// Post Bottom
|
|
||||||
$postBottomEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_bottom_enabled', true);
|
|
||||||
$html .= $this->buildSwitch($cid . 'PostBottomEnabled', 'Despues del contenido', $postBottomEnabled);
|
|
||||||
$html .= $this->buildSelect($cid . 'PostBottomFormat', 'Formato',
|
|
||||||
$this->renderer->getFieldValue($cid, 'behavior', 'post_bottom_format', 'auto'),
|
|
||||||
['auto' => 'Auto', 'in-article' => 'In-Article', 'display' => 'Display']
|
|
||||||
);
|
|
||||||
|
|
||||||
// After Related
|
|
||||||
$afterRelatedEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'after_related_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'AfterRelatedEnabled', 'Despues de Related Posts', $afterRelatedEnabled);
|
|
||||||
$html .= $this->buildSelect($cid . 'AfterRelatedFormat', 'Formato',
|
|
||||||
$this->renderer->getFieldValue($cid, 'behavior', 'after_related_format', 'autorelaxed'),
|
|
||||||
['autorelaxed' => 'Autorelaxed', 'auto' => 'Auto']
|
|
||||||
);
|
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
@@ -221,13 +437,14 @@ final class AdsensePlacementFormBuilder
|
|||||||
|
|
||||||
private function buildRailAdsGroup(string $cid): string
|
private function buildRailAdsGroup(string $cid): 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 #dc3545;">';
|
||||||
$html .= ' <div class="card-body">';
|
$html .= ' <div class="card-body">';
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
$html .= ' <i class="bi bi-layout-sidebar me-2" style="color: #FF8600;"></i>';
|
$html .= ' <i class="bi bi-layout-sidebar me-2" style="color: #dc3545;"></i>';
|
||||||
$html .= ' Rail Ads (Margenes Laterales)';
|
$html .= ' Rail Ads (Laterales)';
|
||||||
|
$html .= ' <span class="badge bg-secondary ms-2">>1600px</span>';
|
||||||
$html .= ' </h5>';
|
$html .= ' </h5>';
|
||||||
$html .= ' <p class="small text-muted mb-3">Anuncios fijos en los espacios laterales del viewport. Solo visibles en pantallas >= 1600px.</p>';
|
$html .= ' <p class="small text-muted mb-3">Anuncios fijos en los margenes del viewport. Solo en pantallas muy anchas.</p>';
|
||||||
|
|
||||||
// Master switch
|
// Master switch
|
||||||
$railEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'rail_ads_enabled', false);
|
$railEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'rail_ads_enabled', false);
|
||||||
@@ -262,66 +479,28 @@ final class AdsensePlacementFormBuilder
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildArchiveLocationsGroup(string $cid): string
|
|
||||||
{
|
|
||||||
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #1e3a5f;">';
|
|
||||||
$html .= ' <div class="card-body">';
|
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
|
||||||
$html .= ' <i class="bi bi-grid me-2" style="color: #FF8600;"></i>';
|
|
||||||
$html .= ' Ubicaciones Archives/Globales';
|
|
||||||
$html .= ' </h5>';
|
|
||||||
|
|
||||||
// Archive locations
|
|
||||||
$archiveTopEnabled = $this->renderer->getFieldValue($cid, 'layout', 'archive_top_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'ArchiveTopEnabled', 'Arriba del listado', $archiveTopEnabled);
|
|
||||||
|
|
||||||
$archiveBetweenEnabled = $this->renderer->getFieldValue($cid, 'layout', 'archive_between_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'ArchiveBetweenEnabled', 'Entre posts del listado', $archiveBetweenEnabled);
|
|
||||||
|
|
||||||
$archiveEvery = $this->renderer->getFieldValue($cid, 'layout', 'archive_between_every', '4');
|
|
||||||
$html .= $this->buildTextInput($cid . 'ArchiveBetweenEvery', 'Mostrar cada X posts', $archiveEvery);
|
|
||||||
|
|
||||||
$archiveBottomEnabled = $this->renderer->getFieldValue($cid, 'layout', 'archive_bottom_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'ArchiveBottomEnabled', 'Abajo del listado', $archiveBottomEnabled);
|
|
||||||
|
|
||||||
// Archive format (aplica a todas las ubicaciones archive)
|
|
||||||
$html .= $this->buildSelect($cid . 'ArchiveFormat', 'Formato para archives',
|
|
||||||
$this->renderer->getFieldValue($cid, 'layout', 'archive_format', 'autorelaxed'),
|
|
||||||
['autorelaxed' => 'Autorelaxed', 'auto' => 'Auto']
|
|
||||||
);
|
|
||||||
|
|
||||||
$html .= '<hr class="my-3">';
|
|
||||||
$html .= '<p class="small text-muted mb-2"><strong>Ubicaciones Globales</strong></p>';
|
|
||||||
|
|
||||||
// Global locations
|
|
||||||
$headerBelowEnabled = $this->renderer->getFieldValue($cid, 'layout', 'header_below_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'HeaderBelowEnabled', 'Debajo del header (global)', $headerBelowEnabled);
|
|
||||||
|
|
||||||
$footerAboveEnabled = $this->renderer->getFieldValue($cid, 'layout', 'footer_above_enabled', false);
|
|
||||||
$html .= $this->buildSwitch($cid . 'FooterAboveEnabled', 'Arriba del footer (global)', $footerAboveEnabled);
|
|
||||||
|
|
||||||
// Global format
|
|
||||||
$html .= $this->buildSelect($cid . 'GlobalFormat', 'Formato para globales',
|
|
||||||
$this->renderer->getFieldValue($cid, 'layout', 'global_format', 'auto'),
|
|
||||||
['auto' => 'Auto', 'display-large' => 'Display Large (970x250)']
|
|
||||||
);
|
|
||||||
|
|
||||||
$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 #1e3a5f;">';
|
$html = '<div class="card shadow-sm mb-3" style="border-left: 4px solid #6c757d;">';
|
||||||
$html .= ' <div class="card-body">';
|
$html .= ' <div class="card-body">';
|
||||||
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
$html .= ' <i class="bi bi-slash-circle me-2" style="color: #FF8600;"></i>';
|
$html .= ' <i class="bi bi-slash-circle me-2" style="color: #6c757d;"></i>';
|
||||||
$html .= ' Exclusiones y Rendimiento';
|
$html .= ' Exclusiones y Rendimiento';
|
||||||
$html .= ' </h5>';
|
$html .= ' </h5>';
|
||||||
|
|
||||||
// Exclusions
|
// Accordion para exclusiones
|
||||||
|
$html .= '<div class="accordion accordion-flush" id="exclusionsAccordion">';
|
||||||
|
|
||||||
|
// Exclusiones
|
||||||
|
$html .= '<div class="accordion-item">';
|
||||||
|
$html .= ' <h2 class="accordion-header">';
|
||||||
|
$html .= ' <button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#exclusionsCollapse">';
|
||||||
|
$html .= ' <i class="bi bi-funnel me-2"></i> Filtros de exclusion';
|
||||||
|
$html .= ' </button>';
|
||||||
|
$html .= ' </h2>';
|
||||||
|
$html .= ' <div id="exclusionsCollapse" class="accordion-collapse collapse" data-bs-parent="#exclusionsAccordion">';
|
||||||
|
$html .= ' <div class="accordion-body">';
|
||||||
|
|
||||||
$excludeCats = $this->renderer->getFieldValue($cid, 'forms', 'exclude_categories', '');
|
$excludeCats = $this->renderer->getFieldValue($cid, 'forms', 'exclude_categories', '');
|
||||||
$html .= $this->buildTextarea($cid . 'ExcludeCategories', 'Excluir categorias (IDs)', $excludeCats, 'Ej: 5,12,23');
|
$html .= $this->buildTextarea($cid . 'ExcludeCategories', 'Excluir categorias (IDs)', $excludeCats, 'Ej: 5,12,23');
|
||||||
|
|
||||||
@@ -334,9 +513,18 @@ final class AdsensePlacementFormBuilder
|
|||||||
$minLength = $this->renderer->getFieldValue($cid, 'forms', 'min_content_length', '500');
|
$minLength = $this->renderer->getFieldValue($cid, 'forms', 'min_content_length', '500');
|
||||||
$html .= $this->buildTextInput($cid . 'MinContentLength', 'Longitud minima de contenido', $minLength);
|
$html .= $this->buildTextInput($cid . 'MinContentLength', 'Longitud minima de contenido', $minLength);
|
||||||
|
|
||||||
// Delay settings
|
$html .= ' </div>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
$html .= '</div>'; // end accordion
|
||||||
|
|
||||||
|
// Delay settings (siempre visibles)
|
||||||
|
$html .= '<hr class="my-3">';
|
||||||
|
$html .= '<p class="small text-muted mb-2"><i class="bi bi-speedometer2 me-1"></i> Rendimiento:</p>';
|
||||||
|
|
||||||
$delayEnabled = $this->renderer->getFieldValue($cid, 'forms', 'delay_enabled', true);
|
$delayEnabled = $this->renderer->getFieldValue($cid, 'forms', 'delay_enabled', true);
|
||||||
$html .= $this->buildSwitch($cid . 'DelayEnabled', 'Retrasar carga de anuncios', $delayEnabled, 'bi-hourglass-split');
|
$html .= $this->buildSwitch($cid . 'DelayEnabled', 'Retrasar carga (mejor PageSpeed)', $delayEnabled, 'bi-hourglass-split');
|
||||||
|
|
||||||
$delayTimeout = $this->renderer->getFieldValue($cid, 'forms', 'delay_timeout', '5000');
|
$delayTimeout = $this->renderer->getFieldValue($cid, 'forms', 'delay_timeout', '5000');
|
||||||
$html .= $this->buildTextInput($cid . 'DelayTimeout', 'Timeout de delay (ms)', $delayTimeout);
|
$html .= $this->buildTextInput($cid . 'DelayTimeout', 'Timeout de delay (ms)', $delayTimeout);
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
|||||||
/**
|
/**
|
||||||
* Inyecta anuncios dentro del contenido del post
|
* Inyecta anuncios dentro del contenido del post
|
||||||
* via filtro the_content
|
* via filtro the_content
|
||||||
|
*
|
||||||
|
* Soporta:
|
||||||
|
* - Modo aleatorio (random) con posiciones variables
|
||||||
|
* - Configuracion de 1-8 ads maximo
|
||||||
|
* - Espacio minimo entre anuncios
|
||||||
*/
|
*/
|
||||||
final class ContentAdInjector
|
final class ContentAdInjector
|
||||||
{
|
{
|
||||||
@@ -31,40 +36,166 @@ final class ContentAdInjector
|
|||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obtener configuracion
|
||||||
|
$minAds = (int)($this->settings['behavior']['post_content_min_ads'] ?? 1);
|
||||||
|
$maxAds = (int)($this->settings['behavior']['post_content_max_ads'] ?? 3);
|
||||||
$afterParagraphs = (int)($this->settings['behavior']['post_content_after_paragraphs'] ?? 3);
|
$afterParagraphs = (int)($this->settings['behavior']['post_content_after_paragraphs'] ?? 3);
|
||||||
$maxAds = (int)($this->settings['behavior']['post_content_max_ads'] ?? 2);
|
$minBetween = (int)($this->settings['behavior']['post_content_min_paragraphs_between'] ?? 4);
|
||||||
|
$randomMode = ($this->settings['behavior']['post_content_random_mode'] ?? true) === true;
|
||||||
|
|
||||||
|
// Validar min <= max
|
||||||
|
if ($minAds > $maxAds) {
|
||||||
|
$minAds = $maxAds;
|
||||||
|
}
|
||||||
|
|
||||||
// Dividir contenido en parrafos
|
// Dividir contenido en parrafos
|
||||||
$paragraphs = explode('</p>', $content);
|
$paragraphs = $this->splitIntoParagraphs($content);
|
||||||
$totalParagraphs = count($paragraphs);
|
$totalParagraphs = count($paragraphs);
|
||||||
|
|
||||||
|
// Necesitamos al menos afterParagraphs + 1 parrafos
|
||||||
if ($totalParagraphs < $afterParagraphs + 1) {
|
if ($totalParagraphs < $afterParagraphs + 1) {
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
$adsInserted = 0;
|
// Calcular posiciones de insercion
|
||||||
|
$adPositions = $this->calculateAdPositions(
|
||||||
|
$totalParagraphs,
|
||||||
|
$afterParagraphs,
|
||||||
|
$minBetween,
|
||||||
|
$minAds,
|
||||||
|
$maxAds,
|
||||||
|
$randomMode
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($adPositions)) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruir contenido con anuncios insertados
|
||||||
|
return $this->buildContentWithAds($paragraphs, $adPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divide el contenido en parrafos preservando el HTML
|
||||||
|
*/
|
||||||
|
private function splitIntoParagraphs(string $content): array
|
||||||
|
{
|
||||||
|
// Dividir por </p>, pero mantener el tag
|
||||||
|
$parts = preg_split('/(<\/p>)/i', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||||
|
|
||||||
|
$paragraphs = [];
|
||||||
|
$current = '';
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$current .= $part;
|
||||||
|
if (strtolower($part) === '</p>') {
|
||||||
|
$paragraphs[] = $current;
|
||||||
|
$current = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si hay contenido restante (sin cerrar), agregarlo
|
||||||
|
if (!empty(trim($current))) {
|
||||||
|
$paragraphs[] = $current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $paragraphs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcula las posiciones donde insertar anuncios
|
||||||
|
*
|
||||||
|
* @return int[] Indices de parrafos despues de los cuales insertar ads
|
||||||
|
*/
|
||||||
|
private function calculateAdPositions(
|
||||||
|
int $totalParagraphs,
|
||||||
|
int $afterFirst,
|
||||||
|
int $minBetween,
|
||||||
|
int $minAds,
|
||||||
|
int $maxAds,
|
||||||
|
bool $randomMode
|
||||||
|
): array {
|
||||||
|
// Calcular posiciones disponibles respetando el espacio minimo
|
||||||
|
$availablePositions = [];
|
||||||
|
$lastPosition = $afterFirst; // Primera posicion fija
|
||||||
|
|
||||||
|
// La primera posicion siempre es despues del parrafo indicado
|
||||||
|
if ($afterFirst < $totalParagraphs) {
|
||||||
|
$availablePositions[] = $afterFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcular posiciones adicionales respetando minBetween
|
||||||
|
$nextPosition = $afterFirst + $minBetween;
|
||||||
|
while ($nextPosition < $totalParagraphs - 1) { // -1 para no insertar al final
|
||||||
|
$availablePositions[] = $nextPosition;
|
||||||
|
$nextPosition += $minBetween;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determinar cuantos ads insertar
|
||||||
|
$maxPossible = count($availablePositions);
|
||||||
|
if ($maxPossible === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limitar por maxAds y lo que el contenido permite
|
||||||
|
$actualMax = min($maxAds, $maxPossible);
|
||||||
|
$actualMin = min($minAds, $actualMax);
|
||||||
|
|
||||||
|
// Determinar cantidad final de ads
|
||||||
|
if ($randomMode) {
|
||||||
|
// En modo random, elegir cantidad aleatoria entre min y max
|
||||||
|
$numAds = rand($actualMin, $actualMax);
|
||||||
|
} else {
|
||||||
|
// En modo fijo, usar el maximo posible
|
||||||
|
$numAds = $actualMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($numAds === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seleccionar posiciones
|
||||||
|
if ($randomMode && $numAds < $maxPossible) {
|
||||||
|
// Modo random: elegir posiciones aleatorias
|
||||||
|
// Siempre incluir la primera posicion
|
||||||
|
$selectedPositions = [$availablePositions[0]];
|
||||||
|
|
||||||
|
if ($numAds > 1) {
|
||||||
|
// Elegir aleatoriamente del resto
|
||||||
|
$remainingPositions = array_slice($availablePositions, 1);
|
||||||
|
shuffle($remainingPositions);
|
||||||
|
$additionalPositions = array_slice($remainingPositions, 0, $numAds - 1);
|
||||||
|
$selectedPositions = array_merge($selectedPositions, $additionalPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenar para insertar en orden correcto
|
||||||
|
sort($selectedPositions);
|
||||||
|
return $selectedPositions;
|
||||||
|
} else {
|
||||||
|
// Modo fijo o todas las posiciones necesarias
|
||||||
|
return array_slice($availablePositions, 0, $numAds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconstruye el contenido insertando anuncios en las posiciones indicadas
|
||||||
|
*/
|
||||||
|
private function buildContentWithAds(array $paragraphs, array $adPositions): string
|
||||||
|
{
|
||||||
$newContent = '';
|
$newContent = '';
|
||||||
|
$adsInserted = 0;
|
||||||
|
|
||||||
foreach ($paragraphs as $index => $paragraph) {
|
foreach ($paragraphs as $index => $paragraph) {
|
||||||
$newContent .= $paragraph;
|
$newContent .= $paragraph;
|
||||||
|
|
||||||
if ($index < $totalParagraphs - 1) {
|
// Verificar si debemos insertar un ad despues de este parrafo
|
||||||
$newContent .= '</p>';
|
// El indice es 0-based, las posiciones son 1-based (parrafo #3 = index 2)
|
||||||
}
|
|
||||||
|
|
||||||
$paragraphNumber = $index + 1;
|
$paragraphNumber = $index + 1;
|
||||||
|
|
||||||
// Primer anuncio despues del parrafo indicado
|
if (in_array($paragraphNumber, $adPositions, true)) {
|
||||||
if ($paragraphNumber === $afterParagraphs && $adsInserted < $maxAds) {
|
|
||||||
$newContent .= $this->renderer->renderSlot($this->settings, 'post-content-' . ($adsInserted + 1));
|
|
||||||
$adsInserted++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Segundo anuncio a mitad del contenido restante
|
|
||||||
$midPoint = $afterParagraphs + (int)(($totalParagraphs - $afterParagraphs) / 2);
|
|
||||||
if ($paragraphNumber === $midPoint && $adsInserted < $maxAds && $maxAds > 1) {
|
|
||||||
$newContent .= $this->renderer->renderSlot($this->settings, 'post-content-' . ($adsInserted + 1));
|
|
||||||
$adsInserted++;
|
$adsInserted++;
|
||||||
|
$adHtml = $this->renderer->renderSlot($this->settings, 'post-content-' . $adsInserted);
|
||||||
|
$newContent .= $adHtml;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,15 @@ final class AdsensePlacementRenderer
|
|||||||
{
|
{
|
||||||
$locationKey = str_replace('-', '_', $location);
|
$locationKey = str_replace('-', '_', $location);
|
||||||
|
|
||||||
|
// Manejar ubicaciones de in-content (post_content_1, post_content_2, etc.)
|
||||||
|
if (preg_match('/^post_content_(\d+)$/', $locationKey, $matches)) {
|
||||||
|
// In-content ads heredan la configuracion de post_content
|
||||||
|
return [
|
||||||
|
'enabled' => $settings['behavior']['post_content_enabled'] ?? false,
|
||||||
|
'format' => $settings['behavior']['post_content_format'] ?? 'in-article',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Mapeo de ubicaciones a grupos y campos
|
// Mapeo de ubicaciones a grupos y campos
|
||||||
$locationMap = [
|
$locationMap = [
|
||||||
'post_top' => ['group' => 'behavior', 'enabled' => 'post_top_enabled', 'format' => 'post_top_format'],
|
'post_top' => ['group' => 'behavior', 'enabled' => 'post_top_enabled', 'format' => 'post_top_format'],
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"component_name": "adsense-placement",
|
"component_name": "adsense-placement",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"description": "Control de AdSense y Google Analytics",
|
"description": "Control de AdSense y Google Analytics - Panel reorganizado",
|
||||||
"groups": {
|
"groups": {
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"label": "Visibilidad AdSense",
|
"label": "Activacion",
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"fields": {
|
"fields": {
|
||||||
"is_enabled": {
|
"is_enabled": {
|
||||||
@@ -130,18 +130,43 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Inserta anuncios automaticamente entre parrafos"
|
"description": "Inserta anuncios automaticamente entre parrafos"
|
||||||
},
|
},
|
||||||
"post_content_after_paragraphs": {
|
"post_content_random_mode": {
|
||||||
"type": "text",
|
"type": "boolean",
|
||||||
"label": "Despues del parrafo #",
|
"label": "Modo aleatorio",
|
||||||
"default": "3",
|
"default": true,
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"description": "Numero de parrafo despues del cual insertar"
|
"description": "Inserta ads en posiciones aleatorias (mejor UX)"
|
||||||
|
},
|
||||||
|
"post_content_min_ads": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Minimo de ads",
|
||||||
|
"default": "1",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["1", "2", "3", "4"],
|
||||||
|
"description": "Cantidad minima de anuncios a mostrar"
|
||||||
},
|
},
|
||||||
"post_content_max_ads": {
|
"post_content_max_ads": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Maximo de ads",
|
||||||
|
"default": "3",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["1", "2", "3", "4", "5", "6", "7", "8"],
|
||||||
|
"description": "Cantidad maxima de anuncios a mostrar"
|
||||||
|
},
|
||||||
|
"post_content_after_paragraphs": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Maximo ads en contenido",
|
"label": "Primer ad despues del parrafo #",
|
||||||
"default": "2",
|
"default": "3",
|
||||||
"editable": true
|
"editable": true,
|
||||||
|
"description": "Numero de parrafo despues del cual insertar el primer ad"
|
||||||
|
},
|
||||||
|
"post_content_min_paragraphs_between": {
|
||||||
|
"type": "select",
|
||||||
|
"label": "Parrafos minimos entre ads",
|
||||||
|
"default": "4",
|
||||||
|
"editable": true,
|
||||||
|
"options": ["2", "3", "4", "5", "6"],
|
||||||
|
"description": "Espacio minimo entre anuncios consecutivos"
|
||||||
},
|
},
|
||||||
"post_content_format": {
|
"post_content_format": {
|
||||||
"type": "select",
|
"type": "select",
|
||||||
@@ -270,7 +295,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"forms": {
|
"forms": {
|
||||||
"label": "Exclusiones",
|
"label": "Exclusiones y Rendimiento",
|
||||||
"priority": 90,
|
"priority": 90,
|
||||||
"fields": {
|
"fields": {
|
||||||
"exclude_categories": {
|
"exclude_categories": {
|
||||||
|
|||||||
Reference in New Issue
Block a user