Backup antes de optimizar Bootstrap Icons (subset)
Estado actual: - Bootstrap Icons completo: 211 KB (2050 iconos) - Solo usamos 105 iconos (5.1%) Próximo paso: crear subset de iconos para ahorrar ~199 KB 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Admin\AdsensePlacement\Infrastructure\FieldMapping;
|
||||||
|
|
||||||
|
use ROITheme\Admin\Shared\Infrastructure\FieldMapping\FieldMapperInterface;
|
||||||
|
|
||||||
|
final class AdsensePlacementFieldMapper implements FieldMapperInterface
|
||||||
|
{
|
||||||
|
public function getComponentId(): string
|
||||||
|
{
|
||||||
|
return 'adsense-placement';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldMapping(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// VISIBILITY
|
||||||
|
'adsense-placementEnabled' => ['group' => 'visibility', 'attribute' => 'is_enabled'],
|
||||||
|
'adsense-placementDisableAutoAds' => ['group' => 'visibility', 'attribute' => 'disable_auto_ads'],
|
||||||
|
'adsense-placementShowOnMobile' => ['group' => 'visibility', 'attribute' => 'show_on_mobile'],
|
||||||
|
'adsense-placementShowOnDesktop' => ['group' => 'visibility', 'attribute' => 'show_on_desktop'],
|
||||||
|
|
||||||
|
// CONTENT (Credentials)
|
||||||
|
'adsense-placementPublisherId' => ['group' => 'content', 'attribute' => 'publisher_id'],
|
||||||
|
'adsense-placementSlotDisplay' => ['group' => 'content', 'attribute' => 'slot_display'],
|
||||||
|
'adsense-placementSlotAuto' => ['group' => 'content', 'attribute' => 'slot_auto'],
|
||||||
|
'adsense-placementSlotAutorelaxed' => ['group' => 'content', 'attribute' => 'slot_autorelaxed'],
|
||||||
|
'adsense-placementSlotInarticle' => ['group' => 'content', 'attribute' => 'slot_inarticle'],
|
||||||
|
'adsense-placementSlotSkyscraper' => ['group' => 'content', 'attribute' => 'slot_skyscraper'],
|
||||||
|
|
||||||
|
// BEHAVIOR (Post locations + formats)
|
||||||
|
'adsense-placementPostTopEnabled' => ['group' => 'behavior', 'attribute' => 'post_top_enabled'],
|
||||||
|
'adsense-placementPostTopFormat' => ['group' => 'behavior', 'attribute' => 'post_top_format'],
|
||||||
|
'adsense-placementPostContentEnabled' => ['group' => 'behavior', 'attribute' => 'post_content_enabled'],
|
||||||
|
'adsense-placementPostContentAfterParagraphs' => ['group' => 'behavior', 'attribute' => 'post_content_after_paragraphs'],
|
||||||
|
'adsense-placementPostContentMaxAds' => ['group' => 'behavior', 'attribute' => 'post_content_max_ads'],
|
||||||
|
'adsense-placementPostContentFormat' => ['group' => 'behavior', 'attribute' => 'post_content_format'],
|
||||||
|
'adsense-placementPostBottomEnabled' => ['group' => 'behavior', 'attribute' => 'post_bottom_enabled'],
|
||||||
|
'adsense-placementPostBottomFormat' => ['group' => 'behavior', 'attribute' => 'post_bottom_format'],
|
||||||
|
'adsense-placementAfterRelatedEnabled' => ['group' => 'behavior', 'attribute' => 'after_related_enabled'],
|
||||||
|
'adsense-placementAfterRelatedFormat' => ['group' => 'behavior', 'attribute' => 'after_related_format'],
|
||||||
|
|
||||||
|
// BEHAVIOR (Rail Ads)
|
||||||
|
'adsense-placementRailAdsEnabled' => ['group' => 'behavior', 'attribute' => 'rail_ads_enabled'],
|
||||||
|
'adsense-placementRailLeftEnabled' => ['group' => 'behavior', 'attribute' => 'rail_left_enabled'],
|
||||||
|
'adsense-placementRailRightEnabled' => ['group' => 'behavior', 'attribute' => 'rail_right_enabled'],
|
||||||
|
'adsense-placementRailFormat' => ['group' => 'behavior', 'attribute' => 'rail_format'],
|
||||||
|
'adsense-placementRailTopOffset' => ['group' => 'behavior', 'attribute' => 'rail_top_offset'],
|
||||||
|
|
||||||
|
// LAYOUT (Archive/Global locations + formats)
|
||||||
|
'adsense-placementArchiveTopEnabled' => ['group' => 'layout', 'attribute' => 'archive_top_enabled'],
|
||||||
|
'adsense-placementArchiveBetweenEnabled' => ['group' => 'layout', 'attribute' => 'archive_between_enabled'],
|
||||||
|
'adsense-placementArchiveBetweenEvery' => ['group' => 'layout', 'attribute' => 'archive_between_every'],
|
||||||
|
'adsense-placementArchiveBottomEnabled' => ['group' => 'layout', 'attribute' => 'archive_bottom_enabled'],
|
||||||
|
'adsense-placementArchiveFormat' => ['group' => 'layout', 'attribute' => 'archive_format'],
|
||||||
|
'adsense-placementHeaderBelowEnabled' => ['group' => 'layout', 'attribute' => 'header_below_enabled'],
|
||||||
|
'adsense-placementFooterAboveEnabled' => ['group' => 'layout', 'attribute' => 'footer_above_enabled'],
|
||||||
|
'adsense-placementGlobalFormat' => ['group' => 'layout', 'attribute' => 'global_format'],
|
||||||
|
|
||||||
|
// FORMS (Exclusions + Delay)
|
||||||
|
'adsense-placementExcludeCategories' => ['group' => 'forms', 'attribute' => 'exclude_categories'],
|
||||||
|
'adsense-placementExcludePostTypes' => ['group' => 'forms', 'attribute' => 'exclude_post_types'],
|
||||||
|
'adsense-placementExcludePostIds' => ['group' => 'forms', 'attribute' => 'exclude_post_ids'],
|
||||||
|
'adsense-placementMinContentLength' => ['group' => 'forms', 'attribute' => 'min_content_length'],
|
||||||
|
'adsense-placementDelayEnabled' => ['group' => 'forms', 'attribute' => 'delay_enabled'],
|
||||||
|
'adsense-placementDelayTimeout' => ['group' => 'forms', 'attribute' => 'delay_timeout'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Admin\AdsensePlacement\Infrastructure\Ui;
|
||||||
|
|
||||||
|
use ROITheme\Admin\Infrastructure\Ui\AdminDashboardRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FormBuilder para AdSense Placement
|
||||||
|
*/
|
||||||
|
final class AdsensePlacementFormBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AdminDashboardRenderer $renderer
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function buildForm(string $componentId): string
|
||||||
|
{
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
// HEADER CON GRADIENTE
|
||||||
|
$html .= '<div class="rounded p-4 mb-4 shadow text-white" style="background: linear-gradient(135deg, #0E2337 0%, #1e3a5f 100%); border-left: 4px solid #FF8600;">';
|
||||||
|
$html .= ' <div class="d-flex align-items-center justify-content-between flex-wrap gap-3">';
|
||||||
|
$html .= ' <div>';
|
||||||
|
$html .= ' <h3 class="h4 mb-1 fw-bold">';
|
||||||
|
$html .= ' <i class="bi bi-megaphone me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Control de Anuncios AdSense';
|
||||||
|
$html .= ' </h3>';
|
||||||
|
$html .= ' <p class="mb-0 small" style="opacity: 0.85;">';
|
||||||
|
$html .= ' Configura ubicaciones manuales de anuncios para evitar Auto Ads';
|
||||||
|
$html .= ' </p>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// LAYOUT 2 COLUMNAS
|
||||||
|
$html .= '<div class="row g-3">';
|
||||||
|
|
||||||
|
// COLUMNA IZQUIERDA
|
||||||
|
$html .= ' <div class="col-lg-6">';
|
||||||
|
$html .= $this->buildVisibilityGroup($componentId);
|
||||||
|
$html .= $this->buildCredentialsGroup($componentId);
|
||||||
|
$html .= $this->buildPostLocationsGroup($componentId);
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
// COLUMNA DERECHA
|
||||||
|
$html .= ' <div class="col-lg-6">';
|
||||||
|
$html .= $this->buildRailAdsGroup($componentId);
|
||||||
|
$html .= $this->buildArchiveLocationsGroup($componentId);
|
||||||
|
$html .= $this->buildExclusionsGroup($componentId);
|
||||||
|
$html .= ' </div>';
|
||||||
|
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildVisibilityGroup(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-toggle-on me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Activacion y Visibilidad';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
// Switch: Enabled
|
||||||
|
$enabled = $this->renderer->getFieldValue($cid, 'visibility', 'is_enabled', false);
|
||||||
|
$html .= $this->buildSwitch($cid . 'Enabled', 'Activar Placement Manual', $enabled, 'bi-power');
|
||||||
|
|
||||||
|
// Switch: Disable Auto Ads
|
||||||
|
$disableAuto = $this->renderer->getFieldValue($cid, 'visibility', 'disable_auto_ads', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'DisableAutoAds', 'Deshabilitar Auto Ads de Google', $disableAuto, 'bi-shield-x');
|
||||||
|
|
||||||
|
// Switch: Show on Mobile
|
||||||
|
$showMobile = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_mobile', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'ShowOnMobile', 'Mostrar en movil', $showMobile, 'bi-phone');
|
||||||
|
|
||||||
|
// Switch: Show on Desktop
|
||||||
|
$showDesktop = $this->renderer->getFieldValue($cid, 'visibility', 'show_on_desktop', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'ShowOnDesktop', 'Mostrar en escritorio', $showDesktop, 'bi-display');
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCredentialsGroup(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-key me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Credenciales AdSense';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
// Publisher ID
|
||||||
|
$pubId = $this->renderer->getFieldValue($cid, 'content', 'publisher_id', 'ca-pub-8476420265998726');
|
||||||
|
$html .= $this->buildTextInput($cid . 'PublisherId', 'Publisher ID', $pubId, 'ca-pub-XXXXX');
|
||||||
|
|
||||||
|
// Slots (grid 2 columnas)
|
||||||
|
$html .= '<div class="row g-2 mt-2">';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$slotDisplay = $this->renderer->getFieldValue($cid, 'content', 'slot_display', '2873062302');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotDisplay', 'Slot Display', $slotDisplay);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$slotAuto = $this->renderer->getFieldValue($cid, 'content', 'slot_auto', '8471732096');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotAuto', 'Slot Auto', $slotAuto);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$slotRelaxed = $this->renderer->getFieldValue($cid, 'content', 'slot_autorelaxed', '9205569855');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotAutorelaxed', 'Slot Autorelaxed', $slotRelaxed);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$slotInArticle = $this->renderer->getFieldValue($cid, 'content', 'slot_inarticle', '7285187368');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotInarticle', 'Slot In-Article', $slotInArticle);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$slotSkyscraper = $this->renderer->getFieldValue($cid, 'content', 'slot_skyscraper', '');
|
||||||
|
$html .= $this->buildTextInput($cid . 'SlotSkyscraper', 'Slot Skyscraper (Rail Ads)', $slotSkyscraper);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$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 #1e3a5f;">';
|
||||||
|
$html .= ' <div class="card-body">';
|
||||||
|
$html .= ' <h5 class="fw-bold mb-3" style="color: #1e3a5f;">';
|
||||||
|
$html .= ' <i class="bi bi-file-text me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Ubicaciones en Posts';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
// Post Top
|
||||||
|
$postTopEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_top_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostTopEnabled', 'Despues de Featured Image', $postTopEnabled);
|
||||||
|
$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
|
||||||
|
$postContentEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_enabled', false);
|
||||||
|
$html .= $this->buildSwitch($cid . 'PostContentEnabled', 'Insertar dentro del contenido', $postContentEnabled);
|
||||||
|
|
||||||
|
$html .= '<div class="row g-2">';
|
||||||
|
$html .= ' <div class="col-4">';
|
||||||
|
$afterPara = $this->renderer->getFieldValue($cid, 'behavior', 'post_content_after_paragraphs', '3');
|
||||||
|
$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>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRailAdsGroup(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-layout-sidebar me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Rail Ads (Margenes Laterales)';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
$html .= ' <p class="small text-muted mb-3">Anuncios fijos en los espacios laterales del viewport. Solo visibles en pantallas >= 1600px.</p>';
|
||||||
|
|
||||||
|
// Master switch
|
||||||
|
$railEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'rail_ads_enabled', false);
|
||||||
|
$html .= $this->buildSwitch($cid . 'RailAdsEnabled', 'Activar Rail Ads', $railEnabled, 'bi-power');
|
||||||
|
|
||||||
|
// Left/Right toggles
|
||||||
|
$html .= '<div class="row g-2 mt-2">';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$leftEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'rail_left_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'RailLeftEnabled', 'Rail izquierdo', $leftEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div class="col-6">';
|
||||||
|
$rightEnabled = $this->renderer->getFieldValue($cid, 'behavior', 'rail_right_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'RailRightEnabled', 'Rail derecho', $rightEnabled);
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
// Format select
|
||||||
|
$railFormat = $this->renderer->getFieldValue($cid, 'behavior', 'rail_format', 'skyscraper');
|
||||||
|
$html .= $this->buildSelect($cid . 'RailFormat', 'Formato',
|
||||||
|
$railFormat,
|
||||||
|
['skyscraper' => 'Skyscraper (160x600)', 'half-page' => 'Half Page (300x600)']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Top offset
|
||||||
|
$topOffset = $this->renderer->getFieldValue($cid, 'behavior', 'rail_top_offset', '150');
|
||||||
|
$html .= $this->buildTextInput($cid . 'RailTopOffset', 'Distancia desde arriba (px)', $topOffset);
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
$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-slash-circle me-2" style="color: #FF8600;"></i>';
|
||||||
|
$html .= ' Exclusiones y Rendimiento';
|
||||||
|
$html .= ' </h5>';
|
||||||
|
|
||||||
|
// Exclusions
|
||||||
|
$excludeCats = $this->renderer->getFieldValue($cid, 'forms', 'exclude_categories', '');
|
||||||
|
$html .= $this->buildTextarea($cid . 'ExcludeCategories', 'Excluir categorias (IDs)', $excludeCats, 'Ej: 5,12,23');
|
||||||
|
|
||||||
|
$excludeTypes = $this->renderer->getFieldValue($cid, 'forms', 'exclude_post_types', '');
|
||||||
|
$html .= $this->buildTextarea($cid . 'ExcludePostTypes', 'Excluir tipos de post', $excludeTypes, 'Ej: page,attachment');
|
||||||
|
|
||||||
|
$excludeIds = $this->renderer->getFieldValue($cid, 'forms', 'exclude_post_ids', '');
|
||||||
|
$html .= $this->buildTextarea($cid . 'ExcludePostIds', 'Excluir posts (IDs)', $excludeIds, 'Ej: 100,205,310');
|
||||||
|
|
||||||
|
$minLength = $this->renderer->getFieldValue($cid, 'forms', 'min_content_length', '500');
|
||||||
|
$html .= $this->buildTextInput($cid . 'MinContentLength', 'Longitud minima de contenido', $minLength);
|
||||||
|
|
||||||
|
// Delay settings
|
||||||
|
$delayEnabled = $this->renderer->getFieldValue($cid, 'forms', 'delay_enabled', true);
|
||||||
|
$html .= $this->buildSwitch($cid . 'DelayEnabled', 'Retrasar carga de anuncios', $delayEnabled, 'bi-hourglass-split');
|
||||||
|
|
||||||
|
$delayTimeout = $this->renderer->getFieldValue($cid, 'forms', 'delay_timeout', '5000');
|
||||||
|
$html .= $this->buildTextInput($cid . 'DelayTimeout', 'Timeout de delay (ms)', $delayTimeout);
|
||||||
|
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HELPERS ===
|
||||||
|
|
||||||
|
private function buildSwitch(string $id, string $label, $value, string $icon = ''): string
|
||||||
|
{
|
||||||
|
$checked = checked($value, true, false);
|
||||||
|
$iconHtml = $icon ? '<i class="bi ' . $icon . ' me-1" style="color: #FF8600;"></i>' : '';
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<div class="mb-2">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="%s" %s>
|
||||||
|
<label class="form-check-label small" for="%s">%s%s</label>
|
||||||
|
</div>
|
||||||
|
</div>',
|
||||||
|
esc_attr($id), $checked, esc_attr($id), $iconHtml, esc_html($label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildTextInput(string $id, string $label, string $value, string $placeholder = ''): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="mb-3">
|
||||||
|
<label for="%s" class="form-label small fw-semibold">%s</label>
|
||||||
|
<input type="text" class="form-control form-control-sm" id="%s" value="%s" placeholder="%s">
|
||||||
|
</div>',
|
||||||
|
esc_attr($id), esc_html($label), esc_attr($id), esc_attr($value), esc_attr($placeholder)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildTextarea(string $id, string $label, string $value, string $placeholder = ''): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="mb-3">
|
||||||
|
<label for="%s" class="form-label small fw-semibold">%s</label>
|
||||||
|
<textarea class="form-control form-control-sm" id="%s" rows="2" placeholder="%s">%s</textarea>
|
||||||
|
</div>',
|
||||||
|
esc_attr($id), esc_html($label), esc_attr($id), esc_attr($placeholder), esc_textarea($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSelect(string $id, string $label, string $value, array $options): string
|
||||||
|
{
|
||||||
|
$optionsHtml = '';
|
||||||
|
foreach ($options as $optValue => $optLabel) {
|
||||||
|
$selected = selected($value, $optValue, false);
|
||||||
|
$optionsHtml .= sprintf(
|
||||||
|
'<option value="%s" %s>%s</option>',
|
||||||
|
esc_attr($optValue),
|
||||||
|
$selected,
|
||||||
|
esc_html($optLabel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<div class="mb-2">
|
||||||
|
<label for="%s" class="form-label small fw-semibold">%s</label>
|
||||||
|
<select class="form-select form-select-sm" id="%s">%s</select>
|
||||||
|
</div>',
|
||||||
|
esc_attr($id), esc_html($label), esc_attr($id), $optionsHtml
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,6 +111,11 @@ final class AdminDashboardRenderer implements DashboardRendererInterface
|
|||||||
'label' => 'Theme Settings',
|
'label' => 'Theme Settings',
|
||||||
'icon' => 'bi-gear',
|
'icon' => 'bi-gear',
|
||||||
],
|
],
|
||||||
|
'adsense-placement' => [
|
||||||
|
'id' => 'adsense-placement',
|
||||||
|
'label' => 'AdSense',
|
||||||
|
'icon' => 'bi-megaphone',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ final class FieldMapperProvider
|
|||||||
'ContactForm',
|
'ContactForm',
|
||||||
'Footer',
|
'Footer',
|
||||||
'ThemeSettings',
|
'ThemeSettings',
|
||||||
|
'AdsensePlacement',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
|||||||
196
Inc/adsense-placement.php
Normal file
196
Inc/adsense-placement.php
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AdSense Placement - Helper Functions
|
||||||
|
*
|
||||||
|
* Funciones para usar en templates:
|
||||||
|
* - roi_render_ad_slot('post-top')
|
||||||
|
* - roi_render_rail_ads() - Para los margenes laterales del viewport
|
||||||
|
* - roi_should_disable_auto_ads()
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 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 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 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si se debe deshabilitar Auto Ads
|
||||||
|
*/
|
||||||
|
function roi_should_disable_auto_ads(): bool
|
||||||
|
{
|
||||||
|
global $container;
|
||||||
|
|
||||||
|
if ($container === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$repository = $container->getComponentSettingsRepository();
|
||||||
|
$settings = $repository->getComponentSettings('adsense-placement');
|
||||||
|
|
||||||
|
$isEnabled = ($settings['visibility']['is_enabled'] ?? false) === true;
|
||||||
|
$disableAutoAds = ($settings['visibility']['disable_auto_ads'] ?? true) === true;
|
||||||
|
|
||||||
|
return $isEnabled && $disableAutoAds;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Public\AdsensePlacement\Infrastructure\Services;
|
||||||
|
|
||||||
|
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inyecta anuncios dentro del contenido del post
|
||||||
|
* via filtro the_content
|
||||||
|
*/
|
||||||
|
final class ContentAdInjector
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private array $settings,
|
||||||
|
private AdsensePlacementRenderer $renderer
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtra the_content para insertar anuncios
|
||||||
|
*/
|
||||||
|
public function inject(string $content): string
|
||||||
|
{
|
||||||
|
if (!($this->settings['behavior']['post_content_enabled'] ?? false)) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar longitud minima
|
||||||
|
$minLength = (int)($this->settings['forms']['min_content_length'] ?? 500);
|
||||||
|
if (strlen(strip_tags($content)) < $minLength) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$afterParagraphs = (int)($this->settings['behavior']['post_content_after_paragraphs'] ?? 3);
|
||||||
|
$maxAds = (int)($this->settings['behavior']['post_content_max_ads'] ?? 2);
|
||||||
|
|
||||||
|
// Dividir contenido en parrafos
|
||||||
|
$paragraphs = explode('</p>', $content);
|
||||||
|
$totalParagraphs = count($paragraphs);
|
||||||
|
|
||||||
|
if ($totalParagraphs < $afterParagraphs + 1) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$adsInserted = 0;
|
||||||
|
$newContent = '';
|
||||||
|
|
||||||
|
foreach ($paragraphs as $index => $paragraph) {
|
||||||
|
$newContent .= $paragraph;
|
||||||
|
|
||||||
|
if ($index < $totalParagraphs - 1) {
|
||||||
|
$newContent .= '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$paragraphNumber = $index + 1;
|
||||||
|
|
||||||
|
// Primer anuncio despues del parrafo indicado
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Public\AdsensePlacement\Infrastructure\Ui;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderer para slots de AdSense
|
||||||
|
*
|
||||||
|
* Responsabilidad:
|
||||||
|
* - Generar HTML de bloques de anuncios
|
||||||
|
* - Aplicar visibilidad desktop/mobile
|
||||||
|
* - NO hardcodear CSS (usar CSSGeneratorInterface)
|
||||||
|
*/
|
||||||
|
final class AdsensePlacementRenderer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CSSGeneratorInterface $cssGenerator
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifica el componente que soporta
|
||||||
|
*/
|
||||||
|
public function supports(string $componentType): bool
|
||||||
|
{
|
||||||
|
return $componentType === 'adsense-placement';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderiza un slot de anuncio
|
||||||
|
*
|
||||||
|
* @param array $settings Configuracion desde BD
|
||||||
|
* @param string $location Ubicacion (post-top, sidebar, etc.)
|
||||||
|
* @return string HTML del anuncio o vacio
|
||||||
|
*/
|
||||||
|
public function renderSlot(array $settings, string $location): string
|
||||||
|
{
|
||||||
|
// 1. Validar is_enabled
|
||||||
|
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Calcular clases de visibilidad
|
||||||
|
$visibilityClasses = $this->getVisibilityClasses(
|
||||||
|
$settings['visibility']['show_on_desktop'] ?? true,
|
||||||
|
$settings['visibility']['show_on_mobile'] ?? true
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($visibilityClasses === null) {
|
||||||
|
return ''; // Ambos false = no renderizar
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Obtener configuracion de la ubicacion
|
||||||
|
$locationConfig = $this->getLocationConfig($settings, $location);
|
||||||
|
if (!$locationConfig['enabled']) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Generar CSS (usando CSSGeneratorService)
|
||||||
|
$css = $this->cssGenerator->generate(
|
||||||
|
".roi-ad-{$location}",
|
||||||
|
[
|
||||||
|
'display' => 'flex',
|
||||||
|
'justify_content' => 'center',
|
||||||
|
'margin_top' => '1rem',
|
||||||
|
'margin_bottom' => '1rem',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Generar HTML del anuncio
|
||||||
|
$html = $this->buildAdHTML(
|
||||||
|
$settings,
|
||||||
|
$locationConfig['format'],
|
||||||
|
$location,
|
||||||
|
$visibilityClasses
|
||||||
|
);
|
||||||
|
|
||||||
|
return "<style>{$css}</style>\n{$html}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tabla de decision Bootstrap para visibilidad
|
||||||
|
*/
|
||||||
|
private function getVisibilityClasses(bool $desktop, bool $mobile): ?string
|
||||||
|
{
|
||||||
|
if (!$desktop && !$mobile) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!$desktop && $mobile) {
|
||||||
|
return 'd-lg-none';
|
||||||
|
}
|
||||||
|
if ($desktop && !$mobile) {
|
||||||
|
return 'd-none d-lg-block';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene configuracion de una ubicacion especifica
|
||||||
|
*/
|
||||||
|
private function getLocationConfig(array $settings, string $location): array
|
||||||
|
{
|
||||||
|
$locationKey = str_replace('-', '_', $location);
|
||||||
|
|
||||||
|
// Mapeo de ubicaciones a grupos y campos
|
||||||
|
$locationMap = [
|
||||||
|
'post_top' => ['group' => 'behavior', 'enabled' => 'post_top_enabled', 'format' => 'post_top_format'],
|
||||||
|
'post_bottom' => ['group' => 'behavior', 'enabled' => 'post_bottom_enabled', 'format' => 'post_bottom_format'],
|
||||||
|
'after_related' => ['group' => 'behavior', 'enabled' => 'after_related_enabled', 'format' => 'after_related_format'],
|
||||||
|
'archive_top' => ['group' => 'layout', 'enabled' => 'archive_top_enabled', 'format' => 'archive_format'],
|
||||||
|
'archive_between' => ['group' => 'layout', 'enabled' => 'archive_between_enabled', 'format' => 'archive_format'],
|
||||||
|
'archive_bottom' => ['group' => 'layout', 'enabled' => 'archive_bottom_enabled', 'format' => 'archive_format'],
|
||||||
|
'header_below' => ['group' => 'layout', 'enabled' => 'header_below_enabled', 'format' => 'global_format'],
|
||||||
|
'footer_above' => ['group' => 'layout', 'enabled' => 'footer_above_enabled', 'format' => 'global_format'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($locationMap[$locationKey])) {
|
||||||
|
return ['enabled' => false, 'format' => 'auto'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = $locationMap[$locationKey];
|
||||||
|
$group = $settings[$map['group']] ?? [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'enabled' => $group[$map['enabled']] ?? false,
|
||||||
|
'format' => $group[$map['format']] ?? 'auto',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera HTML del bloque de anuncio
|
||||||
|
*/
|
||||||
|
private function buildAdHTML(array $settings, string $format, string $location, string $visClasses): string
|
||||||
|
{
|
||||||
|
$publisherId = esc_attr($settings['content']['publisher_id'] ?? '');
|
||||||
|
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
||||||
|
|
||||||
|
if (empty($publisherId)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener slot segun formato
|
||||||
|
$slotId = $this->getSlotByFormat($settings, $format);
|
||||||
|
if (empty($slotId)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$scriptType = $delayEnabled ? 'text/plain' : 'text/javascript';
|
||||||
|
$dataAttr = $delayEnabled ? ' data-adsense-push' : '';
|
||||||
|
$locationClass = 'roi-ad-' . esc_attr(str_replace('_', '-', $location));
|
||||||
|
|
||||||
|
return $this->generateAdMarkup($format, $publisherId, $slotId, $locationClass, $visClasses, $scriptType, $dataAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el slot ID segun el formato
|
||||||
|
*/
|
||||||
|
private function getSlotByFormat(array $settings, string $format): string
|
||||||
|
{
|
||||||
|
$content = $settings['content'] ?? [];
|
||||||
|
|
||||||
|
return match($format) {
|
||||||
|
'display', 'display-large', 'display-square' => $content['slot_display'] ?? '',
|
||||||
|
'in-article' => $content['slot_inarticle'] ?? '',
|
||||||
|
'autorelaxed' => $content['slot_autorelaxed'] ?? '',
|
||||||
|
default => $content['slot_auto'] ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera el markup HTML segun formato de anuncio
|
||||||
|
*
|
||||||
|
* EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense
|
||||||
|
* ----------------------------------------------------------------
|
||||||
|
* Los atributos style="display:inline-block", style="display:block",
|
||||||
|
* style="text-align:center", etc. son ESPECIFICACION DE GOOGLE y NO
|
||||||
|
* pueden generarse via CSSGenerator.
|
||||||
|
*
|
||||||
|
* Documentacion oficial:
|
||||||
|
* - https://support.google.com/adsense/answer/9274516
|
||||||
|
* - https://support.google.com/adsense/answer/9183460
|
||||||
|
*
|
||||||
|
* Estos estilos son necesarios para que AdSense funcione correctamente
|
||||||
|
* y son inyectados tal como Google los especifica en su documentacion.
|
||||||
|
*/
|
||||||
|
private function generateAdMarkup(
|
||||||
|
string $format,
|
||||||
|
string $client,
|
||||||
|
string $slot,
|
||||||
|
string $locationClass,
|
||||||
|
string $visClasses,
|
||||||
|
string $scriptType,
|
||||||
|
string $dataAttr
|
||||||
|
): string {
|
||||||
|
$allClasses = trim("{$locationClass} {$visClasses}");
|
||||||
|
|
||||||
|
return match($format) {
|
||||||
|
'display' => $this->adDisplay($client, $slot, 728, 90, $allClasses, $scriptType, $dataAttr),
|
||||||
|
'display-large' => $this->adDisplay($client, $slot, 970, 250, $allClasses, $scriptType, $dataAttr),
|
||||||
|
'display-square' => $this->adDisplay($client, $slot, 300, 250, $allClasses, $scriptType, $dataAttr),
|
||||||
|
'in-article' => $this->adInArticle($client, $slot, $allClasses, $scriptType, $dataAttr),
|
||||||
|
'autorelaxed' => $this->adAutorelaxed($client, $slot, $allClasses, $scriptType, $dataAttr),
|
||||||
|
default => $this->adAuto($client, $slot, $allClasses, $scriptType, $dataAttr),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adDisplay(string $c, string $s, int $w, int $h, string $cl, string $t, string $a): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="roi-ad-slot %s">
|
||||||
|
<ins class="adsbygoogle" style="display:inline-block;width:%dpx;height:%dpx"
|
||||||
|
data-ad-client="%s" data-ad-slot="%s"></ins>
|
||||||
|
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||||
|
</div>',
|
||||||
|
esc_attr($cl), $w, $h, esc_attr($c), esc_attr($s), $t, $a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adAuto(string $c, string $s, string $cl, string $t, string $a): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="roi-ad-slot %s">
|
||||||
|
<ins class="adsbygoogle" style="display:block;min-height:250px"
|
||||||
|
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>',
|
||||||
|
esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adInArticle(string $c, string $s, string $cl, string $t, string $a): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="roi-ad-slot %s">
|
||||||
|
<ins class="adsbygoogle" style="display:block;text-align:center;min-height:200px"
|
||||||
|
data-ad-layout="in-article" data-ad-format="fluid"
|
||||||
|
data-ad-client="%s" data-ad-slot="%s"></ins>
|
||||||
|
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||||
|
</div>',
|
||||||
|
esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function adAutorelaxed(string $c, string $s, string $cl, string $t, string $a): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'<div class="roi-ad-slot %s">
|
||||||
|
<ins class="adsbygoogle" style="display:block;min-height:280px"
|
||||||
|
data-ad-format="autorelaxed"
|
||||||
|
data-ad-client="%s" data-ad-slot="%s"></ins>
|
||||||
|
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||||
|
</div>',
|
||||||
|
esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderiza Rail Ads (anuncios fijos en margenes laterales)
|
||||||
|
* Se inyectan via wp_footer y usan position: fixed
|
||||||
|
*/
|
||||||
|
public function renderRailAds(array $settings): string
|
||||||
|
{
|
||||||
|
// Verificar si Rail Ads estan habilitados
|
||||||
|
if (!($settings['visibility']['is_enabled'] ?? false)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!($settings['behavior']['rail_ads_enabled'] ?? false)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$publisherId = esc_attr($settings['content']['publisher_id'] ?? '');
|
||||||
|
$slotId = $settings['content']['slot_skyscraper'] ?? '';
|
||||||
|
|
||||||
|
if (empty($publisherId) || empty($slotId)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$leftEnabled = ($settings['behavior']['rail_left_enabled'] ?? true) === true;
|
||||||
|
$rightEnabled = ($settings['behavior']['rail_right_enabled'] ?? true) === true;
|
||||||
|
$format = $settings['behavior']['rail_format'] ?? 'skyscraper';
|
||||||
|
$topOffset = (int)($settings['behavior']['rail_top_offset'] ?? 150);
|
||||||
|
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
||||||
|
|
||||||
|
// Dimensiones segun formato
|
||||||
|
$width = $format === 'half-page' ? 300 : 160;
|
||||||
|
$height = 600;
|
||||||
|
|
||||||
|
$scriptType = $delayEnabled ? 'text/plain' : 'text/javascript';
|
||||||
|
$dataAttr = $delayEnabled ? ' data-adsense-push' : '';
|
||||||
|
|
||||||
|
// === CSS via CSSGenerator (NO hardcodeado) ===
|
||||||
|
$cssRules = [];
|
||||||
|
|
||||||
|
// Estilos base para Rail Ads
|
||||||
|
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad', [
|
||||||
|
'position' => 'fixed',
|
||||||
|
'top' => $topOffset . 'px',
|
||||||
|
'width' => $width . 'px',
|
||||||
|
'z-index' => '100',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Posicion rail izquierdo
|
||||||
|
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-left', [
|
||||||
|
'left' => 'calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px)',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Posicion rail derecho
|
||||||
|
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-right', [
|
||||||
|
'right' => 'calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px)',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Media query para ocultar en pantallas < 1600px
|
||||||
|
// NOTA: Media queries se escriben directamente (patron consistente con FeaturedImageRenderer)
|
||||||
|
$cssRules[] = "@media (max-width: 1599px) {
|
||||||
|
.roi-rail-ad { display: none !important; }
|
||||||
|
}";
|
||||||
|
|
||||||
|
$css = implode("\n", $cssRules);
|
||||||
|
$html = "<style>{$css}</style>\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense
|
||||||
|
* Los atributos style="display:inline-block;width:Xpx;height:Xpx" son
|
||||||
|
* especificacion de Google y NO pueden generarse via CSSGenerator.
|
||||||
|
* Ref: https://support.google.com/adsense/answer/9274516
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Rail izquierdo
|
||||||
|
if ($leftEnabled) {
|
||||||
|
$html .= sprintf(
|
||||||
|
'<div class="roi-rail-ad roi-rail-ad-left">
|
||||||
|
<ins class="adsbygoogle" style="display:inline-block;width:%dpx;height:%dpx"
|
||||||
|
data-ad-client="%s" data-ad-slot="%s"></ins>
|
||||||
|
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||||
|
</div>',
|
||||||
|
$width, $height, esc_attr($publisherId), esc_attr($slotId), $scriptType, $dataAttr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rail derecho
|
||||||
|
if ($rightEnabled) {
|
||||||
|
$html .= sprintf(
|
||||||
|
'<div class="roi-rail-ad roi-rail-ad-right">
|
||||||
|
<ins class="adsbygoogle" style="display:inline-block;width:%dpx;height:%dpx"
|
||||||
|
data-ad-client="%s" data-ad-slot="%s"></ins>
|
||||||
|
<script type="%s"%s>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||||
|
</div>',
|
||||||
|
$width, $height, esc_attr($publisherId), esc_attr($slotId), $scriptType, $dataAttr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ use ROITheme\Shared\Infrastructure\Services\CleanupService;
|
|||||||
use ROITheme\Shared\Infrastructure\Services\CSSGeneratorService;
|
use ROITheme\Shared\Infrastructure\Services\CSSGeneratorService;
|
||||||
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
|
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
|
||||||
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
|
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
|
||||||
|
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DIContainer - Contenedor de Inyección de Dependencias
|
* DIContainer - Contenedor de Inyección de Dependencias
|
||||||
@@ -233,4 +234,23 @@ final class DIContainer
|
|||||||
|
|
||||||
return $this->instances['saveComponentSettingsUseCase'];
|
return $this->instances['saveComponentSettingsUseCase'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener renderer de AdSense Placement
|
||||||
|
*
|
||||||
|
* Lazy initialization: Crea la instancia solo en la primera llamada
|
||||||
|
* Resuelve dependencia: getCSSGeneratorService()
|
||||||
|
*
|
||||||
|
* @return AdsensePlacementRenderer
|
||||||
|
*/
|
||||||
|
public function getAdsensePlacementRenderer(): AdsensePlacementRenderer
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['adsensePlacementRenderer'])) {
|
||||||
|
$this->instances['adsensePlacementRenderer'] = new AdsensePlacementRenderer(
|
||||||
|
$this->getCSSGeneratorService()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->instances['adsensePlacementRenderer'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ require_once get_template_directory() . '/Inc/template-tags.php';
|
|||||||
require_once get_template_directory() . '/Inc/featured-image.php';
|
require_once get_template_directory() . '/Inc/featured-image.php';
|
||||||
require_once get_template_directory() . '/Inc/category-badge.php';
|
require_once get_template_directory() . '/Inc/category-badge.php';
|
||||||
require_once get_template_directory() . '/Inc/adsense-delay.php';
|
require_once get_template_directory() . '/Inc/adsense-delay.php';
|
||||||
|
require_once get_template_directory() . '/Inc/adsense-placement.php';
|
||||||
require_once get_template_directory() . '/Inc/related-posts.php';
|
require_once get_template_directory() . '/Inc/related-posts.php';
|
||||||
// ELIMINADO: Inc/toc.php (FASE 6 - Clean Architecture: usa TableOfContentsRenderer)
|
// ELIMINADO: Inc/toc.php (FASE 6 - Clean Architecture: usa TableOfContentsRenderer)
|
||||||
require_once get_template_directory() . '/Inc/apu-tables.php';
|
require_once get_template_directory() . '/Inc/apu-tables.php';
|
||||||
|
|||||||
Reference in New Issue
Block a user