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:
FrankZamora
2025-11-27 14:31:04 -06:00
parent b43cb22dc1
commit cd09666f1d
9 changed files with 1103 additions and 0 deletions

View File

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

View File

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