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:
FrankZamora
2025-11-27 20:45:40 -06:00
parent 1a069a1336
commit 55f061df67
5 changed files with 544 additions and 188 deletions

View File

@@ -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'],

View File

@@ -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 &gt;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">&gt;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);

View File

@@ -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;
} }
} }

View File

@@ -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'],

View File

@@ -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": {