repository = new WordPressSnippetRepository($wpdb); $this->getAllUseCase = new GetAllSnippetsUseCase($this->repository); $this->saveUseCase = new SaveSnippetUseCase($this->repository); $this->deleteUseCase = new DeleteSnippetUseCase($this->repository); // Registrar handler de formulario POST $this->registerFormHandler(); } /** * Registra handler para procesar formularios POST */ private function registerFormHandler(): void { // Solo registrar una vez static $registered = false; if ($registered) { return; } $registered = true; add_action('admin_init', function() { $this->handleFormSubmission(); }); } /** * Procesa envío de formulario */ public function handleFormSubmission(): void { if (!isset($_POST['roi_css_action'])) { return; } // Verificar nonce if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', self::NONCE_ACTION)) { wp_die('Nonce verification failed'); } // Verificar permisos if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $action = sanitize_text_field($_POST['roi_css_action']); try { match ($action) { 'save' => $this->processSave($_POST), 'delete' => $this->processDelete($_POST), default => null, }; // Redirect con mensaje de éxito wp_redirect(add_query_arg('roi_message', 'success', wp_get_referer())); exit; } catch (ValidationException $e) { // Redirect con mensaje de error wp_redirect(add_query_arg([ 'roi_message' => 'error', 'roi_error' => urlencode($e->getMessage()) ], wp_get_referer())); exit; } } /** * Procesa guardado de snippet */ private function processSave(array $data): void { $id = sanitize_text_field($data['snippet_id'] ?? ''); // Generar ID si es nuevo if (empty($id)) { $id = SnippetId::generate()->value(); } $request = SaveSnippetRequest::fromArray([ 'id' => $id, 'name' => sanitize_text_field($data['snippet_name'] ?? ''), 'description' => sanitize_textarea_field($data['snippet_description'] ?? ''), 'css' => wp_strip_all_tags($data['snippet_css'] ?? ''), 'type' => sanitize_text_field($data['snippet_type'] ?? 'deferred'), 'pages' => array_map('sanitize_text_field', $data['snippet_pages'] ?? ['all']), 'enabled' => isset($data['snippet_enabled']), 'order' => absint($data['snippet_order'] ?? 100), ]); $this->saveUseCase->execute($request); } /** * Procesa eliminación de snippet */ private function processDelete(array $data): void { $id = sanitize_text_field($data['snippet_id'] ?? ''); if (empty($id)) { throw new ValidationException('ID de snippet requerido para eliminar'); } $this->deleteUseCase->execute($id); } /** * Construye el formulario del componente * * @param string $componentId ID del componente (custom-css-manager) * @return string HTML del formulario */ public function buildForm(string $componentId): string { $snippets = $this->getAllUseCase->execute(); $message = $this->getFlashMessage(); $html = ''; // Header $html .= $this->buildHeader($componentId, count($snippets)); // Mensajes flash if ($message) { $html .= sprintf( '
%s
', esc_attr($message['type']), esc_html($message['text']) ); } // Lista de snippets existentes $html .= $this->buildSnippetsList($snippets); // Formulario de creación/edición $html .= $this->buildSnippetForm(); // JavaScript $html .= $this->buildJavaScript(); return $html; } /** * Construye el header del componente */ private function buildHeader(string $componentId, int $snippetCount): string { $html = '
'; $html .= '
'; $html .= '

Gestor de CSS Personalizado

'; $html .= ' ' . esc_html($snippetCount) . ' snippet(s)'; $html .= '
'; $html .= '
'; $html .= '

Gestiona snippets de CSS personalizados. Los snippets críticos se cargan en el head, los diferidos en el footer.

'; $html .= '
'; $html .= '
'; return $html; } /** * Construye la lista de snippets existentes */ private function buildSnippetsList(array $snippets): string { $html = '
'; $html .= '
'; $html .= '
'; $html .= ' '; $html .= ' Snippets Configurados'; $html .= '
'; if (empty($snippets)) { $html .= '

No hay snippets configurados.

'; } else { $html .= '
'; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; $html .= ' '; foreach ($snippets as $snippet) { $html .= $this->renderSnippetRow($snippet); } $html .= ' '; $html .= '
NombreTipoPáginasEstadoAcciones
'; $html .= '
'; } $html .= '
'; $html .= '
'; return $html; } /** * Renderiza una fila de snippet en la tabla */ private function renderSnippetRow(array $snippet): string { $id = esc_attr($snippet['id']); $name = esc_html($snippet['name']); $type = $snippet['type'] === 'critical' ? 'Crítico' : 'Diferido'; $typeBadge = $snippet['type'] === 'critical' ? 'bg-danger' : 'bg-info'; $pages = implode(', ', $snippet['pages'] ?? ['all']); $enabled = ($snippet['enabled'] ?? false) ? 'Activo' : 'Inactivo'; $enabledBadge = ($snippet['enabled'] ?? false) ? 'bg-success' : 'bg-secondary'; // Usar data-attribute para JSON seguro $snippetJson = esc_attr(wp_json_encode($snippet, JSON_HEX_APOS | JSON_HEX_QUOT)); $nonce = wp_create_nonce(self::NONCE_ACTION); return << {$name} {$type} {$pages} {$enabled}
HTML; } /** * Construye el formulario de creación/edición de snippets */ private function buildSnippetForm(): string { $nonce = wp_create_nonce(self::NONCE_ACTION); $html = '
'; $html .= '
'; $html .= '
'; $html .= ' '; $html .= ' Agregar/Editar Snippet'; $html .= '
'; $html .= '
'; $html .= ' '; $html .= ' '; $html .= ' '; $html .= '
'; // Nombre $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; // Tipo $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; // Orden $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; // Descripción $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; // Páginas $html .= '
'; $html .= ' '; $html .= '
'; foreach ($this->getPageOptions() as $value => $label) { $checked = $value === 'all' ? 'checked' : ''; $html .= sprintf( '
', esc_attr($value), esc_attr($value), $checked, esc_attr($value), esc_html($label) ); } $html .= '
'; $html .= '
'; // Estado $html .= '
'; $html .= ' '; $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; $html .= '
'; // Código CSS $html .= '
'; $html .= ' '; $html .= ' '; $html .= ' Crítico: máx 14KB | Diferido: máx 100KB'; $html .= '
'; // Botones $html .= '
'; $html .= ' '; $html .= ' '; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; return $html; } /** * Genera el JavaScript necesario para el formulario */ private function buildJavaScript(): string { return << // Event delegation para botones de edición document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.btn-edit-snippet').forEach(function(btn) { btn.addEventListener('click', function() { const snippet = JSON.parse(this.dataset.snippet); editCssSnippet(snippet); }); }); }); function editCssSnippet(snippet) { document.getElementById('snippet_id').value = snippet.id; document.getElementById('snippet_name').value = snippet.name; document.getElementById('snippet_description').value = snippet.description || ''; document.getElementById('snippet_type').value = snippet.type; document.getElementById('snippet_order').value = snippet.order || 100; document.getElementById('snippet_css').value = snippet.css; document.getElementById('snippet_enabled').checked = snippet.enabled; // Actualizar checkboxes de páginas document.querySelectorAll('input[name="snippet_pages[]"]').forEach(cb => { cb.checked = (snippet.pages || ['all']).includes(cb.value); }); document.getElementById('snippet_name').focus(); // Scroll al formulario document.getElementById('roi-snippet-form').scrollIntoView({ behavior: 'smooth' }); } function resetCssForm() { document.getElementById('roi-snippet-form').reset(); document.getElementById('snippet_id').value = ''; } JS; } /** * Opciones de páginas disponibles */ private function getPageOptions(): array { return [ 'all' => 'Todas', 'home' => 'Inicio', 'posts' => 'Posts', 'pages' => 'Páginas', 'archives' => 'Archivos', ]; } /** * Obtiene mensaje flash de la URL */ private function getFlashMessage(): ?array { $message = $_GET['roi_message'] ?? null; if ($message === 'success') { return ['type' => 'success', 'text' => 'Snippet guardado correctamente']; } if ($message === 'error') { $error = urldecode($_GET['roi_error'] ?? 'Error desconocido'); return ['type' => 'danger', 'text' => $error]; } return null; } }