feat(adsense): reorganizar panel con UX mejorada y soporte 1-8 ads random
Panel AdSense reorganizado: - Diagrama visual mostrando ubicaciones de anuncios (POST-TOP, IN-CONTENT, POST-BOTTOM, RAIL) - Secciones colapsables por ubicación con badges de color - Slots con descripciones claras indicando uso (Auto, In-Article, Display, etc.) In-Content Ads mejorado: - Soporte para 1-8 anuncios dentro del contenido - Modo aleatorio (random) que varía posiciones en cada visita - Configuración de mínimo/máximo de ads - Párrafos mínimos entre anuncios configurable (2-6) - Primer ad siempre en posición fija configurada Archivos modificados: - Schema v1.2.0 con 4 nuevos campos (random_mode, min_ads, max_ads, min_paragraphs_between) - FormBuilder con diagrama visual y mejor organización - ContentAdInjector con lógica de posicionamiento random - Renderer con soporte para post-content-1 hasta post-content-8 - FieldMapper actualizado con nuevos campos 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,11 @@ use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
|
||||
/**
|
||||
* Inyecta anuncios dentro del contenido del post
|
||||
* via filtro the_content
|
||||
*
|
||||
* Soporta:
|
||||
* - Modo aleatorio (random) con posiciones variables
|
||||
* - Configuracion de 1-8 ads maximo
|
||||
* - Espacio minimo entre anuncios
|
||||
*/
|
||||
final class ContentAdInjector
|
||||
{
|
||||
@@ -31,40 +36,166 @@ final class ContentAdInjector
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Obtener configuracion
|
||||
$minAds = (int)($this->settings['behavior']['post_content_min_ads'] ?? 1);
|
||||
$maxAds = (int)($this->settings['behavior']['post_content_max_ads'] ?? 3);
|
||||
$afterParagraphs = (int)($this->settings['behavior']['post_content_after_paragraphs'] ?? 3);
|
||||
$maxAds = (int)($this->settings['behavior']['post_content_max_ads'] ?? 2);
|
||||
$minBetween = (int)($this->settings['behavior']['post_content_min_paragraphs_between'] ?? 4);
|
||||
$randomMode = ($this->settings['behavior']['post_content_random_mode'] ?? true) === true;
|
||||
|
||||
// Validar min <= max
|
||||
if ($minAds > $maxAds) {
|
||||
$minAds = $maxAds;
|
||||
}
|
||||
|
||||
// Dividir contenido en parrafos
|
||||
$paragraphs = explode('</p>', $content);
|
||||
$paragraphs = $this->splitIntoParagraphs($content);
|
||||
$totalParagraphs = count($paragraphs);
|
||||
|
||||
// Necesitamos al menos afterParagraphs + 1 parrafos
|
||||
if ($totalParagraphs < $afterParagraphs + 1) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$adsInserted = 0;
|
||||
// Calcular posiciones de insercion
|
||||
$adPositions = $this->calculateAdPositions(
|
||||
$totalParagraphs,
|
||||
$afterParagraphs,
|
||||
$minBetween,
|
||||
$minAds,
|
||||
$maxAds,
|
||||
$randomMode
|
||||
);
|
||||
|
||||
if (empty($adPositions)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Reconstruir contenido con anuncios insertados
|
||||
return $this->buildContentWithAds($paragraphs, $adPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide el contenido en parrafos preservando el HTML
|
||||
*/
|
||||
private function splitIntoParagraphs(string $content): array
|
||||
{
|
||||
// Dividir por </p>, pero mantener el tag
|
||||
$parts = preg_split('/(<\/p>)/i', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
$paragraphs = [];
|
||||
$current = '';
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$current .= $part;
|
||||
if (strtolower($part) === '</p>') {
|
||||
$paragraphs[] = $current;
|
||||
$current = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Si hay contenido restante (sin cerrar), agregarlo
|
||||
if (!empty(trim($current))) {
|
||||
$paragraphs[] = $current;
|
||||
}
|
||||
|
||||
return $paragraphs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula las posiciones donde insertar anuncios
|
||||
*
|
||||
* @return int[] Indices de parrafos despues de los cuales insertar ads
|
||||
*/
|
||||
private function calculateAdPositions(
|
||||
int $totalParagraphs,
|
||||
int $afterFirst,
|
||||
int $minBetween,
|
||||
int $minAds,
|
||||
int $maxAds,
|
||||
bool $randomMode
|
||||
): array {
|
||||
// Calcular posiciones disponibles respetando el espacio minimo
|
||||
$availablePositions = [];
|
||||
$lastPosition = $afterFirst; // Primera posicion fija
|
||||
|
||||
// La primera posicion siempre es despues del parrafo indicado
|
||||
if ($afterFirst < $totalParagraphs) {
|
||||
$availablePositions[] = $afterFirst;
|
||||
}
|
||||
|
||||
// Calcular posiciones adicionales respetando minBetween
|
||||
$nextPosition = $afterFirst + $minBetween;
|
||||
while ($nextPosition < $totalParagraphs - 1) { // -1 para no insertar al final
|
||||
$availablePositions[] = $nextPosition;
|
||||
$nextPosition += $minBetween;
|
||||
}
|
||||
|
||||
// Determinar cuantos ads insertar
|
||||
$maxPossible = count($availablePositions);
|
||||
if ($maxPossible === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Limitar por maxAds y lo que el contenido permite
|
||||
$actualMax = min($maxAds, $maxPossible);
|
||||
$actualMin = min($minAds, $actualMax);
|
||||
|
||||
// Determinar cantidad final de ads
|
||||
if ($randomMode) {
|
||||
// En modo random, elegir cantidad aleatoria entre min y max
|
||||
$numAds = rand($actualMin, $actualMax);
|
||||
} else {
|
||||
// En modo fijo, usar el maximo posible
|
||||
$numAds = $actualMax;
|
||||
}
|
||||
|
||||
if ($numAds === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Seleccionar posiciones
|
||||
if ($randomMode && $numAds < $maxPossible) {
|
||||
// Modo random: elegir posiciones aleatorias
|
||||
// Siempre incluir la primera posicion
|
||||
$selectedPositions = [$availablePositions[0]];
|
||||
|
||||
if ($numAds > 1) {
|
||||
// Elegir aleatoriamente del resto
|
||||
$remainingPositions = array_slice($availablePositions, 1);
|
||||
shuffle($remainingPositions);
|
||||
$additionalPositions = array_slice($remainingPositions, 0, $numAds - 1);
|
||||
$selectedPositions = array_merge($selectedPositions, $additionalPositions);
|
||||
}
|
||||
|
||||
// Ordenar para insertar en orden correcto
|
||||
sort($selectedPositions);
|
||||
return $selectedPositions;
|
||||
} else {
|
||||
// Modo fijo o todas las posiciones necesarias
|
||||
return array_slice($availablePositions, 0, $numAds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruye el contenido insertando anuncios en las posiciones indicadas
|
||||
*/
|
||||
private function buildContentWithAds(array $paragraphs, array $adPositions): string
|
||||
{
|
||||
$newContent = '';
|
||||
$adsInserted = 0;
|
||||
|
||||
foreach ($paragraphs as $index => $paragraph) {
|
||||
$newContent .= $paragraph;
|
||||
|
||||
if ($index < $totalParagraphs - 1) {
|
||||
$newContent .= '</p>';
|
||||
}
|
||||
|
||||
// Verificar si debemos insertar un ad despues de este parrafo
|
||||
// El indice es 0-based, las posiciones son 1-based (parrafo #3 = index 2)
|
||||
$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));
|
||||
if (in_array($paragraphNumber, $adPositions, true)) {
|
||||
$adsInserted++;
|
||||
$adHtml = $this->renderer->renderSlot($this->settings, 'post-content-' . $adsInserted);
|
||||
$newContent .= $adHtml;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,15 @@ final class AdsensePlacementRenderer
|
||||
{
|
||||
$locationKey = str_replace('-', '_', $location);
|
||||
|
||||
// Manejar ubicaciones de in-content (post_content_1, post_content_2, etc.)
|
||||
if (preg_match('/^post_content_(\d+)$/', $locationKey, $matches)) {
|
||||
// In-content ads heredan la configuracion de post_content
|
||||
return [
|
||||
'enabled' => $settings['behavior']['post_content_enabled'] ?? false,
|
||||
'format' => $settings['behavior']['post_content_format'] ?? 'in-article',
|
||||
];
|
||||
}
|
||||
|
||||
// Mapeo de ubicaciones a grupos y campos
|
||||
$locationMap = [
|
||||
'post_top' => ['group' => 'behavior', 'enabled' => 'post_top_enabled', 'format' => 'post_top_format'],
|
||||
|
||||
Reference in New Issue
Block a user