683 lines
23 KiB
PHP
683 lines
23 KiB
PHP
<?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
|
|
*
|
|
* Soporta dos modos:
|
|
* - Solo parrafos: Logica clasica solo con parrafos (usa config de behavior)
|
|
* - Avanzado: Multiples tipos de elementos (H2, H3, p, img, lists, blockquotes, tables)
|
|
*
|
|
* El modo se determina por incontent_mode:
|
|
* - "paragraphs_only": usa config de behavior (insercion solo en parrafos)
|
|
* - Otros: usa config de incontent_advanced
|
|
*/
|
|
final class ContentAdInjector
|
|
{
|
|
/**
|
|
* Prioridades de elementos para seleccion
|
|
* Mayor = mas importante
|
|
*/
|
|
private const ELEMENT_PRIORITIES = [
|
|
'h2' => 10,
|
|
'p' => 8,
|
|
'h3' => 7,
|
|
'image' => 6,
|
|
'list' => 5,
|
|
'blockquote' => 4,
|
|
'table' => 3,
|
|
];
|
|
|
|
public function __construct(
|
|
private array $settings,
|
|
private AdsensePlacementRenderer $renderer
|
|
) {}
|
|
|
|
/**
|
|
* Filtra the_content para insertar anuncios
|
|
*/
|
|
public function inject(string $content): string
|
|
{
|
|
// DEBUG TEMPORAL
|
|
$debug = '<!-- ROI_AD_DEBUG: inject() called, content length=' . strlen($content) . ' -->';
|
|
|
|
// PASO 0: Validar longitud minima (aplica a todos los modos)
|
|
$minLength = (int)($this->settings['forms']['min_content_length'] ?? 500);
|
|
if (strlen(strip_tags($content)) < $minLength) {
|
|
return $debug . '<!-- SKIP: too short -->' . $content;
|
|
}
|
|
|
|
// Determinar modo de operacion
|
|
$mode = $this->settings['incontent_advanced']['incontent_mode'] ?? 'paragraphs_only';
|
|
$debug .= '<!-- MODE=' . $mode . ' -->';
|
|
|
|
if ($mode === 'paragraphs_only') {
|
|
return $debug . $this->injectParagraphsOnly($content);
|
|
}
|
|
|
|
return $debug . $this->injectAdvanced($content);
|
|
}
|
|
|
|
/**
|
|
* Modo solo parrafos: logica clasica que inserta anuncios unicamente despues de parrafos
|
|
*/
|
|
private function injectParagraphsOnly(string $content): string
|
|
{
|
|
if (!($this->settings['behavior']['post_content_enabled'] ?? false)) {
|
|
return $content;
|
|
}
|
|
|
|
// Obtener configuracion de behavior (modo solo parrafos)
|
|
$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);
|
|
$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 = $this->splitIntoParagraphs($content);
|
|
$totalParagraphs = count($paragraphs);
|
|
|
|
// Necesitamos al menos afterParagraphs + 1 parrafos
|
|
if ($totalParagraphs < $afterParagraphs + 1) {
|
|
return $content;
|
|
}
|
|
|
|
// Calcular posiciones de insercion
|
|
$adPositions = $this->calculateParagraphsOnlyPositions(
|
|
$totalParagraphs,
|
|
$afterParagraphs,
|
|
$minBetween,
|
|
$minAds,
|
|
$maxAds,
|
|
$randomMode
|
|
);
|
|
|
|
if (empty($adPositions)) {
|
|
return $content;
|
|
}
|
|
|
|
// Reconstruir contenido con anuncios insertados
|
|
return $this->buildParagraphsOnlyContent($paragraphs, $adPositions);
|
|
}
|
|
|
|
/**
|
|
* Modo avanzado: multiples tipos de elementos
|
|
*/
|
|
private function injectAdvanced(string $content): string
|
|
{
|
|
$config = $this->settings['incontent_advanced'] ?? [];
|
|
$debug = '<!-- ADV: config keys=' . implode(',', array_keys($config)) . ' -->';
|
|
|
|
// Obtener configuracion
|
|
$maxAds = (int)($config['incontent_max_total_ads'] ?? 8);
|
|
$minSpacing = (int)($config['incontent_min_spacing'] ?? 3);
|
|
$priorityMode = $config['incontent_priority_mode'] ?? 'position';
|
|
$format = $config['incontent_format'] ?? 'in-article';
|
|
$debug .= '<!-- ADV: maxAds=' . $maxAds . ' minSpacing=' . $minSpacing . ' -->';
|
|
|
|
// PASO 1: Escanear contenido para encontrar todas las ubicaciones
|
|
$locations = $this->scanContent($content);
|
|
$debug .= '<!-- ADV: scanContent found ' . count($locations) . ' locations -->';
|
|
|
|
if (empty($locations)) {
|
|
return $debug . '<!-- ADV: NO locations found -->' . $content;
|
|
}
|
|
|
|
// PASO 2: Filtrar por configuracion (enabled)
|
|
$locations = $this->filterByEnabled($locations, $config);
|
|
$debug .= '<!-- ADV: filterByEnabled left ' . count($locations) . ' -->';
|
|
|
|
if (empty($locations)) {
|
|
return $debug . '<!-- ADV: EMPTY after filterByEnabled -->' . $content;
|
|
}
|
|
|
|
// PASO 3: Aplicar probabilidad deterministica
|
|
$postId = get_the_ID() ?: 0;
|
|
$locations = $this->applyProbability($locations, $config, $postId);
|
|
$debug .= '<!-- ADV: applyProbability left ' . count($locations) . ' -->';
|
|
|
|
if (empty($locations)) {
|
|
return $debug . '<!-- ADV: EMPTY after applyProbability -->' . $content;
|
|
}
|
|
|
|
// PASO 4-5: Filtrar por espaciado y limite (segun priority_mode)
|
|
if ($priorityMode === 'priority') {
|
|
$locations = $this->filterByPriorityFirst($locations, $minSpacing, $maxAds);
|
|
} else {
|
|
$locations = $this->filterByPositionFirst($locations, $minSpacing, $maxAds);
|
|
}
|
|
$debug .= '<!-- ADV: filterBySpacing left ' . count($locations) . ' -->';
|
|
|
|
if (empty($locations)) {
|
|
return $debug . '<!-- ADV: EMPTY after filterBySpacing -->' . $content;
|
|
}
|
|
|
|
// Ordenar por posicion para insercion correcta
|
|
usort($locations, fn($a, $b) => $a['position'] <=> $b['position']);
|
|
|
|
$debug .= '<!-- ADV: INSERTING ' . count($locations) . ' ads -->';
|
|
|
|
// PASO 6: Insertar anuncios
|
|
return $debug . $this->insertAds($content, $locations, $format);
|
|
}
|
|
|
|
/**
|
|
* PASO 1: Escanea el contenido para encontrar ubicaciones elegibles
|
|
*
|
|
* IMPORTANTE: No inserta anuncios:
|
|
* - Dentro de tablas (<table>...</table>)
|
|
* - Dentro de embeds/iframes (YouTube, etc.)
|
|
* - Despues de tablas (tables excluidas completamente)
|
|
*
|
|
* @return array{position: int, type: string, tag: string, element_index: int}[]
|
|
*/
|
|
private function scanContent(string $content): array
|
|
{
|
|
$locations = [];
|
|
$elementIndex = 0;
|
|
|
|
// Primero, mapear zonas prohibidas (tablas, iframes, embeds)
|
|
$forbiddenZones = $this->mapForbiddenZones($content);
|
|
|
|
// Regex para encontrar tags de cierre de elementos de bloque
|
|
// NOTA: Excluimos </table> - no queremos insertar despues de tablas
|
|
$pattern = '/(<\/(?:p|h2|h3|figure|ul|ol|blockquote)>)/i';
|
|
|
|
// Encontrar todas las coincidencias con sus posiciones
|
|
if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
|
|
foreach ($matches[0] as $match) {
|
|
$tag = strtolower($match[0]);
|
|
$position = $match[1] + strlen($match[0]); // Posicion despues del tag
|
|
|
|
// Verificar que no este dentro de una zona prohibida
|
|
if ($this->isInForbiddenZone($position, $forbiddenZones)) {
|
|
continue;
|
|
}
|
|
|
|
$type = $this->getTypeFromTag($tag);
|
|
if ($type) {
|
|
$locations[] = [
|
|
'position' => $position,
|
|
'type' => $type,
|
|
'tag' => $tag,
|
|
'element_index' => $elementIndex++,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detectar imagenes standalone (no dentro de figure ni zonas prohibidas)
|
|
$locations = array_merge($locations, $this->scanStandaloneImages($content, $elementIndex, $forbiddenZones));
|
|
|
|
// Validar listas (minimo 3 items)
|
|
$locations = $this->validateLists($content, $locations);
|
|
|
|
// Ordenar por posicion
|
|
usort($locations, fn($a, $b) => $a['position'] <=> $b['position']);
|
|
|
|
// Reasignar indices de elemento
|
|
foreach ($locations as $i => &$loc) {
|
|
$loc['element_index'] = $i;
|
|
}
|
|
|
|
return $locations;
|
|
}
|
|
|
|
/**
|
|
* Mapea zonas donde NO se deben insertar anuncios
|
|
* Incluye: tablas, iframes, embeds de video
|
|
*
|
|
* @return array{start: int, end: int}[]
|
|
*/
|
|
private function mapForbiddenZones(string $content): array
|
|
{
|
|
$zones = [];
|
|
$contentLength = strlen($content);
|
|
|
|
// Tablas: buscar cada <table> y su </table> correspondiente
|
|
$this->findMatchingTags($content, 'table', $zones);
|
|
|
|
// Iframes (YouTube, Vimeo, etc)
|
|
$this->findMatchingTags($content, 'iframe', $zones);
|
|
|
|
// Figure con clase wp-block-embed (embeds de WordPress)
|
|
if (preg_match_all('/<figure[^>]*class="[^"]*wp-block-embed[^"]*"[^>]*>/i', $content, $matches, PREG_OFFSET_CAPTURE)) {
|
|
foreach ($matches[0] as $match) {
|
|
$startPos = $match[1];
|
|
$closePos = strpos($content, '</figure>', $startPos);
|
|
if ($closePos !== false) {
|
|
$zones[] = [
|
|
'start' => $startPos,
|
|
'end' => $closePos + strlen('</figure>'),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $zones;
|
|
}
|
|
|
|
/**
|
|
* Encuentra tags de apertura y cierre correspondientes
|
|
*/
|
|
private function findMatchingTags(string $content, string $tagName, array &$zones): void
|
|
{
|
|
$openTag = '<' . $tagName;
|
|
$closeTag = '</' . $tagName . '>';
|
|
$offset = 0;
|
|
|
|
while (($startPos = stripos($content, $openTag, $offset)) !== false) {
|
|
// Buscar el cierre correspondiente
|
|
$closePos = stripos($content, $closeTag, $startPos);
|
|
if ($closePos !== false) {
|
|
$zones[] = [
|
|
'start' => $startPos,
|
|
'end' => $closePos + strlen($closeTag),
|
|
];
|
|
$offset = $closePos + strlen($closeTag);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica si una posicion esta dentro de una zona prohibida
|
|
*/
|
|
private function isInForbiddenZone(int $position, array $forbiddenZones): bool
|
|
{
|
|
foreach ($forbiddenZones as $zone) {
|
|
if ($position >= $zone['start'] && $position <= $zone['end']) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convierte tag de cierre a tipo de elemento
|
|
* NOTA: </table> excluido - no insertamos ads despues de tablas
|
|
*/
|
|
private function getTypeFromTag(string $tag): ?string
|
|
{
|
|
return match ($tag) {
|
|
'</p>' => 'p',
|
|
'</h2>' => 'h2',
|
|
'</h3>' => 'h3',
|
|
'</figure>' => 'image',
|
|
'</ul>', '</ol>' => 'list',
|
|
'</blockquote>' => 'blockquote',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detecta imagenes que no estan dentro de figure ni zonas prohibidas
|
|
*/
|
|
private function scanStandaloneImages(string $content, int $startIndex, array $forbiddenZones = []): array
|
|
{
|
|
$locations = [];
|
|
|
|
// Encontrar todas las imagenes con sus posiciones
|
|
if (!preg_match_all('/<img[^>]*>/i', $content, $matches, PREG_OFFSET_CAPTURE)) {
|
|
return $locations;
|
|
}
|
|
|
|
foreach ($matches[0] as $match) {
|
|
$imgTag = $match[0];
|
|
$imgPosition = $match[1];
|
|
|
|
// Verificar que no este en zona prohibida
|
|
if ($this->isInForbiddenZone($imgPosition, $forbiddenZones)) {
|
|
continue;
|
|
}
|
|
|
|
// Verificar si hay un <figure> abierto antes de esta imagen
|
|
$contentBefore = substr($content, 0, $imgPosition);
|
|
$lastFigureOpen = strrpos($contentBefore, '<figure');
|
|
$lastFigureClose = strrpos($contentBefore, '</figure>');
|
|
|
|
// Si hay figure abierto sin cerrar, esta imagen esta dentro de figure
|
|
if ($lastFigureOpen !== false && ($lastFigureClose === false || $lastFigureClose < $lastFigureOpen)) {
|
|
continue; // Ignorar, se contara con </figure>
|
|
}
|
|
|
|
// Imagen standalone - calcular posicion despues del tag
|
|
$endPosition = $imgPosition + strlen($imgTag);
|
|
|
|
// Si la imagen esta seguida de </p> o similar, usar esa posicion
|
|
$contentAfter = substr($content, $endPosition, 20);
|
|
if (preg_match('/^\s*<\/p>/i', $contentAfter, $closeMatch)) {
|
|
// La imagen esta dentro de un parrafo, no es standalone
|
|
continue;
|
|
}
|
|
|
|
$locations[] = [
|
|
'position' => $endPosition,
|
|
'type' => 'image',
|
|
'tag' => $imgTag,
|
|
'element_index' => $startIndex++,
|
|
];
|
|
}
|
|
|
|
return $locations;
|
|
}
|
|
|
|
/**
|
|
* Valida que las listas tengan minimo 3 items
|
|
*/
|
|
private function validateLists(string $content, array $locations): array
|
|
{
|
|
return array_filter($locations, function ($loc) use ($content) {
|
|
if ($loc['type'] !== 'list') {
|
|
return true;
|
|
}
|
|
|
|
// Encontrar el contenido de la lista
|
|
$endPos = $loc['position'];
|
|
$tag = $loc['tag'];
|
|
$openTag = str_replace('/', '', $tag); // </ul> -> <ul>
|
|
|
|
// Buscar hacia atras el tag de apertura
|
|
$contentBefore = substr($content, 0, $endPos);
|
|
$lastOpen = strrpos($contentBefore, '<' . substr($openTag, 1)); // <ul o <ol
|
|
|
|
if ($lastOpen === false) {
|
|
return false;
|
|
}
|
|
|
|
$listContent = substr($content, $lastOpen, $endPos - $lastOpen);
|
|
|
|
// Contar items (usando substr_count como indica el spec)
|
|
$itemCount = substr_count(strtolower($listContent), '<li');
|
|
|
|
return $itemCount >= 3;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* PASO 2: Filtra ubicaciones por campos enabled
|
|
*/
|
|
private function filterByEnabled(array $locations, array $config): array
|
|
{
|
|
$enabledTypes = [];
|
|
|
|
$typeMapping = [
|
|
'h2' => 'incontent_after_h2_enabled',
|
|
'h3' => 'incontent_after_h3_enabled',
|
|
'p' => 'incontent_after_paragraphs_enabled',
|
|
'image' => 'incontent_after_images_enabled',
|
|
'list' => 'incontent_after_lists_enabled',
|
|
'blockquote' => 'incontent_after_blockquotes_enabled',
|
|
'table' => 'incontent_after_tables_enabled',
|
|
];
|
|
|
|
foreach ($typeMapping as $type => $field) {
|
|
if ($config[$field] ?? false) {
|
|
$enabledTypes[] = $type;
|
|
}
|
|
}
|
|
|
|
return array_filter($locations, fn($loc) => in_array($loc['type'], $enabledTypes, true));
|
|
}
|
|
|
|
/**
|
|
* PASO 3: Aplica probabilidad deterministica usando seed del dia
|
|
*/
|
|
private function applyProbability(array $locations, array $config, int $postId): array
|
|
{
|
|
// Calcular seed deterministico
|
|
$seed = crc32($postId . date('Y-m-d'));
|
|
mt_srand($seed);
|
|
|
|
$probMapping = [
|
|
'h2' => 'incontent_after_h2_probability',
|
|
'h3' => 'incontent_after_h3_probability',
|
|
'p' => 'incontent_after_paragraphs_probability',
|
|
'image' => 'incontent_after_images_probability',
|
|
'list' => 'incontent_after_lists_probability',
|
|
'blockquote' => 'incontent_after_blockquotes_probability',
|
|
'table' => 'incontent_after_tables_probability',
|
|
];
|
|
|
|
return array_filter($locations, function ($loc) use ($config, $probMapping) {
|
|
$field = $probMapping[$loc['type']] ?? null;
|
|
if (!$field) {
|
|
return true;
|
|
}
|
|
|
|
$probability = (int)($config[$field] ?? 100);
|
|
$roll = mt_rand(1, 100);
|
|
|
|
return $roll <= $probability;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* PASO 4-5 (modo position): Filtrar por espaciado primero, luego por prioridad
|
|
*/
|
|
private function filterByPositionFirst(array $locations, int $minSpacing, int $maxAds): array
|
|
{
|
|
// PASO 4: Ordenar por posicion y filtrar por espaciado
|
|
usort($locations, fn($a, $b) => $a['element_index'] <=> $b['element_index']);
|
|
|
|
$filtered = [];
|
|
$lastIndex = -999;
|
|
|
|
foreach ($locations as $loc) {
|
|
if ($loc['element_index'] - $lastIndex >= $minSpacing) {
|
|
$filtered[] = $loc;
|
|
$lastIndex = $loc['element_index'];
|
|
}
|
|
}
|
|
|
|
// PASO 5: Si excede max, seleccionar por prioridad
|
|
if (count($filtered) > $maxAds) {
|
|
usort($filtered, fn($a, $b) =>
|
|
(self::ELEMENT_PRIORITIES[$b['type']] ?? 0) <=> (self::ELEMENT_PRIORITIES[$a['type']] ?? 0)
|
|
);
|
|
$filtered = array_slice($filtered, 0, $maxAds);
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
/**
|
|
* PASO 4-5 (modo priority): Ordenar por prioridad primero, luego aplicar espaciado
|
|
*/
|
|
private function filterByPriorityFirst(array $locations, int $minSpacing, int $maxAds): array
|
|
{
|
|
// PASO 4: Ordenar por prioridad
|
|
usort($locations, fn($a, $b) =>
|
|
(self::ELEMENT_PRIORITIES[$b['type']] ?? 0) <=> (self::ELEMENT_PRIORITIES[$a['type']] ?? 0)
|
|
);
|
|
|
|
// PASO 5: Filtrar por espaciado en orden de prioridad
|
|
$selected = [];
|
|
$usedIndices = [];
|
|
|
|
foreach ($locations as $loc) {
|
|
if (count($selected) >= $maxAds) {
|
|
break;
|
|
}
|
|
|
|
// Verificar espaciado con ubicaciones ya seleccionadas
|
|
$violatesSpacing = false;
|
|
foreach ($usedIndices as $usedIndex) {
|
|
if (abs($loc['element_index'] - $usedIndex) < $minSpacing) {
|
|
$violatesSpacing = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$violatesSpacing) {
|
|
$selected[] = $loc;
|
|
$usedIndices[] = $loc['element_index'];
|
|
}
|
|
}
|
|
|
|
return $selected;
|
|
}
|
|
|
|
/**
|
|
* PASO 6: Inserta los anuncios en las posiciones calculadas
|
|
*/
|
|
private function insertAds(string $content, array $locations, string $format): string
|
|
{
|
|
// Insertar de atras hacia adelante para no afectar posiciones
|
|
$locations = array_reverse($locations);
|
|
|
|
$adCount = count($locations);
|
|
$currentAd = $adCount;
|
|
|
|
foreach ($locations as $loc) {
|
|
$adHtml = $this->renderer->renderSlot($this->settings, 'post-content-adv-' . $currentAd);
|
|
$content = substr_replace($content, $adHtml, $loc['position'], 0);
|
|
$currentAd--;
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
// =========================================================================
|
|
// METODOS LEGACY (sin cambios para backward compatibility)
|
|
// =========================================================================
|
|
|
|
/**
|
|
* 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 (modo solo parrafos)
|
|
*
|
|
* @return int[] Indices de parrafos despues de los cuales insertar ads
|
|
*/
|
|
private function calculateParagraphsOnlyPositions(
|
|
int $totalParagraphs,
|
|
int $afterFirst,
|
|
int $minBetween,
|
|
int $minAds,
|
|
int $maxAds,
|
|
bool $randomMode
|
|
): array {
|
|
// Calcular posiciones disponibles respetando el espacio minimo
|
|
$availablePositions = [];
|
|
|
|
// 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 (modo solo parrafos)
|
|
*/
|
|
private function buildParagraphsOnlyContent(array $paragraphs, array $adPositions): string
|
|
{
|
|
$newContent = '';
|
|
$adsInserted = 0;
|
|
|
|
foreach ($paragraphs as $index => $paragraph) {
|
|
$newContent .= $paragraph;
|
|
|
|
// 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;
|
|
|
|
if (in_array($paragraphNumber, $adPositions, true)) {
|
|
$adsInserted++;
|
|
$adHtml = $this->renderer->renderSlot($this->settings, 'post-content-' . $adsInserted);
|
|
$newContent .= $adHtml;
|
|
}
|
|
}
|
|
|
|
return $newContent;
|
|
}
|
|
}
|