- 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>
540 lines
16 KiB
PHP
540 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* AdSense Placement & Google Analytics - Helper Functions
|
|
*
|
|
* Funciones para usar en templates:
|
|
* - roi_render_ad_slot('post-top')
|
|
* - roi_render_rail_ads() - Para los margenes laterales del viewport
|
|
* - roi_render_anchor_ads() - Anuncios fijos top/bottom
|
|
* - roi_render_vignette_ad() - Anuncio pantalla completa
|
|
*
|
|
* Funciones auto-registradas en wp_head:
|
|
* - roi_enqueue_adsense_script() - Script principal de AdSense
|
|
* - roi_enqueue_analytics_script() - Script de Google Analytics
|
|
*
|
|
* Funciones auto-registradas en wp_footer:
|
|
* - roi_render_rail_ads() (priority 50)
|
|
* - roi_render_anchor_ads() (priority 51)
|
|
* - roi_render_vignette_ad() (priority 52)
|
|
*
|
|
* Funciones auto-registradas en wp_enqueue_scripts:
|
|
* - roi_enqueue_anchor_vignette_scripts() - Script JS para Anchor/Vignette
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Renderiza un slot de anuncio en una ubicacion
|
|
*
|
|
* NOTA DIP: El renderer se obtiene del DIContainer, NO se instancia directamente.
|
|
* Esto cumple con el Principio de Inversion de Dependencias.
|
|
*/
|
|
function roi_render_ad_slot(string $location): string
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (empty($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar exclusiones
|
|
if (roi_is_ad_excluded($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Obtener renderer desde DIContainer (DIP compliant)
|
|
$renderer = $container->getAdsensePlacementRenderer();
|
|
|
|
return $renderer->renderSlot($settings, $location);
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI AdSense: ' . $e->getMessage());
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica si se deben ocultar anuncios para usuarios logueados
|
|
*/
|
|
function roi_should_hide_for_logged_in(array $settings): bool
|
|
{
|
|
// Si la opcion esta activada Y el usuario esta logueado, ocultar ads
|
|
$hideForLoggedIn = $settings['visibility']['hide_for_logged_in'] ?? false;
|
|
|
|
if ($hideForLoggedIn && is_user_logged_in()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Verifica si el contenido actual esta excluido
|
|
*/
|
|
function roi_is_ad_excluded(array $settings): bool
|
|
{
|
|
$forms = $settings['forms'] ?? [];
|
|
|
|
// Excluir categorias
|
|
$excludeCats = array_filter(array_map('trim', explode(',', $forms['exclude_categories'] ?? '')));
|
|
if (!empty($excludeCats) && is_single()) {
|
|
$postCats = wp_get_post_categories(get_the_ID());
|
|
if (array_intersect($excludeCats, $postCats)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Excluir tipos de post
|
|
$excludeTypes = array_filter(array_map('trim', explode(',', $forms['exclude_post_types'] ?? '')));
|
|
if (!empty($excludeTypes) && in_array(get_post_type(), $excludeTypes, true)) {
|
|
return true;
|
|
}
|
|
|
|
// Excluir posts especificos
|
|
$excludeIds = array_filter(array_map('trim', explode(',', $forms['exclude_post_ids'] ?? '')));
|
|
if (!empty($excludeIds) && in_array((string)get_the_ID(), $excludeIds, true)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Renderiza los Rail Ads (margenes laterales del viewport)
|
|
* Se llama desde wp_footer para inyectar al final del body
|
|
*
|
|
* NOTA DIP: El renderer se obtiene del DIContainer, NO se instancia directamente.
|
|
*/
|
|
function roi_render_rail_ads(): string
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (empty($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar exclusiones
|
|
if (roi_is_ad_excluded($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Obtener renderer desde DIContainer (DIP compliant)
|
|
$renderer = $container->getAdsensePlacementRenderer();
|
|
|
|
return $renderer->renderRailAds($settings);
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI AdSense Rail Ads: ' . $e->getMessage());
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook para inyectar Rail Ads en el footer
|
|
*/
|
|
add_action('wp_footer', function() {
|
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
echo roi_render_rail_ads();
|
|
}, 50);
|
|
|
|
/**
|
|
* Carga el script principal de AdSense
|
|
*/
|
|
function roi_enqueue_adsense_script(): void
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null || is_admin()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
|
return;
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return;
|
|
}
|
|
|
|
$publisherId = $settings['content']['publisher_id'] ?? '';
|
|
if (empty($publisherId)) {
|
|
return;
|
|
}
|
|
|
|
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
|
|
|
if ($delayEnabled) {
|
|
echo '<script type="text/plain" data-adsense-script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=' . esc_attr($publisherId) . '" crossorigin="anonymous"></script>' . "\n";
|
|
} else {
|
|
echo '<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=' . esc_attr($publisherId) . '" crossorigin="anonymous"></script>' . "\n";
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI AdSense: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
add_action('wp_head', 'roi_enqueue_adsense_script', 5);
|
|
|
|
/**
|
|
* Registra el filtro the_content para inyectar anuncios
|
|
*/
|
|
// Prioridad 150 para ejecutar DESPUÉS de restrict-content-pro (100)
|
|
add_filter('the_content', 'roi_inject_content_ads', 150);
|
|
|
|
function roi_inject_content_ads(string $content): string
|
|
{
|
|
global $container;
|
|
|
|
// Solo en posts individuales
|
|
if (!is_single() || !in_the_loop() || !is_main_query()) {
|
|
return $content;
|
|
}
|
|
|
|
if ($container === null) {
|
|
return $content;
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (empty($settings) || !($settings['visibility']['is_enabled'] ?? false)) {
|
|
return $content;
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return $content;
|
|
}
|
|
|
|
// Verificar exclusiones
|
|
if (roi_is_ad_excluded($settings)) {
|
|
return $content;
|
|
}
|
|
|
|
$renderer = $container->getAdsensePlacementRenderer();
|
|
|
|
// Inyectar anuncio al inicio (post-top)
|
|
$postTopHtml = '';
|
|
if ($settings['behavior']['post_top_enabled'] ?? false) {
|
|
$postTopHtml = $renderer->renderSlot($settings, 'post-top');
|
|
}
|
|
|
|
// Inyectar anuncio al final (post-bottom)
|
|
$postBottomHtml = '';
|
|
if ($settings['behavior']['post_bottom_enabled'] ?? false) {
|
|
$postBottomHtml = $renderer->renderSlot($settings, 'post-bottom');
|
|
}
|
|
|
|
// Inyectar anuncios dentro del contenido
|
|
$injector = new \ROITheme\Public\AdsensePlacement\Infrastructure\Services\ContentAdInjector(
|
|
$settings,
|
|
$renderer
|
|
);
|
|
$content = $injector->inject($content);
|
|
|
|
return $postTopHtml . $content . $postBottomHtml;
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI AdSense Content ERROR: ' . $e->getMessage());
|
|
}
|
|
return $content;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Carga el script de Google Analytics (GA4 o Universal Analytics)
|
|
*/
|
|
function roi_enqueue_analytics_script(): void
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null || is_admin()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
// Verificar si Analytics esta habilitado
|
|
if (!($settings['analytics']['analytics_enabled'] ?? false)) {
|
|
return;
|
|
}
|
|
|
|
$trackingId = trim($settings['analytics']['ga_tracking_id'] ?? '');
|
|
if (empty($trackingId)) {
|
|
return;
|
|
}
|
|
|
|
// Verificar si GA ya esta cargado por otro plugin
|
|
if (roi_is_analytics_loaded()) {
|
|
return;
|
|
}
|
|
|
|
$anonymizeIp = ($settings['analytics']['ga_anonymize_ip'] ?? true) === true;
|
|
|
|
// Detectar tipo de ID (GA4 vs Universal Analytics)
|
|
if (strpos($trackingId, 'G-') === 0) {
|
|
roi_render_ga4_script($trackingId, $anonymizeIp);
|
|
} elseif (strpos($trackingId, 'UA-') === 0) {
|
|
roi_render_universal_analytics_script($trackingId, $anonymizeIp);
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI Analytics: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
add_action('wp_head', 'roi_enqueue_analytics_script', 1);
|
|
|
|
/**
|
|
* Verifica si Google Analytics ya esta cargado por otro plugin
|
|
*/
|
|
function roi_is_analytics_loaded(): bool
|
|
{
|
|
// Verificar si MonsterInsights esta activo
|
|
if (class_exists('MonsterInsights_Lite') || class_exists('MonsterInsights')) {
|
|
return true;
|
|
}
|
|
|
|
// Verificar si Site Kit de Google esta activo
|
|
if (class_exists('Google\Site_Kit\Plugin')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Renderiza script de Google Analytics 4
|
|
*/
|
|
function roi_render_ga4_script(string $trackingId, bool $anonymizeIp): void
|
|
{
|
|
$config = $anonymizeIp ? "{ 'anonymize_ip': true }" : '{}';
|
|
|
|
echo '<!-- Google Analytics 4 (ROI Theme) -->' . "\n";
|
|
echo '<script async src="https://www.googletagmanager.com/gtag/js?id=' . esc_attr($trackingId) . '"></script>' . "\n";
|
|
echo '<script>' . "\n";
|
|
echo 'window.dataLayer = window.dataLayer || [];' . "\n";
|
|
echo 'function gtag(){dataLayer.push(arguments);}' . "\n";
|
|
echo 'gtag("js", new Date());' . "\n";
|
|
echo 'gtag("config", "' . esc_js($trackingId) . '", ' . $config . ');' . "\n";
|
|
echo '</script>' . "\n";
|
|
}
|
|
|
|
/**
|
|
* Renderiza script de Universal Analytics (legacy)
|
|
*/
|
|
function roi_render_universal_analytics_script(string $trackingId, bool $anonymizeIp): void
|
|
{
|
|
$anonymizeConfig = $anonymizeIp ? 'ga("set", "anonymizeIp", true);' : '';
|
|
|
|
echo '<!-- Universal Analytics (ROI Theme) -->' . "\n";
|
|
echo '<script>' . "\n";
|
|
echo '(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){' . "\n";
|
|
echo '(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),' . "\n";
|
|
echo 'm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)' . "\n";
|
|
echo '})(window,document,"script","https://www.google-analytics.com/analytics.js","ga");' . "\n";
|
|
echo 'ga("create", "' . esc_js($trackingId) . '", "auto");' . "\n";
|
|
if (!empty($anonymizeConfig)) {
|
|
echo $anonymizeConfig . "\n";
|
|
}
|
|
echo 'ga("send", "pageview");' . "\n";
|
|
echo '</script>' . "\n";
|
|
}
|
|
|
|
/**
|
|
* Renderiza Anchor Ads (anuncios fijos top/bottom)
|
|
*
|
|
* NOTA DIP: El renderer se obtiene del DIContainer, NO se instancia directamente.
|
|
*/
|
|
function roi_render_anchor_ads(): string
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (empty($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar exclusiones
|
|
if (roi_is_ad_excluded($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Obtener renderer desde DIContainer (DIP compliant)
|
|
$renderer = $container->getAdsensePlacementRenderer();
|
|
|
|
return $renderer->renderAnchorAds($settings);
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI Anchor Ads: ' . $e->getMessage());
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renderiza Vignette Ad (pantalla completa)
|
|
*
|
|
* NOTA DIP: El renderer se obtiene del DIContainer, NO se instancia directamente.
|
|
*/
|
|
function roi_render_vignette_ad(): string
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (empty($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Verificar exclusiones
|
|
if (roi_is_ad_excluded($settings)) {
|
|
return '';
|
|
}
|
|
|
|
// Obtener renderer desde DIContainer (DIP compliant)
|
|
$renderer = $container->getAdsensePlacementRenderer();
|
|
|
|
return $renderer->renderVignetteAd($settings);
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI Vignette Ad: ' . $e->getMessage());
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook para inyectar Anchor Ads en el footer
|
|
*/
|
|
add_action('wp_footer', function() {
|
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
echo roi_render_anchor_ads();
|
|
}, 51);
|
|
|
|
/**
|
|
* Hook para inyectar Vignette Ad en el footer
|
|
*/
|
|
add_action('wp_footer', function() {
|
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
echo roi_render_vignette_ad();
|
|
}, 52);
|
|
|
|
/**
|
|
* Encola el script JavaScript para Anchor y Vignette Ads
|
|
*/
|
|
function roi_enqueue_anchor_vignette_scripts(): void
|
|
{
|
|
global $container;
|
|
|
|
if ($container === null || is_admin()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$repository = $container->getComponentSettingsRepository();
|
|
$settings = $repository->getComponentSettings('adsense-placement');
|
|
|
|
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
|
return;
|
|
}
|
|
|
|
// Verificar si Anchor o Vignette estan habilitados
|
|
$anchorEnabled = $settings['anchor_ads']['anchor_enabled'] ?? false;
|
|
$vignetteEnabled = $settings['vignette_ads']['vignette_enabled'] ?? false;
|
|
|
|
if (!$anchorEnabled && !$vignetteEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Verificar si ocultar para usuarios logueados
|
|
if (roi_should_hide_for_logged_in($settings)) {
|
|
return;
|
|
}
|
|
|
|
// Encolar script
|
|
wp_enqueue_script(
|
|
'roi-anchor-vignette',
|
|
get_template_directory_uri() . '/Public/AdsensePlacement/Infrastructure/Ui/Assets/anchor-vignette.js',
|
|
[],
|
|
'1.0.0',
|
|
true // En footer
|
|
);
|
|
|
|
} catch (\Throwable $e) {
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('ROI Anchor/Vignette Scripts: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
add_action('wp_enqueue_scripts', 'roi_enqueue_anchor_vignette_scripts');
|