feat(adsense): implementar Anchor Ads y Vignette Ads
- Anchor Ads: anuncios fijos top/bottom con botones minimizar/cerrar - Vignette Ads: modal fullscreen con triggers configurables - Schema v1.3.0 con grupos anchor_ads y vignette_ads (18 campos) - FieldMapper actualizado para persistir settings en BD - JavaScript para interacción (colapso, cierre, localStorage) - Soporte para responsive y tamaños fijos en vignette IMPORTANTE: Ejecutar en servidor remoto: wp roi-theme sync-component adsense-placement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -549,4 +549,395 @@ final class AdsensePlacementRenderer
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderiza Anchor Ads (anuncios fijos en top/bottom)
|
||||
*
|
||||
* @param array $settings Configuracion desde BD
|
||||
* @return string HTML de los anchor ads
|
||||
*/
|
||||
public function renderAnchorAds(array $settings): string
|
||||
{
|
||||
// Verificar si Anchor Ads estan habilitados
|
||||
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
||||
return '';
|
||||
}
|
||||
if (!($settings['anchor_ads']['anchor_enabled'] ?? false)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$publisherId = esc_attr($settings['content']['publisher_id'] ?? '');
|
||||
$slotId = $settings['content']['slot_auto'] ?? '';
|
||||
|
||||
if (empty($publisherId) || empty($slotId)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Configuracion de anchor
|
||||
$anchorConfig = $settings['anchor_ads'] ?? [];
|
||||
$position = $anchorConfig['anchor_position'] ?? 'bottom';
|
||||
$height = (int)($anchorConfig['anchor_height'] ?? 90);
|
||||
$showMobile = ($anchorConfig['anchor_show_on_mobile'] ?? true) === true;
|
||||
$showWide = ($anchorConfig['anchor_show_on_wide_screens'] ?? false) === true;
|
||||
$collapsible = ($anchorConfig['anchor_collapsible_enabled'] ?? true) === true;
|
||||
$collapsedHeight = (int)($anchorConfig['anchor_collapsed_height'] ?? 24);
|
||||
$collapseText = esc_html($anchorConfig['anchor_collapse_button_text'] ?? 'Ver anuncio');
|
||||
$closePosition = $anchorConfig['anchor_close_position'] ?? 'right';
|
||||
$rememberState = ($anchorConfig['anchor_remember_state'] ?? true) === true;
|
||||
$rememberDuration = $anchorConfig['anchor_remember_duration'] ?? 'session';
|
||||
|
||||
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
||||
$scriptType = $delayEnabled ? 'text/plain' : 'text/javascript';
|
||||
$dataAttr = $delayEnabled ? ' data-adsense-push' : '';
|
||||
|
||||
// === CSS via CSSGenerator ===
|
||||
$cssRules = [];
|
||||
|
||||
// Base anchor styles
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad', [
|
||||
'position' => 'fixed',
|
||||
'left' => '0',
|
||||
'right' => '0',
|
||||
'z-index' => '9999',
|
||||
'background' => '#f8f9fa',
|
||||
'border-color' => '#dee2e6',
|
||||
'box-shadow' => '0 -2px 10px rgba(0,0,0,0.1)',
|
||||
'transition' => 'height 0.3s ease, transform 0.3s ease, opacity 0.3s ease',
|
||||
'display' => 'flex',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad-top', [
|
||||
'top' => '0',
|
||||
'border-bottom-width' => '1px',
|
||||
'border-bottom-style' => 'solid',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad-bottom', [
|
||||
'bottom' => '0',
|
||||
'border-top-width' => '1px',
|
||||
'border-top-style' => 'solid',
|
||||
]);
|
||||
|
||||
// Controls container
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-controls', [
|
||||
'position' => 'absolute',
|
||||
'top' => '4px',
|
||||
'display' => 'flex',
|
||||
'gap' => '4px',
|
||||
'z-index' => '10',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-controls-left', ['left' => '8px']);
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-controls-right', ['right' => '8px']);
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-controls-center', ['left' => '50%', 'transform' => 'translateX(-50%)']);
|
||||
|
||||
// Buttons
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-btn', [
|
||||
'width' => '28px',
|
||||
'height' => '28px',
|
||||
'border' => 'none',
|
||||
'border-radius' => '4px',
|
||||
'background' => 'rgba(0,0,0,0.1)',
|
||||
'color' => '#333',
|
||||
'cursor' => 'pointer',
|
||||
'font-size' => '14px',
|
||||
'display' => 'flex',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
'transition' => 'background 0.2s',
|
||||
]);
|
||||
|
||||
// Collapsed state
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad.collapsed', [
|
||||
'height' => $collapsedHeight . 'px !important',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad.collapsed .roi-anchor-content', [
|
||||
'display' => 'none',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-expand-bar', [
|
||||
'display' => 'none',
|
||||
'width' => '100%',
|
||||
'height' => '100%',
|
||||
'background' => '#e9ecef',
|
||||
'border' => 'none',
|
||||
'cursor' => 'pointer',
|
||||
'font-size' => '12px',
|
||||
'color' => '#495057',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad.collapsed .roi-anchor-expand-bar', [
|
||||
'display' => 'flex',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-anchor-ad.hidden', [
|
||||
'display' => 'none !important',
|
||||
]);
|
||||
|
||||
// Media query para visibilidad
|
||||
if (!$showMobile && $showWide) {
|
||||
$cssRules[] = "@media (max-width: 999px) { .roi-anchor-ad { display: none !important; } }";
|
||||
} elseif ($showMobile && !$showWide) {
|
||||
$cssRules[] = "@media (min-width: 1000px) { .roi-anchor-ad { display: none !important; } }";
|
||||
} elseif (!$showMobile && !$showWide) {
|
||||
$cssRules[] = ".roi-anchor-ad { display: none !important; }";
|
||||
}
|
||||
|
||||
$css = implode("\n", $cssRules);
|
||||
$html = "<style id=\"roi-anchor-ads-css\">{$css}</style>\n";
|
||||
|
||||
// Renderizar anchor(s)
|
||||
$controlsClass = 'roi-anchor-controls roi-anchor-controls-' . esc_attr($closePosition);
|
||||
|
||||
if ($position === 'top' || $position === 'both') {
|
||||
$html .= $this->buildAnchorHTML('top', $height, $publisherId, $slotId, $scriptType, $dataAttr, $controlsClass, $collapsible, $collapseText);
|
||||
}
|
||||
|
||||
if ($position === 'bottom' || $position === 'both') {
|
||||
$html .= $this->buildAnchorHTML('bottom', $height, $publisherId, $slotId, $scriptType, $dataAttr, $controlsClass, $collapsible, $collapseText);
|
||||
}
|
||||
|
||||
// Config para JavaScript (data attributes en lugar de inline JS)
|
||||
$jsConfig = json_encode([
|
||||
'rememberState' => $rememberState,
|
||||
'rememberDuration' => $rememberDuration,
|
||||
'collapsible' => $collapsible,
|
||||
]);
|
||||
|
||||
$html .= '<script id="roi-anchor-config" type="application/json">' . $jsConfig . '</script>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera HTML para un anchor individual
|
||||
*/
|
||||
private function buildAnchorHTML(
|
||||
string $pos,
|
||||
int $height,
|
||||
string $client,
|
||||
string $slot,
|
||||
string $scriptType,
|
||||
string $dataAttr,
|
||||
string $controlsClass,
|
||||
bool $collapsible,
|
||||
string $collapseText
|
||||
): string {
|
||||
$posClass = 'roi-anchor-ad-' . $pos;
|
||||
|
||||
$collapseBtn = $collapsible
|
||||
? '<button class="roi-anchor-btn" data-action="collapse" title="Minimizar"><i class="bi bi-dash"></i></button>'
|
||||
: '';
|
||||
|
||||
return sprintf(
|
||||
'<div class="roi-anchor-ad %s" data-position="%s" style="height:%dpx;">
|
||||
<div class="%s">
|
||||
%s
|
||||
<button class="roi-anchor-btn" data-action="close" title="Cerrar"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
<div class="roi-anchor-content">
|
||||
<ins class="adsbygoogle" style="display:block;width:100%%;height:%dpx"
|
||||
data-ad-client="%s" data-ad-slot="%s"
|
||||
data-ad-format="auto" data-full-width-responsive="true"></ins>
|
||||
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||
</div>
|
||||
<button class="roi-anchor-expand-bar" data-action="expand">
|
||||
<i class="bi bi-plus-circle me-1"></i> %s
|
||||
</button>
|
||||
</div>',
|
||||
esc_attr($posClass),
|
||||
esc_attr($pos),
|
||||
$height,
|
||||
esc_attr($controlsClass),
|
||||
$collapseBtn,
|
||||
$height - 10,
|
||||
esc_attr($client),
|
||||
esc_attr($slot),
|
||||
$scriptType,
|
||||
$dataAttr,
|
||||
esc_html($collapseText)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderiza Vignette Ad (pantalla completa)
|
||||
*
|
||||
* @param array $settings Configuracion desde BD
|
||||
* @return string HTML del vignette ad
|
||||
*/
|
||||
public function renderVignetteAd(array $settings): string
|
||||
{
|
||||
// Verificar si Vignette Ads estan habilitados
|
||||
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
||||
return '';
|
||||
}
|
||||
if (!($settings['vignette_ads']['vignette_enabled'] ?? false)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$publisherId = esc_attr($settings['content']['publisher_id'] ?? '');
|
||||
$slotId = $settings['content']['slot_display'] ?? $settings['content']['slot_auto'] ?? '';
|
||||
|
||||
if (empty($publisherId) || empty($slotId)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Configuracion de vignette
|
||||
$vignetteConfig = $settings['vignette_ads'] ?? [];
|
||||
$trigger = $vignetteConfig['vignette_trigger'] ?? 'pageview';
|
||||
$triggerDelay = (int)($vignetteConfig['vignette_trigger_delay'] ?? 5);
|
||||
$showMobile = ($vignetteConfig['vignette_show_on_mobile'] ?? true) === true;
|
||||
$showDesktop = ($vignetteConfig['vignette_show_on_desktop'] ?? true) === true;
|
||||
$size = $vignetteConfig['vignette_size'] ?? '300x250';
|
||||
$overlayOpacity = (float)($vignetteConfig['vignette_overlay_opacity'] ?? 0.7);
|
||||
$closeDelay = (int)($vignetteConfig['vignette_close_button_delay'] ?? 0);
|
||||
$reshowEnabled = ($vignetteConfig['vignette_reshow_enabled'] ?? true) === true;
|
||||
$reshowTime = (int)($vignetteConfig['vignette_reshow_time'] ?? 5);
|
||||
$maxPerSession = $vignetteConfig['vignette_max_per_session'] ?? '3';
|
||||
$maxPerPage = $vignetteConfig['vignette_max_per_page'] ?? '1';
|
||||
|
||||
// Calcular dimensiones
|
||||
list($adWidth, $adHeight) = $this->parseVignetteSize($size);
|
||||
|
||||
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
||||
$scriptType = $delayEnabled ? 'text/plain' : 'text/javascript';
|
||||
$dataAttr = $delayEnabled ? ' data-adsense-push' : '';
|
||||
|
||||
// === CSS via CSSGenerator ===
|
||||
$cssRules = [];
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-overlay', [
|
||||
'position' => 'fixed',
|
||||
'top' => '0',
|
||||
'left' => '0',
|
||||
'right' => '0',
|
||||
'bottom' => '0',
|
||||
'background' => 'rgba(0,0,0,' . $overlayOpacity . ')',
|
||||
'z-index' => '99999',
|
||||
'display' => 'none',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
'opacity' => '0',
|
||||
'transition' => 'opacity 0.3s ease',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-overlay.active', [
|
||||
'display' => 'flex',
|
||||
'opacity' => '1',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-modal', [
|
||||
'position' => 'relative',
|
||||
'background' => '#fff',
|
||||
'border-radius' => '8px',
|
||||
'padding' => '20px',
|
||||
'box-shadow' => '0 10px 40px rgba(0,0,0,0.3)',
|
||||
'max-width' => '95vw',
|
||||
'max-height' => '95vh',
|
||||
'overflow' => 'auto',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-close', [
|
||||
'position' => 'absolute',
|
||||
'top' => '-12px',
|
||||
'right' => '-12px',
|
||||
'width' => '32px',
|
||||
'height' => '32px',
|
||||
'border' => 'none',
|
||||
'border-radius' => '50%',
|
||||
'background' => '#dc3545',
|
||||
'color' => '#fff',
|
||||
'cursor' => 'pointer',
|
||||
'font-size' => '18px',
|
||||
'display' => 'flex',
|
||||
'align-items' => 'center',
|
||||
'justify-content' => 'center',
|
||||
'box-shadow' => '0 2px 8px rgba(0,0,0,0.2)',
|
||||
'transition' => 'transform 0.2s, opacity 0.3s',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-close.delayed', [
|
||||
'opacity' => '0',
|
||||
'pointer-events' => 'none',
|
||||
]);
|
||||
|
||||
$cssRules[] = $this->cssGenerator->generate('.roi-vignette-close:not(.delayed)', [
|
||||
'opacity' => '1',
|
||||
'pointer-events' => 'auto',
|
||||
]);
|
||||
|
||||
// Media query para visibilidad
|
||||
if (!$showMobile && $showDesktop) {
|
||||
$cssRules[] = "@media (max-width: 991px) { .roi-vignette-overlay { display: none !important; } }";
|
||||
} elseif ($showMobile && !$showDesktop) {
|
||||
$cssRules[] = "@media (min-width: 992px) { .roi-vignette-overlay { display: none !important; } }";
|
||||
} elseif (!$showMobile && !$showDesktop) {
|
||||
$cssRules[] = ".roi-vignette-overlay { display: none !important; }";
|
||||
}
|
||||
|
||||
$css = implode("\n", $cssRules);
|
||||
$html = "<style id=\"roi-vignette-css\">{$css}</style>\n";
|
||||
|
||||
// Determinar estilo de anuncio segun tamano
|
||||
$adStyle = $size === 'responsive'
|
||||
? 'display:block;min-width:300px;min-height:250px'
|
||||
: sprintf('display:inline-block;width:%dpx;height:%dpx', $adWidth, $adHeight);
|
||||
|
||||
$adFormat = $size === 'responsive' ? ' data-ad-format="auto" data-full-width-responsive="true"' : '';
|
||||
|
||||
$closeDelayClass = $closeDelay > 0 ? ' delayed' : '';
|
||||
|
||||
$html .= sprintf(
|
||||
'<div id="roi-vignette-overlay" class="roi-vignette-overlay">
|
||||
<div class="roi-vignette-modal">
|
||||
<button class="roi-vignette-close%s" data-action="close-vignette" title="Cerrar" data-delay="%d">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
<ins class="adsbygoogle" style="%s"
|
||||
data-ad-client="%s" data-ad-slot="%s"%s></ins>
|
||||
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||
</div>
|
||||
</div>',
|
||||
$closeDelayClass,
|
||||
$closeDelay,
|
||||
$adStyle,
|
||||
esc_attr($publisherId),
|
||||
esc_attr($slotId),
|
||||
$adFormat,
|
||||
$scriptType,
|
||||
$dataAttr
|
||||
);
|
||||
|
||||
// Config para JavaScript
|
||||
$jsConfig = json_encode([
|
||||
'trigger' => $trigger,
|
||||
'triggerDelay' => $triggerDelay,
|
||||
'closeDelay' => $closeDelay,
|
||||
'reshowEnabled' => $reshowEnabled,
|
||||
'reshowTime' => $reshowTime,
|
||||
'maxPerSession' => $maxPerSession,
|
||||
'maxPerPage' => $maxPerPage,
|
||||
]);
|
||||
|
||||
$html .= '<script id="roi-vignette-config" type="application/json">' . $jsConfig . '</script>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsea el tamano del vignette
|
||||
*/
|
||||
private function parseVignetteSize(string $size): array
|
||||
{
|
||||
return match($size) {
|
||||
'300x250' => [300, 250],
|
||||
'336x280' => [336, 280],
|
||||
default => [300, 250],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user