- Añadir PageVisibility use case y repositorio - Implementar PageTypeDetector para detectar home/single/page/archive - Actualizar FieldMappers con soporte show_on_[page_type] - Extender FormBuilders con UI de visibilidad por página - Refactorizar Renderers para evaluar visibilidad dinámica - Limpiar schemas removiendo campos de visibilidad legacy - Añadir MigrationCommand para migrar configuraciones existentes - Implementar adsense-loader.js para carga lazy de ads - Actualizar front-page.php con nueva estructura - Extender DIContainer con nuevos servicios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
378 lines
13 KiB
PHP
378 lines
13 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\Public\SocialShare\Infrastructure\Ui;
|
|
|
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
|
use ROITheme\Shared\Domain\Entities\Component;
|
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
|
|
|
/**
|
|
* SocialShareRenderer - Renderiza botones de compartir en redes sociales
|
|
*
|
|
* RESPONSABILIDAD: Generar HTML y CSS del componente Social Share
|
|
*
|
|
* CARACTERISTICAS:
|
|
* - 6 redes sociales: Facebook, Instagram, LinkedIn, WhatsApp, X, Email
|
|
* - Colores configurables por red
|
|
* - Toggle individual por red social
|
|
* - Estilos 100% desde BD via CSSGenerator
|
|
*
|
|
* Cumple con:
|
|
* - DIP: Recibe CSSGeneratorInterface por constructor
|
|
* - SRP: Una responsabilidad (renderizar social share)
|
|
* - Clean Architecture: Infrastructure puede usar WordPress
|
|
*
|
|
* @package ROITheme\Public\SocialShare\Infrastructure\Ui
|
|
*/
|
|
final class SocialShareRenderer implements RendererInterface
|
|
{
|
|
private const COMPONENT_NAME = 'social-share';
|
|
|
|
private const NETWORKS = [
|
|
'facebook' => [
|
|
'field' => 'show_facebook',
|
|
'url_field' => 'facebook_url',
|
|
'icon' => 'bi-facebook',
|
|
'label' => 'Facebook',
|
|
'share_pattern' => 'https://www.facebook.com/sharer/sharer.php?u=%s',
|
|
],
|
|
'instagram' => [
|
|
'field' => 'show_instagram',
|
|
'url_field' => 'instagram_url',
|
|
'icon' => 'bi-instagram',
|
|
'label' => 'Instagram',
|
|
'share_pattern' => '', // Instagram no tiene share directo - requiere URL configurada
|
|
],
|
|
'linkedin' => [
|
|
'field' => 'show_linkedin',
|
|
'url_field' => 'linkedin_url',
|
|
'icon' => 'bi-linkedin',
|
|
'label' => 'LinkedIn',
|
|
'share_pattern' => 'https://www.linkedin.com/sharing/share-offsite/?url=%s',
|
|
],
|
|
'whatsapp' => [
|
|
'field' => 'show_whatsapp',
|
|
'url_field' => 'whatsapp_number',
|
|
'icon' => 'bi-whatsapp',
|
|
'label' => 'WhatsApp',
|
|
'share_pattern' => 'https://wa.me/?text=%s', // Compartir via WhatsApp
|
|
],
|
|
'twitter' => [
|
|
'field' => 'show_twitter',
|
|
'url_field' => 'twitter_url',
|
|
'icon' => 'bi-twitter-x',
|
|
'label' => 'X',
|
|
'share_pattern' => 'https://twitter.com/intent/tweet?url=%s&text=%s',
|
|
],
|
|
'email' => [
|
|
'field' => 'show_email',
|
|
'url_field' => 'email_address',
|
|
'icon' => 'bi-envelope',
|
|
'label' => 'Email',
|
|
'share_pattern' => 'mailto:?subject=%s&body=%s', // Compartir via Email
|
|
],
|
|
];
|
|
|
|
public function __construct(
|
|
private CSSGeneratorInterface $cssGenerator
|
|
) {}
|
|
|
|
public function render(Component $component): string
|
|
{
|
|
$data = $component->getData();
|
|
|
|
if (!$this->isEnabled($data)) {
|
|
return '';
|
|
}
|
|
|
|
if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) {
|
|
return '';
|
|
}
|
|
|
|
$css = $this->generateCSS($data);
|
|
$html = $this->buildHTML($data);
|
|
|
|
return sprintf("<style>%s</style>\n%s", $css, $html);
|
|
}
|
|
|
|
public function supports(string $componentType): bool
|
|
{
|
|
return $componentType === self::COMPONENT_NAME;
|
|
}
|
|
|
|
private function isEnabled(array $data): bool
|
|
{
|
|
$value = $data['visibility']['is_enabled'] ?? false;
|
|
return $value === true || $value === '1' || $value === 1;
|
|
}
|
|
|
|
private function generateCSS(array $data): string
|
|
{
|
|
$colors = $data['colors'] ?? [];
|
|
$spacing = $data['spacing'] ?? [];
|
|
$typography = $data['typography'] ?? [];
|
|
$effects = $data['visual_effects'] ?? [];
|
|
$visibility = $data['visibility'] ?? [];
|
|
|
|
$cssRules = [];
|
|
$transitionDuration = $effects['transition_duration'] ?? '0.3s';
|
|
$buttonBorderWidth = $effects['button_border_width'] ?? '2px';
|
|
|
|
// Container styles
|
|
$cssRules[] = $this->cssGenerator->generate('.social-share-container', [
|
|
'margin-top' => $spacing['container_margin_top'] ?? '3rem',
|
|
'margin-bottom' => $spacing['container_margin_bottom'] ?? '3rem',
|
|
'padding-top' => $spacing['container_padding_top'] ?? '1.5rem',
|
|
'padding-bottom' => $spacing['container_padding_bottom'] ?? '1.5rem',
|
|
'border-top' => sprintf('%s solid %s',
|
|
$effects['border_top_width'] ?? '1px',
|
|
$colors['border_top_color'] ?? '#dee2e6'
|
|
),
|
|
]);
|
|
|
|
// Label styles
|
|
$cssRules[] = $this->cssGenerator->generate('.social-share-container .share-label', [
|
|
'font-size' => $typography['label_font_size'] ?? '1rem',
|
|
'color' => $colors['label_color'] ?? '#6c757d',
|
|
'margin-bottom' => $spacing['label_margin_bottom'] ?? '1rem',
|
|
]);
|
|
|
|
// Buttons wrapper
|
|
$cssRules[] = $this->cssGenerator->generate('.social-share-container .share-buttons', [
|
|
'display' => 'flex',
|
|
'flex-wrap' => 'wrap',
|
|
'gap' => $spacing['buttons_gap'] ?? '0.5rem',
|
|
]);
|
|
|
|
// Base button styles
|
|
$cssRules[] = $this->cssGenerator->generate('.social-share-container .share-buttons .btn', [
|
|
'padding' => $spacing['button_padding'] ?? '0.25rem 0.5rem',
|
|
'font-size' => $typography['icon_font_size'] ?? '1rem',
|
|
'border-width' => $buttonBorderWidth,
|
|
'border-radius' => $effects['button_border_radius'] ?? '0.375rem',
|
|
'transition' => "all {$transitionDuration} ease",
|
|
'background-color' => $colors['button_background'] ?? '#ffffff',
|
|
]);
|
|
|
|
// Hover effect
|
|
$cssRules[] = $this->cssGenerator->generate('.social-share-container .share-buttons .btn:hover', [
|
|
'box-shadow' => $effects['hover_box_shadow'] ?? '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
]);
|
|
|
|
// Network-specific colors
|
|
$networkColors = [
|
|
'facebook' => $colors['facebook_color'] ?? '#0d6efd',
|
|
'instagram' => $colors['instagram_color'] ?? '#dc3545',
|
|
'linkedin' => $colors['linkedin_color'] ?? '#0dcaf0',
|
|
'whatsapp' => $colors['whatsapp_color'] ?? '#198754',
|
|
'twitter' => $colors['twitter_color'] ?? '#212529',
|
|
'email' => $colors['email_color'] ?? '#6c757d',
|
|
];
|
|
|
|
foreach ($networkColors as $network => $color) {
|
|
// Outline style
|
|
$cssRules[] = $this->cssGenerator->generate(".social-share-container .btn-share-{$network}", [
|
|
'color' => $color,
|
|
'border-color' => $color,
|
|
]);
|
|
// Hover fills the button
|
|
$cssRules[] = $this->cssGenerator->generate(".social-share-container .btn-share-{$network}:hover", [
|
|
'background-color' => $color,
|
|
'color' => '#ffffff',
|
|
]);
|
|
}
|
|
|
|
// Responsive visibility (normalizar booleanos desde BD)
|
|
$showOnDesktop = $visibility['show_on_desktop'] ?? true;
|
|
$showOnDesktop = $showOnDesktop === true || $showOnDesktop === '1' || $showOnDesktop === 1;
|
|
$showOnMobile = $visibility['show_on_mobile'] ?? true;
|
|
$showOnMobile = $showOnMobile === true || $showOnMobile === '1' || $showOnMobile === 1;
|
|
|
|
if (!$showOnMobile) {
|
|
$cssRules[] = "@media (max-width: 991.98px) {
|
|
.social-share-container { display: none !important; }
|
|
}";
|
|
}
|
|
|
|
if (!$showOnDesktop) {
|
|
$cssRules[] = "@media (min-width: 992px) {
|
|
.social-share-container { display: none !important; }
|
|
}";
|
|
}
|
|
|
|
return implode("\n", $cssRules);
|
|
}
|
|
|
|
private function buildHTML(array $data): string
|
|
{
|
|
$content = $data['content'] ?? [];
|
|
$networks = $data['networks'] ?? [];
|
|
|
|
$labelText = $content['label_text'] ?? 'Compartir:';
|
|
$showLabel = $content['show_label'] ?? true;
|
|
$showLabel = $showLabel === true || $showLabel === '1' || $showLabel === 1;
|
|
|
|
$html = '<div class="social-share-container">';
|
|
|
|
// Label
|
|
if ($showLabel && !empty($labelText)) {
|
|
$html .= sprintf(
|
|
'<p class="share-label">%s</p>',
|
|
esc_html($labelText)
|
|
);
|
|
}
|
|
|
|
// Buttons wrapper
|
|
$html .= '<div class="share-buttons">';
|
|
|
|
// Get current post data for share URLs
|
|
$shareUrl = $this->getCurrentUrl();
|
|
$shareTitle = $this->getCurrentTitle();
|
|
|
|
foreach (self::NETWORKS as $networkKey => $networkData) {
|
|
$fieldKey = $networkData['field'];
|
|
$isEnabled = $networks[$fieldKey] ?? true;
|
|
$isEnabled = $isEnabled === true || $isEnabled === '1' || $isEnabled === 1;
|
|
|
|
if (!$isEnabled) {
|
|
continue;
|
|
}
|
|
|
|
// Obtener URL configurada para esta red
|
|
$urlFieldKey = $networkData['url_field'];
|
|
$configuredUrl = $networks[$urlFieldKey] ?? '';
|
|
|
|
$shareHref = $this->buildNetworkUrl(
|
|
$networkKey,
|
|
$configuredUrl,
|
|
$networkData['share_pattern'],
|
|
$shareUrl,
|
|
$shareTitle
|
|
);
|
|
|
|
// Si no hay URL válida usar "#" como fallback (para mantener el icono visible)
|
|
if (empty($shareHref)) {
|
|
$shareHref = '#';
|
|
}
|
|
|
|
$ariaLabel = sprintf('Compartir en %s', $networkData['label']);
|
|
|
|
$html .= sprintf(
|
|
'<a href="%s" class="btn btn-share-%s" aria-label="%s" target="_blank" rel="noopener noreferrer">
|
|
<i class="bi %s"></i>
|
|
</a>',
|
|
esc_url($shareHref),
|
|
esc_attr($networkKey),
|
|
esc_attr($ariaLabel),
|
|
esc_attr($networkData['icon'])
|
|
);
|
|
}
|
|
|
|
$html .= '</div>'; // .share-buttons
|
|
$html .= '</div>'; // .social-share-container
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function getCurrentUrl(): string
|
|
{
|
|
if (is_singular()) {
|
|
return get_permalink() ?: '';
|
|
}
|
|
return home_url(add_query_arg([], $GLOBALS['wp']->request ?? ''));
|
|
}
|
|
|
|
private function getCurrentTitle(): string
|
|
{
|
|
if (is_singular()) {
|
|
return get_the_title() ?: '';
|
|
}
|
|
return wp_title('', false) ?: get_bloginfo('name');
|
|
}
|
|
|
|
/**
|
|
* Construye la URL para un botón de red social
|
|
*
|
|
* Prioridad:
|
|
* 1. URL configurada por el usuario → enlace directo al perfil
|
|
* 2. Sin URL configurada → usar patrón de compartir (si existe)
|
|
*/
|
|
private function buildNetworkUrl(
|
|
string $network,
|
|
string $configuredUrl,
|
|
string $sharePattern,
|
|
string $pageUrl,
|
|
string $pageTitle
|
|
): string {
|
|
// Si hay URL configurada, usarla directamente
|
|
if (!empty($configuredUrl)) {
|
|
return $this->formatConfiguredUrl($network, $configuredUrl);
|
|
}
|
|
|
|
// Si no hay URL configurada pero existe patrón de compartir
|
|
if (!empty($sharePattern)) {
|
|
return $this->formatShareUrl($network, $sharePattern, $pageUrl, $pageTitle);
|
|
}
|
|
|
|
return '#';
|
|
}
|
|
|
|
/**
|
|
* Formatea URL configurada según el tipo de red
|
|
*/
|
|
private function formatConfiguredUrl(string $network, string $url): string
|
|
{
|
|
switch ($network) {
|
|
case 'whatsapp':
|
|
// Para WhatsApp, el número debe ir sin el +
|
|
$number = preg_replace('/[^0-9]/', '', $url);
|
|
return "https://wa.me/{$number}";
|
|
case 'email':
|
|
// Para email, agregar mailto: si no lo tiene
|
|
if (!str_starts_with($url, 'mailto:')) {
|
|
return "mailto:{$url}";
|
|
}
|
|
return $url;
|
|
default:
|
|
return $url;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Formatea URL de compartir usando el patrón
|
|
*/
|
|
private function formatShareUrl(string $network, string $pattern, string $url, string $title): string
|
|
{
|
|
$encodedUrl = rawurlencode($url);
|
|
$encodedTitle = rawurlencode($title);
|
|
|
|
switch ($network) {
|
|
case 'twitter':
|
|
return sprintf($pattern, $encodedUrl, $encodedTitle);
|
|
case 'whatsapp':
|
|
$text = $title . ' - ' . $url;
|
|
return sprintf($pattern, rawurlencode($text));
|
|
case 'email':
|
|
return sprintf($pattern, $encodedTitle, $encodedUrl);
|
|
default:
|
|
return sprintf($pattern, $encodedUrl);
|
|
}
|
|
}
|
|
|
|
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 '';
|
|
}
|
|
}
|